Product Browsing Flow

Navigate the product catalog from categories down to individual product details.

What you'll build

A complete catalog-browsing experience: load categories, fetch available filters, list products by category with pagination, view enriched product detail (v2), then explore a brand and its products.

Prerequisites

  • A Store UUID — e.g. e87437f2-3e35-4738-af5e-6307e368255c (staging)
  • cURL or any HTTP client

No authentication is required. All endpoints in this flow use the jwt_optional_authenticated pipeline.


Step 1: Get Categories

Load the category tree to build the navigation menu.

cURL

curl -X GET https://ecom-api.staging.blaze.me/api/v1/products/categories \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"

JavaScript (fetch)

const STORE_UUID = "e87437f2-3e35-4738-af5e-6307e368255c";
const BASE_URL = "https://ecom-api.staging.blaze.me";
const headers = {
  "Content-Type": "application/vnd.api+json",
  "Accept": "application/vnd.api+json",
  "X-Store": STORE_UUID,
};

const catResponse = await fetch(`${BASE_URL}/api/v1/products/categories`, { headers });
const { data: categories } = await catResponse.json();

categories.forEach((cat) => {
  console.log(`${cat.attributes.name} (${cat.attributes.count} products)`);
});

Response

{
  "data": [
    {
      "id": "cat-flower-001",
      "type": "product_categories",
      "attributes": {
        "name": "Flower",
        "slug": "flower",
        "description": "Cannabis flower buds",
        "count": 48,
        "position": 1,
        "is_active": true
      }
    },
    {
      "id": "cat-edible-002",
      "type": "product_categories",
      "attributes": {
        "name": "Edibles",
        "slug": "edibles",
        "description": "Cannabis-infused food products",
        "count": 32,
        "position": 2,
        "is_active": true
      }
    }
  ]
}

Tip: Use position to order categories in the UI. Use slug when filtering products by category.


Step 2: Get Available Filters

Fetch the filter dimensions so the sidebar can show checkboxes, sliders, and counts.

cURL

curl -X GET https://ecom-api.staging.blaze.me/api/v1/products/filters \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"

JavaScript (fetch)

const filterResponse = await fetch(`${BASE_URL}/api/v1/products/filters`, { headers });
const { data: filters } = await filterResponse.json();

console.log("Categories:", filters.attributes.categories);
console.log("Types:", filters.attributes.types);
console.log("Brands:", filters.attributes.brands);
console.log("Price range:", filters.attributes.price_ranges);

Response

{
  "data": {
    "id": "filters",
    "type": "filters",
    "attributes": {
      "categories": [
        { "name": "Flower", "slug": "flower", "count": 48 },
        { "name": "Edibles", "slug": "edibles", "count": 32 }
      ],
      "types": [
        { "name": "Hybrid", "count": 25 },
        { "name": "Indica", "count": 18 },
        { "name": "Sativa", "count": 12 }
      ],
      "brands": [
        { "name": "Cookies", "slug": "cookies", "count": 15 },
        { "name": "Stiiizy", "slug": "stiiizy", "count": 22 }
      ],
      "price_ranges": {
        "min": 5.0,
        "max": 120.0
      }
    }
  }
}

Step 3: List Products by Category

The user clicks "Flower" — fetch the first page of products in that category.

cURL

curl -X GET "https://ecom-api.staging.blaze.me/api/v1/products?category=flower&limit=10&offset=0" \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"

JavaScript (fetch)

const params = new URLSearchParams({
  category: "flower",
  limit: "10",
  offset: "0",
});

const prodResponse = await fetch(`${BASE_URL}/api/v1/products?${params}`, { headers });
const { data: products, meta } = await prodResponse.json();

console.log(`Showing ${products.length} of ${meta.total} products`);
products.forEach((p) => {
  const { name, unit_price, type, in_stock } = p.attributes;
  console.log(`${name} — $${unit_price} (${type}) ${in_stock ? "✓" : "✗"}`);
});

Response

{
  "data": [
    {
      "id": "b5c8d1e2-f3a4-5678-9012-abcdef123456",
      "type": "store_products",
      "attributes": {
        "name": "Blue Dream",
        "slug": "blue-dream",
        "sku": "BD-001",
        "description": "A popular sativa-dominant hybrid",
        "type": "Hybrid",
        "flower_type": "Hybrid",
        "thc": "21%",
        "cbd": "0.1%",
        "in_stock": true,
        "is_promoted": false,
        "unit_price": 45.00,
        "main_image": "https://images.example.com/blue-dream.jpg"
      }
    }
  ],
  "meta": {
    "total": 48,
    "limit": 10,
    "offset": 0
  }
}

Pagination

Load the next page by incrementing offset:

# Page 2
curl -X GET "https://ecom-api.staging.blaze.me/api/v1/products?category=flower&limit=10&offset=10" \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"
// Generic pagination helper
async function fetchPage(category, page, pageSize = 10) {
  const params = new URLSearchParams({
    category,
    limit: String(pageSize),
    offset: String((page - 1) * pageSize),
  });
  const res = await fetch(`${BASE_URL}/api/v1/products?${params}`, { headers });
  return res.json();
}

const page2 = await fetchPage("flower", 2);
console.log(`Page 2: ${page2.data.length} products`);

Step 4: Get Product Detail (v2)

The user clicks a product — fetch the enriched v2 detail with variants, relationships, and included resources.

cURL

curl -X GET https://ecom-api.staging.blaze.me/api/v2/products/blue-dream \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"

JavaScript (fetch)

const productSlug = "blue-dream";

const detailResponse = await fetch(`${BASE_URL}/api/v2/products/${productSlug}`, { headers });
const { data: product, included } = await detailResponse.json();

