Products & Catalog

Browse, filter, sort, and search the product catalog. Build dynamic filter UIs and display rich product detail pages.

What you'll learn

  • How to list products with pagination
  • How to filter products by category, type, brand, tags, price range, and potency
  • How to use the filters meta-endpoint to build dynamic filter UIs
  • How to search products by keyword
  • How to sort products
  • How to fetch product details (v1 vs v2)
  • How to navigate brands, categories, types, and tags
  • How to display showcased product groups
  • How to interpret product data: weight_prices, unit_prices, variants, THC/CBD, potency

Prerequisites

  • A Store UUID (staging: e87437f2-3e35-4738-af5e-6307e368255c)
  • cURL or any HTTP client
  • No authentication required — all catalog endpoints are tokenless (jwt_optional_authenticated)

Browsing Products

List Products

Fetch a paginated list of products for the store.

Endpoint: GET /api/v1/products

Query Parameters:

Parameter Default Description
limit 10 Number of products to return
offset 0 Number of products to skip
order date Sort order (see Sorting)

cURL

curl -X GET "https://ecom-api.staging.blaze.me/api/v1/products?limit=20&offset=0&order=brand" \
  -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 params = new URLSearchParams({
  limit: "20",
  offset: "0",
  order: "brand",
});

const response = await fetch(`${BASE_URL}/api/v1/products?${params}`, {
  headers: {
    "Content-Type": "application/vnd.api+json",
    Accept: "application/vnd.api+json",
    "X-Store": STORE_UUID,
  },
});

const { data, meta, included } = await response.json();
console.log(`Showing ${data.length} of ${meta.total_count} products`);

Response Shape

{
  "data": [
    {
      "id": "product-uuid",
      "type": "store_products",
      "attributes": {
        "name": "Blue Dream",
        "slug": "blue-dream",
        "sku": "BD-001",
        "type": "Flower",
        "strain": "Hybrid",
        "flower_type": "Hybrid",
        "description": "A balanced hybrid...",
        "in_stock": true,
        "is_promoted": false,
        "unit_price": 45.00,
        "discount": null,
        "thc": 22.5,
        "cbd": 0.1,
        "min_cbd": null,
        "potency": "22.5% THC",
        "cannabis_weight": "3.5g",
        "main_image": "https://...",
        "weight_prices": [ ... ],
        "unit_prices": [ ... ],
        "size": { "amount": 3.5, "units": "g" },
        "terpenoids": [ ... ],
        "composition": null,
        "external_id": "pos-123"
      },
      "relationships": {
        "product_brands": { "data": [{ "id": "brand-uuid", "type": "product_brands" }] },
        "product_categories": { "data": [{ "id": "cat-uuid", "type": "product_categories" }] },
        "product_images": { "data": [ ... ] },
        "global_brands": { "data": [ ... ] },
        "tags": { "data": [ ... ] }
      }
    }
  ],
  "included": [
    { "id": "brand-uuid", "type": "product_brands", "attributes": { "name": "Premium Farms" } },
    { "id": "cat-uuid", "type": "product_categories", "attributes": { "name": "Flower" } }
  ],
  "meta": {
    "offset": 0,
    "limit": 20,
    "total_count": 150
  }
}

Pagination

Use limit and offset to paginate through results:

GET /api/v1/products?limit=20&offset=0   → Page 1
GET /api/v1/products?limit=20&offset=20  → Page 2
GET /api/v1/products?limit=20&offset=40  → Page 3

The meta.total_count field tells you the total number of matching products, so you can calculate the total number of pages.


Filtering Products

Filter products by passing query parameters to GET /api/v1/products.

Filter Parameters:

Parameter Type Description
category string Category slug or ID
category_exclude string Category slug or ID to exclude
category_external_id string External category ID (from POS)
type string Product type (e.g., Flower, Edible, Concentrate)
brand string Brand slug or ID
brand_external_id string External brand ID (from POS)
tag string Tag name
min_price number Minimum price
max_price number Maximum price
min_thc number Minimum THC content
max_thc number Maximum THC content
min_cbd number Minimum CBD content
max_cbd number Maximum CBD content
weight string Weight filter
on_sale boolean Only products on sale (true)
q string Search query (keyword search)
delivery_type string One of: all, pickup, express, scheduled_delivery, valid_for_sale_only, kiosk
excludes string Product IDs to exclude

Example: Filter by Category + Price Range

cURL

