Product Search Guide
This guide walks through common workflows for finding cannabis products using the CannMenus API, with complete code examples.
Prerequisites
Install the requests library for Python examples:
pip install requests
Set up your API credentials:
import requests
API_URL = "https://api.cannmenus.com/v1"
headers = {"X-Token": "YOUR_API_TOKEN"}
Basic Product Search
Search for products in a state:
response = requests.get(
f"{API_URL}/products",
headers=headers,
params={
"states": "California",
"category": "Flower",
"page": 1
}
)
data = response.json()
print(f"Found {data['pagination']['total_records']} products")
for sku_group in data["data"][:5]:
product = sku_group["products"][0]
print(f"- {product['brand_name']} {product['product_name']}: ${product['latest_price']}")
Find a Retailer and Get All Products
A common workflow: find a specific dispensary, then fetch all its products.
Step 1: Search for the Retailer
# Find a retailer by name in Illinois
response = requests.get(
f"{API_URL}/retailers",
headers=headers,
params={
"state": "Illinois",
"name": "rise",
"is_active": True,
"page": 1
}
)
retailers = response.json()["data"]
if not retailers:
print("No retailers found")
exit()
retailer = retailers[0]
print(f"Found: {retailer['dispensary_name']}")
print(f"Address: {retailer['address']}, {retailer['city']}")
print(f"Retailer ID: {retailer['id']}")
Step 2: Fetch All Products with Pagination
def get_all_products(retailer_id, state):
"""Fetch all products from a retailer, handling pagination."""
all_products = []
page = 1
while True:
response = requests.get(
f"{API_URL}/products",
headers=headers,
params={
"states": state,
"retailers": retailer_id,
"page": page
}
)
data = response.json()
all_products.extend(data["data"])
print(f"Page {page}/{data['pagination']['total_pages']}: fetched {len(data['data'])} SKUs")
if data["pagination"]["next_page"] is None:
break
page += 1
return all_products
# Get all products for the retailer
products = get_all_products(retailer["id"], "Illinois")
print(f"\nTotal SKUs: {len(products)}")
Step 3: Analyze the Products
# Count products by category
from collections import Counter
categories = Counter()
for sku_group in products:
for product in sku_group["products"]:
categories[product["category"]] += 1
print("\nProducts by category:")
for category, count in categories.most_common():
print(f" {category}: {count}")
Complete Example: Retailer Product Export
Here's a complete script that finds a retailer and exports all products:
import requests
import json
from datetime import datetime
API_URL = "https://api.cannmenus.com/v1"
headers = {"X-Token": "YOUR_API_TOKEN"}
def find_retailer(state, name):
"""Search for a retailer by state and name."""
response = requests.get(
f"{API_URL}/retailers",
headers=headers,
params={"state": state, "name": name, "is_active": True, "page": 1}
)
response.raise_for_status()
retailers = response.json()["data"]
if not retailers:
raise ValueError(f"No retailer found matching '{name}' in {state}")
return retailers[0]
def get_all_products(retailer_id, state):
"""Fetch all products from a retailer with pagination."""
all_products = []
page = 1
while True:
response = requests.get(
f"{API_URL}/products",
headers=headers,
params={"states": state, "retailers": retailer_id, "page": page}
)
response.raise_for_status()
data = response.json()
all_products.extend(data["data"])
if data["pagination"]["next_page"] is None:
break
page += 1
return all_products
def flatten_products(sku_groups):
"""Convert nested SKU groups to flat product list."""
products = []
for group in sku_groups:
for product in group["products"]:
products.append({
"retailer_id": group["retailer_id"],
"sku": group["sku"],
**product
})
return products
def main():
# Find the retailer
retailer = find_retailer("Illinois", "rise")
print(f"Found: {retailer['dispensary_name']}")
print(f"Location: {retailer['city']}, {retailer['state']}")
# Get all products
print("\nFetching products...")
sku_groups = get_all_products(retailer["id"], "Illinois")
products = flatten_products(sku_groups)
print(f"Total products: {len(products)}")
# Export to JSON
output = {
"retailer": retailer,
"exported_at": datetime.now().isoformat(),
"product_count": len(products),
"products": products
}
filename = f"{retailer['dispensary_name'].replace(' ', '_')}_products.json"
with open(filename, "w") as f:
json.dump(output, f, indent=2)
print(f"\nExported to {filename}")
if __name__ == "__main__":
main()
Location-Based Search
Find products near a specific location:
# Search for edibles within 5 miles of downtown Denver
response = requests.get(
f"{API_URL}/products",
headers=headers,
params={
"states": "Colorado",
"category": "Edible",
"lat": 39.7392,
"lng": -104.9903,
"distance": 5,
"page": 1
}
)
data = response.json()
print(f"Found {data['pagination']['total_records']} edibles near Denver")
Price Comparison Across Retailers
Compare prices for a specific product:
def compare_prices(state, brand, product_name):
"""Find price ranges for a product across retailers."""
response = requests.get(
f"{API_URL}/products",
headers=headers,
params={
"states": state,
"brand": brand,
"name": product_name,
"page": 1
}
)
data = response.json()
prices = []
for sku_group in data["data"]:
for product in sku_group["products"]:
prices.append({
"retailer_id": sku_group["retailer_id"],
"price": product["latest_price"],
"menu_provider": product["menu_provider"]
})
if prices:
prices.sort(key=lambda x: x["price"])
print(f"\n{brand} {product_name} prices in {state}:")
print(f" Lowest: ${prices[0]['price']}")
print(f" Highest: ${prices[-1]['price']}")
print(f" Found at {len(prices)} locations")
return prices
compare_prices("California", "Stiiizy", "OG Kush")
Filter High-Potency Products
Find products above a THC threshold:
# High-THC flower (25%+ THC)
response = requests.get(
f"{API_URL}/products",
headers=headers,
params={
"states": "Washington",
"category": "Flower",
"min_percentage_thc": 25,
"page": 1
}
)
# High-dose edibles (100mg+ THC)
response = requests.get(
f"{API_URL}/products",
headers=headers,
params={
"states": "Colorado",
"category": "Edible",
"min_mg_thc": 100,
"page": 1
}
)
Error Handling
Always handle potential errors:
def safe_api_call(url, params):
"""Make an API call with proper error handling."""
try:
response = requests.get(url, headers=headers, params=params)
if response.status_code == 200:
return response.json()
error = response.json()
if response.status_code == 401:
raise PermissionError(f"Authentication failed: {error['message']}")
elif response.status_code == 429:
raise RuntimeError(f"Rate limited: {error['message']}")
elif response.status_code == 400:
raise ValueError(f"Bad request: {error['message']}")
else:
raise Exception(f"API error: {error['message']}")
except requests.exceptions.RequestException as e:
raise ConnectionError(f"Network error: {e}")
# Usage
try:
data = safe_api_call(f"{API_URL}/products", {"states": "California", "page": 1})
print(f"Found {data['pagination']['total_records']} products")
except Exception as e:
print(f"Error: {e}")
Next Steps
- See Products endpoint for all available parameters
- Learn about SKU organization for understanding response structure
- Check Categories and Tags for filtering options
- Review Pagination for handling large result sets