console.log(product.attributes.name);          // "Blue Dream"
console.log(product.attributes.weight_prices);  // variant pricing array
console.log(product.relationships);             // linked brands, categories, images

// Resolve a relationship from the included array
const brandRef = product.relationships.product_brands?.data?.[0];
if (brandRef) {
  const brand = included.find((r) => r.type === brandRef.type && r.id === brandRef.id);
  console.log(`Brand: ${brand.attributes.name}`); // "Cookies"
}

Response

{
  "data": {
    "id": "b5c8d1e2-f3a4-5678-9012-abcdef123456",
    "type": "store_products",
    "attributes": {
      "name": "Blue Dream",
      "slug": "blue-dream",
      "sku": "BD-001",
      "type": "Hybrid",
      "thc": "21%",
      "cbd": "0.1%",
      "in_stock": true,
      "unit_price": 45.00,
      "weight_prices": [
        { "weight": 1.0, "weight_unit": "g", "price": 15.00, "original_price": null, "in_stock": true },
        { "weight": 3.5, "weight_unit": "g", "price": 45.00, "original_price": null, "in_stock": true }
      ],
      "unit_prices": [
        {
          "display_name": "1g",
          "quantity": 1.0,
          "price": { "amount": 15.00, "currency": "USD" }
        }
      ]
    },
    "relationships": {
      "product_brands": {
        "data": [{ "id": "brand-001", "type": "product_brands" }]
      },
      "product_categories": {
        "data": [{ "id": "cat-flower-001", "type": "product_categories" }]
      }
    }
  },
  "included": [
    {
      "id": "brand-001",
      "type": "product_brands",
      "attributes": {
        "name": "Cookies",
        "slug": "cookies"
      }
    }
  ]
}

v2 vs v1: The v2 product detail includes weight_prices, unit_prices, relationships, and sideloaded included resources. Always prefer v2 for product detail pages.


Step 5: View Brand Detail

The user clicks the brand name — fetch brand info from the v2 brands endpoint.

cURL

curl -X GET https://ecom-api.staging.blaze.me/api/v2/products/brands \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"

JavaScript (fetch)

const brandsResponse = await fetch(`${BASE_URL}/api/v2/products/brands`, { headers });
const { data: brands } = await brandsResponse.json();

const targetBrand = brands.find((b) => b.attributes.slug === "cookies");
console.log(targetBrand.attributes.name);        // "Cookies"
console.log(targetBrand.attributes.description);  // "Premium cannabis brand"
console.log(targetBrand.attributes.logo_url);     // brand logo image
console.log(targetBrand.attributes.count);        // 15 products

Response

{
  "data": [
    {
      "id": "brand-001",
      "type": "product_brands",
      "attributes": {
        "name": "Cookies",
        "slug": "cookies",
        "description": "Premium cannabis brand",
        "logo_url": "https://images.example.com/brands/cookies.png",
        "count": 15,
        "is_promoted": true
      }
    },
    {
      "id": "brand-002",
      "type": "product_brands",
      "attributes": {
        "name": "Stiiizy",
        "slug": "stiiizy",
        "count": 22,
        "is_promoted": false
      }
    }
  ]
}

Step 6: List Products by Brand

Show all products from the selected brand.

cURL

curl -X GET "https://ecom-api.staging.blaze.me/api/v1/products?brand=cookies&limit=20" \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"

JavaScript (fetch)

const brandParams = new URLSearchParams({
  brand: "cookies",
  limit: "20",
});

const brandProductsRes = await fetch(`${BASE_URL}/api/v1/products?${brandParams}`, { headers });
const { data: brandProducts, meta: brandMeta } = await brandProductsRes.json();

console.log(`${brandMeta.total} products by Cookies`);
brandProducts.forEach((p) => {
  console.log(`  ${p.attributes.name} — $${p.attributes.unit_price}`);
});

Putting It All Together

const STORE_UUID = "e87437f2-3e35-4738-af5e-6307e368255c";
const BASE_URL = "https://ecom-api.staging.blaze.me";
const headers = {
  "Content-Type": "application/vnd.api+json",
  "Accept": "application/vnd.api+json",
  "X-Store": STORE_UUID,
};

// 1. Load categories for the nav menu
const { data: categories } = await (
  await fetch(`${BASE_URL}/api/v1/products/categories`, { headers })
).json();
console.log(`${categories.length} categories loaded`);

// 2. Load filters for the sidebar
const { data: filters } = await (
  await fetch(`${BASE_URL}/api/v1/products/filters`, { headers })
).json();
console.log(`Price range: $${filters.attributes.price_ranges.min}–$${filters.attributes.price_ranges.max}`);

// 3. User selects "Flower" category — list products
const { data: products, meta } = await (
  await fetch(`${BASE_URL}/api/v1/products?category=flower&limit=10`, { headers })
).json();
console.log(`${meta.total} flower products, showing first ${products.length}`);

// 4. User clicks a product — get v2 detail
const firstProduct = products[0];
const { data: detail, included } = await (
  await fetch(`${BASE_URL}/api/v2/products/${firstProduct.id}`, { headers })
).json();
console.log(`Product: ${detail.attributes.name}`);
console.log(`Variants: ${detail.attributes.weight_prices?.length ?? 0}`);

// 5. User clicks the brand — list brand products
const brandSlug = included?.find((r) => r.type === "product_brands")?.attributes?.slug;
if (brandSlug) {
  const { data: brandProducts } = await (
    await fetch(`${BASE_URL}/api/v1/products?brand=${brandSlug}&limit=20`, { headers })
  ).json();
  console.log(`${brandProducts.length} products by ${brandSlug}`);
}

What's Next?