curl -X GET "https://ecom-api.staging.blaze.me/api/v1/products?category=flower&min_price=20&max_price=80&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 params = new URLSearchParams({
  category: "flower",
  min_price: "20",
  max_price: "80",
  limit: "20",
});

const response = await fetch(`${BASE_URL}/api/v1/products?${params}`, {
  headers: {
    "Content-Type": "application/vnd.api+json",
    Accept: "application/vnd.api+json",
    "X-Store": STORE_UUID,
  },
});

const { data, meta } = await response.json();
console.log(`${meta.total_count} flower products between $20–$80`);

Combining Filters

All filter parameters can be combined:

# Hybrid flowers, $30–$60, THC above 20%, on sale
curl -X GET "https://ecom-api.staging.blaze.me/api/v1/products?category=flower&type=Hybrid&min_price=30&max_price=60&min_thc=20&on_sale=true&limit=10" \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "X-Store: e87437f2-3e35-4738-af5e-6307e368255c"

Available Filters Endpoint

Use the filters endpoint to discover which filter options are available for the current store. This powers dynamic filter UIs — only showing categories, types, brands, and tags that actually have products.

Endpoint: GET /api/v2/products/filters

The v2 endpoint returns filter options as JSON relationships with included resources, making it easier to build UI components.

cURL

curl -X GET "https://ecom-api.staging.blaze.me/api/v2/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 response = await fetch(`${BASE_URL}/api/v2/products/filters`, {
  headers: {
    "Content-Type": "application/vnd.api+json",
    Accept: "application/vnd.api+json",
    "X-Store": STORE_UUID,
  },
});

const { data, included } = await response.json();

// Extract filter options from included resources
const categories = included.filter((r) => r.type === "product_categories");
const brands = included.filter((r) => r.type === "product_brands");
const types = included.filter((r) => r.type === "product_types");
const tags = included.filter((r) => r.type === "tags");

console.log(`${categories.length} categories, ${brands.length} brands`);

Response Shape (v2)

The v2 filters response uses JSON relationships to reference the filter options, which are included as sideloaded resources:

{
  "data": {
    "type": "filters",
    "relationships": {
      "categories": {
        "data": [{ "id": "cat-1", "type": "product_categories" }]
      },
      "types": { "data": [{ "id": "Flower", "type": "product_types" }] },
      "brands": { "data": [{ "id": "brand-1", "type": "product_brands" }] },
      "tags": { "data": [{ "id": "tag-1", "type": "tags" }] }
    },
    "attributes": {
      "price_ranges": { "min": 5.0, "max": 500.0 },
      "thc_ranges": { "min": 0, "max": 35, "unit": "%" },
      "cbd_ranges": { "min": 0, "max": 25, "unit": "%" },
      "weights": ["1g", "3.5g", "7g", "14g", "28g"],
      "on_sale": { "count": 12 }
    }
  },
  "included": [
    {
      "id": "cat-1",
      "type": "product_categories",
      "attributes": { "name": "Flower", "slug": "flower", "count": 45 }
    },
    {
      "id": "Flower",
      "type": "product_types",
      "attributes": { "name": "Flower", "count": 45 }
    }
  ]
}

v1 Filters

Endpoint: GET /api/v1/products/filters

The v1 endpoint returns filter data in a simpler flat structure. Consider using v2 for new integrations.

Scoped Filters

You can pass the same filter parameters to the filters endpoint to get filters scoped to a subset of products. For example, to get available brands within the "Flower" category:

GET /api/v1/products/filters?category=flower

This is useful for building cascading filter UIs where selecting one filter updates the available options in other filters.


Searching Products

Use the q parameter to search products by keyword:

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

The search matches against product name, description, brand name, and other text fields.

You can combine q with other filters:

# Search for "dream" in the Flower category
GET /api/v1/products?q=dream&category=flower&limit=10

Sorting Products

Use the order query parameter. Prefix with - for descending order.

Available Sort Options:

Value Description
date Date added (default)
brand Brand name A→Z
-brand Brand name Z→A
price Price low→high
-price Price high→low
name Product name A→Z
-name Product name Z→A
thc THC content low→high
-thc THC content high→low
cbd CBD content low→high
-cbd CBD content high→low
size Package size ascending
-size Package size descending

Example — sort by price, cheapest first:

GET /api/v1/products?order=price&limit=20

Example — sort by THC content, highest first:

GET /api/v1/products?order=-thc&limit=20

Product Detail

Fetch a single product by its UUID or slug.

Endpoint: GET /api/v2/products/{id}

