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"}

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()

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