Understanding SKUs
Products in the CannMenus API are identified by SKU (Stock Keeping Unit). Each response entry pairs a retailer and a SKU with a product listing, so you can compare prices and availability for the same product across menu platforms and, with meta SKUs, across dispensaries.
Data Structure
Each entry in data pairs a retailer and a SKU with one product listing:
data[]
├── Entry: retailer_id + sku
│ └── products: [ listing from one menu provider ]
├── Entry: same retailer_id + sku (only with include_all_menu_providers=true)
│ └── products: [ listing from another menu provider ]
└── Entry: another retailer_id + sku
└── products: [ listing ]
| Field | Description |
|---|---|
retailer_id | A unique dispensary |
sku | A unique product variant at that retailer |
products | The listing for that entry, from one menu provider. Entries that share retailer_id and sku are the same product from different providers. |
What is a SKU?
A SKU uniquely identifies a specific product variant within a dispensary. For example, if a dispensary sells "Blue Dream" flower in 1g, 3.5g, and 7g sizes, each size is a separate SKU.
Same Product, Different Platforms
Many dispensaries list products on multiple platforms (Dutchie, Leafly, Weedmaps, etc.). By default the API returns only the best menu source for each retailer, so a (retailer_id, sku) pair usually appears once. With include_all_menu_providers=true, each provider's listing comes back as its own entry, and entries for the same product share retailer_id and sku. Each entry's products array holds one listing:
[
{
"retailer_id": "10600",
"sku": "07A0Sbb8OieeaR8rIW6MVHRw",
"products": [
{ "cann_sku_id": "07A0Sbb8OieeaR8rIW6MVHRw", "product_name": "Blue Dream", "display_weight": "3.5g", "latest_price": 45.00, "menu_provider": "Dutchie" }
]
},
{
"retailer_id": "10600",
"sku": "07A0Sbb8OieeaR8rIW6MVHRw",
"products": [
{ "cann_sku_id": "07A0Sbb8OieeaR8rIW6MVHRw", "product_name": "Blue Dream", "display_weight": "3.5g", "latest_price": 48.00, "menu_provider": "Leafly" }
]
}
]
Group entries by (retailer_id, sku) to compare pricing across platforms for the exact same product at the exact same retailer.
Working Across Providers
1. Price Comparison
Compare the same product across menu providers by grouping entries on (retailer_id, sku):
from collections import defaultdict
listings = defaultdict(list)
for entry in response["data"]:
listings[(entry["retailer_id"], entry["sku"])].extend(entry["products"])
for (retailer_id, sku), group in listings.items():
prices = [p["latest_price"] for p in group if p["latest_price"] is not None]
if prices:
print(f"{group[0]['product_name']} at retailer {retailer_id}: ${min(prices)} - ${max(prices)}")
2. One Source by Default
By default you get the best menu source for each retailer, so the same product doesn't appear once per platform. Ask for every provider's listing with include_all_menu_providers=true and group by (retailer_id, sku) as shown above.
3. Accurate Inventory View
The products array holds the listings we found for that product at that retailer. By default, only the best menu source per retailer is returned; set include_all_menu_providers=true on the Products endpoint to get listings from each menu provider we track.
4. Consistent Identification
Even when platforms use different naming (e.g., "Blue Dream 3.5g" vs "Blue Dream - Flower - 1/8 oz"), the SKU ensures you're looking at the same product.
Full Response Example
{
"data": [
{
"retailer_id": "10600",
"sku": "07A0Sbb8OieeaR8rIW6MVHRw",
"products": [
{
"cann_sku_id": "07A0Sbb8OieeaR8rIW6MVHRw",
"brand_name": "93 Boyz",
"brand_id": 25407,
"raw_product_name": "Aeriz - 93 Boyz Kept Secret 3.5g",
"product_name": "Aeriz Kept Secret",
"raw_weight_string": "3.5g",
"display_weight": "3.5g",
"raw_product_category": "Flower",
"category": "Flower",
"raw_subcategory": "Flower",
"subcategory": null,
"product_tags": null,
"percentage_thc": null,
"percentage_cbd": null,
"cbd_thc_ratio": null,
"mg_thc": null,
"mg_cbd": null,
"quantity_per_package": 1,
"medical": false,
"recreational": true,
"latest_price": 59.04,
"menu_provider": "Leafly"
}
]
},
{
"retailer_id": "14583",
"sku": "07sDygynAUjkxftzxSnk8Cq8",
"products": [
{
"cann_sku_id": "07sDygynAUjkxftzxSnk8Cq8",
"brand_name": "RYTHM",
"brand_id": 5163,
"raw_product_name": "Rythm Jet Fuel OG 3.5g | Flower",
"product_name": "Jet Fuel Og",
"raw_weight_string": "3.5g",
"display_weight": "3.5g",
"raw_product_category": "Flower",
"category": "Flower",
"raw_subcategory": "small-buds",
"subcategory": "Popcorn",
"product_tags": null,
"percentage_thc": null,
"percentage_cbd": null,
"cbd_thc_ratio": null,
"mg_thc": null,
"mg_cbd": null,
"quantity_per_package": null,
"medical": false,
"recreational": true,
"latest_price": 55.0,
"menu_provider": "Dutchie"
}
]
}
],
"pagination": {
"total_records": 2,
"current_page": 1,
"total_pages": 1,
"next_page": null,
"prev_page": null
}
}
Working with SKUs
Flatten Products for Analysis
# Convert nested structure to flat list
all_products = []
for sku_group in response["data"]:
for product in sku_group["products"]:
product["retailer_id"] = sku_group["retailer_id"]
product["sku"] = sku_group["sku"]
all_products.append(product)
# Now analyze as flat DataFrame
import pandas as pd
df = pd.DataFrame(all_products)
Find Best Price per SKU
from collections import defaultdict
listings = defaultdict(list)
for entry in response["data"]:
listings[(entry["retailer_id"], entry["sku"])].extend(entry["products"])
for key, group in listings.items():
priced = [p for p in group if p["latest_price"] is not None]
if priced:
best = min(priced, key=lambda p: p["latest_price"])
print(f"{best['product_name']}: ${best['latest_price']} on {best['menu_provider']}")
Matching Products Across Dispensaries
A sku is scoped to one dispensary: the same product at two dispensaries has two different SKUs.
To compare the same product across dispensaries, use the Meta SKU endpoint (/v2/products/meta). It returns each listing tagged with its meta_sku, sorted by meta_sku. Listings that share a meta_sku are the same product at different dispensaries; on /v2/products/meta they can span more than one page. You can also narrow results with brands, product_name, and display_weight.
This means:
- Use
skuto compare listings within a single retailer - Use
meta_skuto compare the same product across retailers