The v2 endpoint returns enriched data with complete variant information and inventory details.

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 response = await fetch(`${BASE_URL}/api/v2/products/${productSlug}`, {
  headers: {
    "Content-Type": "application/vnd.api+json",
    Accept: "application/vnd.api+json",
    "X-Store": STORE_UUID,
  },
});

const { data, included } = await response.json();

console.log(data.attributes.name);
console.log(`THC: ${data.attributes.thc}%`);
console.log(`Price: $${data.attributes.unit_price}`);

// Get brand from included resources
const brand = included?.find((r) => r.type === "product_brands");
console.log(`Brand: ${brand?.attributes.name}`);

v1

Endpoint: GET /api/v1/products/{id}

The v1 endpoint returns the same core product data but with a simpler structure. Use v2 for new integrations.

v1 vs v2 Differences

Feature v1 v2
Basic product data
Relationships (brands, categories, images)
Enriched variant/inventory data

Brands

List All Brands

Endpoint: GET /api/v1/products/brands

Returns all brands that have products in the store.

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

Query Parameters:

Parameter Description
q Search brands by name
category Filter brands by product category
type Filter brands by product type
tag Filter brands by product tag
ids Comma-separated brand IDs
slugs Comma-separated brand slugs
limit Pagination limit (default: 10)
offset Pagination offset (default: 0)

v2 Brands: GET /api/v2/products/brands — returns paginated results with meta containing count information.

Brand Response Shape

{
  "data": [
    {
      "id": "brand-uuid",
      "type": "product_brands",
      "attributes": {
        "name": "Premium Farms",
        "slug": "premium-farms",
        "description": "Craft cannabis since 2015",
        "logo_url": "https://...",
        "external_id": "pos-brand-123",
        "count": 15,
        "is_promoted": true
      }
    }
  ]
}

Brand Detail

Endpoint: GET /api/v1/products/brands/{slug_or_id}

Accepts either a brand slug or UUID.

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

Products by Brand

Endpoint: GET /api/v1/brands/{slug_or_id}/products

List all products for a specific brand:

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

Categories

List Categories

Endpoint: GET /api/v1/products/categories

Returns all active product categories with product counts.

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"

Query Parameters:

Parameter Description
q Search categories by name
delivery_type Filter by delivery type

v2 Categories: GET /api/v2/products/categories — returns paginated results with meta containing count information.

Category Response Shape

{
  "data": [
    {
      "id": "category-uuid",
      "type": "product_categories",
      "attributes": {
        "name": "Flower",
        "slug": "flower",
        "description": "Cannabis flower and buds",
        "count": 45,
        "position": 1,
        "is_active": true,
        "icon_url": "https://...",
        "parent_category_id": null
      }
    }
  ]
}

Categories support a hierarchy through the parent_category_id field. Top-level categories have a null parent.


Product Types

Endpoint: GET /api/v1/products/types

Returns the available product types (e.g., Flower, Edible, Concentrate) with counts.

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

Type Response Shape

{
  "data": [
    {
      "id": "Hybrid",
      "type": "product_types",
      "attributes": {
        "name": "Hybrid",
        "count": 30
      }
    }
  ]
}

Note: Type IDs are strings (the type name itself), not UUIDs.


Tags

Endpoint: GET /api/v1/products/tags

Returns the available product tags with metadata.

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

Query Parameters:

Parameter Description
q Search tags by name

Tag Response Shape

{
  "data": [
    {
      "id": "tag-uuid",
      "type": "tags",
      "attributes": {
        "name": "staff-pick",
        "title": "Staff Pick",
        "description": "Our team's favorites",
        "count": 8,
        "position": 1,
        "is_active": true,
        "is_featured": true,
        "is_hidden": false
      }
    }
  ]
}

Tags can be used for editorial curation (staff picks, new arrivals, seasonal selections). Use is_featured tags to highlight collections on the storefront, and is_hidden tags for internal categorization that shouldn't be shown to customers.


Price Ranges

Endpoint: GET /api/v1/products/price-ranges

Returns the minimum and maximum product prices for the store. Use this to set bounds on price filter sliders.

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

Response Shape

{
  "data": {
    "type": "price_ranges",
    "attributes": {
      "min": 5.0,
      "max": 500.0
    }
  }
}

Showcased Product Groups

Endpoint: GET /api/v1/products/showcased

Returns curated groups of products configured by the store (e.g., "Featured", "New Arrivals", "Best Sellers"). Use these to build homepage carousels and featured sections.

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

Response Shape

