Products Endpoint
Search for cannabis products across thousands of dispensaries. Filter by location, category, brand, potency, price, and more.
Use V2 (/v2/products) for all new integrations. V2 uses comma-separated parameters, supports all features including per_page and terpene profiles, and is the actively maintained endpoint. V1 remains available but is no longer recommended.
Request
GET https://api.cannmenus.com/v2/products
V1 is also available at /v1/products — see V1 Legacy Format below.
Core Parameters
- Name
states- Type
- string | string[]
- Description
US state(s) or Canadian province(s) to search. Use full names (e.g., "California", "Ontario"). Required in V1. Optional in V2 (but recommended for performance).
states=California states=California&states=Oregon
Location Parameters
Narrow results by geographic area:
- Name
lat- Type
- number
- Description
Latitude coordinate for location-based search. Must be used with
lng.
- Name
lng- Type
- number
- Description
Longitude coordinate for location-based search. Must be used with
lat.
- Name
distance- Type
- number
- Description
Radius in miles from the lat/lng point. Optional.
Product Filters
- Name
q- Type
- string
- Description
Full-text search across product name, brand name, category, subcategory, strain type, zipcode, and SKU fields.
q=blue dream
- Name
category- Type
- string
- Description
Product category. See Categories reference for valid values.
category=Flower category=Edible category=Vape
- Name
subcategory- Type
- string
- Description
Product subcategory. See Subcategories reference for valid values.
subcategory=Gummy subcategory=Cartridge
- Name
tags- Type
- string | string[]
- Description
Product tags for detailed filtering. See Tags reference for valid values.
tags=Live%20Resin tags=Indica&tags=Sugar%20Free
- Name
brand_name- Type
- string
- Description
Filter by brand name. Partial matching supported.
brand_name=Stiiizy brand_name=Cookies
- Name
brands- Type
- number | number[]
- Description
Filter by brand IDs (faster than
brand_name). Get IDs from the Brands endpoint.brands=789 brands=789&brands=1042
- Name
retailers- Type
- number | number[]
- Description
Filter to specific retailer IDs. Use the Retailers endpoint to find IDs.
retailers=10600 retailers=10600&retailers=10601
- Name
product_name- Type
- string
- Description
Search product names. Partial matching supported.
product_name=Blue%20Dream
- Name
skus- Type
- string | string[]
- Description
Filter by specific Cann SKU IDs.
skus=SKU123 skus=SKU123&skus=SKU456
- Name
display_weight- Type
- string
- Description
Filter by exact product weight/size.
display_weight=3.5g
- Name
quantity_per_package- Type
- number
- Description
Filter by package quantity (e.g., number of gummies in a pack).
quantity_per_package=10
- Name
menu_provider- Type
- string
- Description
Filter by menu data source. Options:
Dutchie,Weedmaps,Leafly,IHeartJane.menu_provider=Dutchie
Potency Filters
Filter by exact cannabinoid content:
- Name
percentage_thc- Type
- number
- Description
Filter by exact THC percentage. For flower, concentrates, vapes.
percentage_thc=25.5
- Name
percentage_cbd- Type
- number
- Description
Filter by exact CBD percentage.
percentage_cbd=1.0
- Name
mg_thc- Type
- number
- Description
Filter by exact THC milligrams. For edibles, tinctures, topicals.
mg_thc=100
- Name
mg_cbd- Type
- number
- Description
Filter by exact CBD milligrams.
mg_cbd=50
Price Filter
- Name
latest_price- Type
- number
- Description
Filter by exact product price in dollars.
latest_price=45.00
Menu Type
- Name
recreational- Type
- boolean
- Description
Filter to recreational products only.
recreational=true
- Name
medical- Type
- boolean
- Description
Filter to medical products only.
medical=true
Inventory & Source Filters
- Name
include_below_threshold- Type
- boolean
- Description
Whether to include low-inventory products that may be out of stock soon. Default:
true. Set tofalseto exclude these products. Each product in the response includes anis_below_thresholdfield so you can identify them.include_below_threshold=false
- Name
include_all_menu_providers- Type
- boolean
- Description
Include listings from all menu data sources. Default:
false(returns only the best source per retailer for faster results).include_all_menu_providers=true
Terpene Profiles
Terpene data is opt-in to keep default responses fast. Set include_terpenes=true to add terpene profiles.
- Name
include_terpenes- Type
- boolean
- Description
Include terpene profiles in response. Default:
false. Whentrue, products that have terpene data will include aterpene_profileobject.include_terpenes=true
- Name
include_terpene_fallback- Type
- boolean
- Description
When
include_terpenes=true, also check other listings of the same product (same UUID) for terpene data. This increases coverage from ~13% to ~20% of products. Addsterpene_sourceandterpene_source_product_idfields for data provenance. Default:false.include_terpenes=true&include_terpene_fallback=true
Terpene Response Fields
When include_terpenes=true:
| Field | Type | Description |
|---|---|---|
terpene_profile | object | Terpene concentrations as fractions (0-1). Keys are terpene names. null if no data available. |
terpene_source | string | exact = from this listing, uuid_match = from another listing of same product. Only present with include_terpene_fallback=true. |
terpene_source_product_id | number | Source product ID when terpene_source is uuid_match. |
Pagination
- Name
page- Type
- number
- Description
Page number to retrieve. Default:
1.
- Name
per_page- Type
- number
- Description
Results per page. Default:
20. Max:200. Use higher values to reduce total API calls for bulk pulls.per_page=200
Example Requests
Basic Search
Search for flower in California:
curl "https://api.cannmenus.com/v2/products?states=California&category=Flower&page=1" \
-H "X-Token: YOUR_API_TOKEN"
Location-Based Search
Find edibles within 5 miles of Denver:
curl "https://api.cannmenus.com/v2/products?states=Colorado&category=Edible&lat=39.7392&lng=-104.9903&distance=5&page=1" \
-H "X-Token: YOUR_API_TOKEN"
Search by Brand Name
Find Stiiizy products in California:
curl "https://api.cannmenus.com/v2/products?states=California&brand_name=Stiiizy&page=1" \
-H "X-Token: YOUR_API_TOKEN"
Full-Text Search
Search for "blue dream" across all fields:
curl "https://api.cannmenus.com/v2/products?states=Washington&q=blue%20dream&page=1" \
-H "X-Token: YOUR_API_TOKEN"
Multiple Filters
Live resin vapes from a specific brand in Nevada:
curl "https://api.cannmenus.com/v2/products?states=Nevada&category=Vape&brand_name=Stiiizy&tags=Live%20Resin&page=1" \
-H "X-Token: YOUR_API_TOKEN"
Response
{
"data": [
{
"retailer_id": "10600",
"sku": "07A0Sbb8OieeaR8rIW6MVHRw",
"products": [
{
"cann_sku_id": "07A0Sbb8OieeaR8rIW6MVHRw",
"brand_name": "Cookies",
"brand_id": 1042,
"url": "https://dutchie.com/store/example/product/gary-payton",
"image_url": "https://images.dutchie.com/products/gary-payton.jpg",
"raw_product_name": "Cookies - Gary Payton 3.5g",
"product_name": "Gary Payton",
"raw_weight_string": "3.5g",
"display_weight": "3.5g",
"raw_product_category": "Flower",
"category": "Flower",
"raw_subcategory": "Flower",
"subcategory": null,
"product_tags": ["Hybrid", "Indoor"],
"percentage_thc": 28.5,
"percentage_cbd": 0.1,
"mg_thc": null,
"mg_cbd": null,
"quantity_per_package": 1,
"medical": false,
"recreational": true,
"latest_price": 55.00,
"original_price": 65.00,
"menu_provider": "Dutchie",
"is_below_threshold": false,
"is_stock_image": false,
"terpene_profile": {
"Myrcene": 0.0089,
"Limonene": 0.0072,
"Caryophyllene": 0.0045
},
"terpene_source": "uuid_match",
"terpene_source_product_id": 193452100
}
]
}
],
"pagination": {
"total_records": 1250,
"current_page": 1,
"total_pages": 63,
"next_page": 2,
"prev_page": null
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
retailer_id | string | Unique dispensary identifier |
sku | string | Unique product variant identifier |
products | array | Listings for this SKU (may include multiple menu providers) |
cann_sku_id | string | Same as parent sku |
brand_name | string | Brand name |
brand_id | number | Unique brand identifier |
url | string | Direct link to product on dispensary menu |
image_url | string | Product image URL |
raw_product_name | string | Original product name from menu provider |
product_name | string | Normalized product name |
display_weight | string | Product size/weight |
category | string | Normalized category |
subcategory | string | Normalized subcategory (if applicable) |
product_tags | array | Applied product tags |
percentage_thc | number | THC percentage (for flower, concentrates) |
percentage_cbd | number | CBD percentage |
mg_thc | number | THC milligrams (for edibles, tinctures) |
mg_cbd | number | CBD milligrams |
latest_price | number | Current retail price |
original_price | number | Original price before discount (if on sale) |
menu_provider | string | Source platform (Dutchie, Weedmaps, Leafly, IHeartJane) |
is_below_threshold | boolean | true if inventory is low and the product may be out of stock soon. Use include_below_threshold=false to hide these. |
is_stock_image | boolean | true if the product image is a stock/placeholder photo. V1 only — not included in V2 responses. |
terpene_profile | object | Terpene percentages (e.g., {"Myrcene": 0.0089}). Present on flower and some concentrates. null when not available. When the specific listing lacks terpene data but another listing of the same product has it, the profile is populated from that source (see terpene_source). |
terpene_source | string | Source of terpene data: exact = from this specific product listing, uuid_match = from another listing of the same product (same UUID). null if no terpene data available. |
terpene_source_product_id | number | When terpene_source is uuid_match, the product ID the terpene data was sourced from. Useful for tracing data provenance. |
recreational | boolean | Available for recreational purchase |
medical | boolean | Available for medical purchase |
Best Practices
Use Location Filters for Performance
Queries with lat/lng are faster and return more relevant results:
# Faster - location-scoped
?states=California&lat=34.0522&lng=-118.2437&distance=10
# Slower - state-wide scan
?states=California
Use IDs Instead of Text Search
Use brands and retailers with numeric IDs instead of brand_name for significantly faster queries:
# Faster - ID-based
?states=California&brands=789
# Slower - text search
?states=California&brand_name=Stiiizy
Paginate Through All Results
Don't assume all results fit on one page. Always check pagination.next_page:
all_products = []
page = 1
while True:
response = requests.get(
f"{API_URL}/products",
headers=headers,
params={"states": "California", "category": "Flower", "page": page}
)
data = response.json()
all_products.extend(data["data"])
if data["pagination"]["next_page"] is None:
break
page += 1
Cache Retailer IDs
If you frequently query products from the same dispensaries, cache their retailer IDs and use the retailers parameter for faster lookups.
Use per_page=200 for Bulk Pulls
The default is 20 results per page. Set per_page=200 to reduce total API calls by 10x:
?states=Colorado&per_page=200&page=1
Always Filter Medical vs Recreational
For states with adult-use/recreational markets (CA, CO, IL, MI, MA, NV, AZ, NJ, etc.), always set recreational=true to avoid double-counting from shared med/rec inventory systems. For medical-only states (FL, PA, OK), use medical=true.
# Recreational state
?states=California&recreational=true
# Medical-only state
?states=Florida&medical=true
V1 Legacy Format
V1 is maintained for backward compatibility but is no longer recommended for new integrations. Use V2 (/v2/products) instead.
V1 uses repeated query parameters instead of comma-separated strings:
GET https://api.cannmenus.com/v1/products
| V2 (Recommended) | V1 (Legacy) |
|---|---|
states=California,Colorado | states=California&states=Colorado |
brands=123,456 | brands=123&brands=456 |
retailers=100,200 | retailers=100&retailers=200 |
tags=Indica,Live | tags=Indica&tags=Live |
category=Flower,Edible | category=Flower (single value only) |
V1 returns the same data, response format, and supports the same features (per_page, terpene profiles, all filters). The only difference is the parameter format.
Meta SKU Endpoint
The meta endpoint groups products by Meta SKU (a UUID that identifies the same product across different retailers) instead of by retailer SKU. This is useful for cross-retailer product matching and deduplication.
GET https://api.cannmenus.com/v2/products/meta
The meta endpoint uses the same V1-style repeated query parameters (not comma-separated) even on the V2 path. states is required.
Parameters
All parameters from the main products endpoint are supported, plus:
| Parameter | Type | Description |
|---|---|---|
uuids | string[] | Filter by specific Meta SKU UUIDs (repeated param) |
Response
The response uses the same pagination format but groups by meta_sku instead of sku:
{
"data": [
{
"retailer_id": "10600",
"meta_sku": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"products": [
{
"display_product_name": "Blue Dream 3.5g",
"brand_name": "Cookies",
"category": "Flower",
"latest_price": 45.00
}
]
}
],
"pagination": {
"total_records": 523,
"current_page": 1,
"total_pages": 27,
"next_page": 2,
"prev_page": null
}
}
Example Request
curl "https://api.cannmenus.com/v2/products/meta?states=California&category=Flower&page=1" \
-H "X-Token: YOUR_API_KEY"
The V1 meta endpoint is also available at /v1/products/meta with the same functionality.
