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.
v2 Filters (Recommended)
Endpoint: GET /api/v2/products/filters
The v2 endpoint returns filter options as JSON
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
{
"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.
v2 (Recommended)
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:
HybridIndicaSativaCBD(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 |