{
  "data": [
    {
      "id": "group-uuid",
      "type": "showcased_products",
      "attributes": {
        "name": "Featured Products",
        "slug": "featured-products",
        "type": "manual",
        "description": "Hand-picked by our team"
      },
      "relationships": {
        "products": {
          "data": [
            { "id": "product-uuid-1", "type": "store_products" },
            { "id": "product-uuid-2", "type": "store_products" }
          ]
        }
      }
    }
  ],
  "included": [ ... ]
}

Understanding Product Data

Pricing: unit_prices vs weight_prices

Products can be priced in two ways:

Unit-priced products have a single price per item. The unit_prices array provides pricing tiers:

{
  "unit_prices": [
    {
      "display_name": "Each",
      "quantity": 1,
      "price": { "amount": 45.0, "currency": "USD" },
      "discount_price": null,
      "savings_per_unit": null
    }
  ],
  "unit_price": 45.0
}

Weight-priced products are sold by weight. The weight_prices array lists each available weight option:

{
  "weight_prices": [
    { "weight": "1g", "price": 15.0, "discount_price": null },
    { "weight": "3.5g", "price": 45.0, "discount_price": 40.0 },
    { "weight": "7g", "price": 80.0, "discount_price": null },
    { "weight": "14g", "price": 150.0, "discount_price": null },
    { "weight": "28g", "price": 280.0, "discount_price": null }
  ]
}

A product has either unit_prices or weight_prices, not both. Check which is non-null to determine the pricing model:

function getDisplayPrice(product) {
  const attrs = product.attributes;
  if (attrs.weight_prices?.length > 0) {
    // Weight-priced: show starting price
    const cheapest = attrs.weight_prices[0];
    return `From $${cheapest.price}/${cheapest.weight}`;
  }
  // Unit-priced
  return `$${attrs.unit_price}`;
}

THC/CBD and Potency

Products include cannabinoid content data:

Field Description
thc THC percentage (single value)
min_thc THC range minimum (when range is provided instead of single value)
max_thc THC range maximum
cbd CBD percentage (single value)
min_cbd CBD range minimum
max_cbd CBD range maximum
potency Human-readable potency string (e.g., "22.5% THC")
cannabis_weight Net cannabis weight (e.g., "3.5g")

The potency field is a pre-formatted display string. For custom formatting, use the individual thc/cbd fields:

function formatPotency(product) {
  const { thc, min_thc, max_thc, cbd } = product.attributes;
  const thcDisplay =
    min_thc && max_thc
      ? `${min_thc}${max_thc}% THC`
      : thc
        ? `${thc}% THC`
        : null;
  const cbdDisplay = cbd ? `${cbd}% CBD` : null;
  return [thcDisplay, cbdDisplay].filter(Boolean).join(" · ");
}

Strain and Flower Type

The flower_type (or strain) field indicates the cannabis strain classification:

  • Hybrid
  • Indica
  • Sativa
  • CBD (high-CBD strains)

Product Size

The size field is an object with amount and units:

{
  "size": {
    "amount": 3.5,
    "units": "g"
  }
}

Relationships and Included Resources

Product list and detail responses include related resources via JSON relationships and included:

Relationship Type Description
product_brands product_brands Store-level brand
product_categories product_categories Product category
product_images product_images Additional product images
global_brands global_brands Cross-store brand (for multi-store groups)
tags tags Associated tags

To resolve a relationship, find the matching resource in the included array by id and type:

function resolveRelationship(response, type) {
  return response.included?.filter((r) => r.type === type) || [];
}

const brands = resolveRelationship(response, "product_brands");
const images = resolveRelationship(response, "product_images");

Endpoint Reference

All endpoints below are under jwt_optional_authenticated — no authentication required. All require the X-Store header.

Endpoint Description
GET /api/v1/products List products with filters and pagination
GET /api/v1/products/{id} Product detail (v1)
GET /api/v2/products/{id} Product detail (v2, recommended)
GET /api/v1/products/categories List categories
GET /api/v2/products/categories List categories (v2, paginated)
GET /api/v1/products/brands List brands
GET /api/v2/products/brands List brands (v2, paginated)
GET /api/v1/products/brands/{slug_or_id} Brand detail
GET /api/v1/brands/{slug_or_id}/products Products for a brand
GET /api/v1/products/types List product types
GET /api/v1/products/tags List tags
GET /api/v1/products/price-ranges Price range (min/max)
GET /api/v1/products/filters All filter options (v1)
GET /api/v2/products/filters All filter options (v2, recommended)
GET /api/v1/products/showcased Showcased product groups