# CannaiQ Query API Query raw crawl payload data with flexible filters, sorting, and aggregation. ## Base URL ``` https://cannaiq.co/api/payloads ``` ## Authentication Include your API key in the header: ``` X-API-Key: your-api-key ``` --- ## Endpoints ### 1. Query Products Filter and search products from a store's latest crawl data. ``` GET /api/payloads/store/{dispensaryId}/query ``` #### Query Parameters | Parameter | Type | Description | |-----------|------|-------------| | `brand` | string | Filter by brand name (partial match) | | `category` | string | Filter by category (flower, vape, edible, etc.) | | `subcategory` | string | Filter by subcategory | | `strain_type` | string | Filter by strain (indica, sativa, hybrid, cbd) | | `in_stock` | boolean | Filter by stock status (true/false) | | `price_min` | number | Minimum price | | `price_max` | number | Maximum price | | `thc_min` | number | Minimum THC percentage | | `thc_max` | number | Maximum THC percentage | | `search` | string | Search product name (partial match) | | `fields` | string | Comma-separated fields to return | | `limit` | number | Max results (default 100, max 1000) | | `offset` | number | Skip results for pagination | | `sort` | string | Sort by: name, price, thc, brand | | `order` | string | Sort order: asc, desc | #### Available Fields When using `fields` parameter, you can request: - `id` - Product ID - `name` - Product name - `brand` - Brand name - `category` - Product category - `subcategory` - Product subcategory - `strain_type` - Indica/Sativa/Hybrid/CBD - `price` - Current price - `price_med` - Medical price - `price_rec` - Recreational price - `thc` - THC percentage - `cbd` - CBD percentage - `weight` - Product weight/size - `status` - Stock status - `in_stock` - Boolean in-stock flag - `image_url` - Product image - `description` - Product description #### Examples **Get all flower products under $40:** ``` GET /api/payloads/store/112/query?category=flower&price_max=40 ``` **Search for "Blue Dream" with high THC:** ``` GET /api/payloads/store/112/query?search=blue+dream&thc_min=20 ``` **Get only name and price for Alien Labs products:** ``` GET /api/payloads/store/112/query?brand=Alien+Labs&fields=name,price,thc ``` **Get top 10 highest THC products:** ``` GET /api/payloads/store/112/query?sort=thc&order=desc&limit=10 ``` **Paginate through in-stock products:** ``` GET /api/payloads/store/112/query?in_stock=true&limit=50&offset=0 GET /api/payloads/store/112/query?in_stock=true&limit=50&offset=50 ``` #### Response ```json { "success": true, "dispensaryId": 112, "payloadId": 45, "fetchedAt": "2025-12-11T10:30:00Z", "query": { "filters": { "brand": "Alien Labs", "category": null, "price_max": null }, "sort": "price", "order": "asc", "limit": 100, "offset": 0 }, "pagination": { "total": 15, "returned": 15, "limit": 100, "offset": 0, "has_more": false }, "products": [ { "id": "507f1f77bcf86cd799439011", "name": "Alien Labs - Baklava 3.5g", "brand": "Alien Labs", "category": "flower", "strain_type": "hybrid", "price": 55, "thc": "28.5", "in_stock": true } ] } ``` --- ### 2. Aggregate Data Group products and calculate metrics. ``` GET /api/payloads/store/{dispensaryId}/aggregate ``` #### Query Parameters | Parameter | Type | Description | |-----------|------|-------------| | `group_by` | string | **Required.** Field to group by: brand, category, subcategory, strain_type | | `metrics` | string | Comma-separated metrics (default: count) | #### Available Metrics - `count` - Number of products - `avg_price` - Average price - `min_price` - Lowest price - `max_price` - Highest price - `avg_thc` - Average THC percentage - `in_stock_count` - Number of in-stock products #### Examples **Count products by brand:** ``` GET /api/payloads/store/112/aggregate?group_by=brand ``` **Get price stats by category:** ``` GET /api/payloads/store/112/aggregate?group_by=category&metrics=count,avg_price,min_price,max_price ``` **Get THC averages by strain type:** ``` GET /api/payloads/store/112/aggregate?group_by=strain_type&metrics=count,avg_thc ``` **Brand analysis with stock info:** ``` GET /api/payloads/store/112/aggregate?group_by=brand&metrics=count,avg_price,in_stock_count ``` #### Response ```json { "success": true, "dispensaryId": 112, "payloadId": 45, "fetchedAt": "2025-12-11T10:30:00Z", "groupBy": "brand", "metrics": ["count", "avg_price"], "totalProducts": 450, "groupCount": 85, "aggregations": [ { "brand": "Alien Labs", "count": 15, "avg_price": 52.33 }, { "brand": "Connected", "count": 12, "avg_price": 48.50 } ] } ``` --- ### 3. Compare Stores (Price Comparison) Query the same data from multiple stores and compare in your app: ```javascript // Get flower prices from Store A const storeA = await fetch('/api/payloads/store/112/query?category=flower&fields=name,brand,price'); // Get flower prices from Store B const storeB = await fetch('/api/payloads/store/115/query?category=flower&fields=name,brand,price'); // Compare in your app const dataA = await storeA.json(); const dataB = await storeB.json(); // Find matching products and compare prices ``` --- ### 4. Price History For historical price data, use the snapshots endpoint: ``` GET /api/v1/products/{productId}/history?days=30 ``` Or compare payloads over time: ``` GET /api/payloads/store/{dispensaryId}/diff?from={payloadId1}&to={payloadId2} ``` The diff endpoint shows: - Products added - Products removed - Price changes - Stock changes --- ### 5. List Stores Get available dispensaries to query: ``` GET /api/stores ``` Returns all stores with their IDs, names, and locations. --- ## Use Cases ### Price Comparison App ```javascript // 1. Get stores in Arizona const stores = await fetch('/api/stores?state=AZ').then(r => r.json()); // 2. Query flower prices from each store const prices = await Promise.all( stores.map(store => fetch(`/api/payloads/store/${store.id}/query?category=flower&fields=name,brand,price`) .then(r => r.json()) ) ); // 3. Build comparison matrix in your app ``` ### Brand Analytics Dashboard ```javascript // Get brand presence across stores const brandData = await Promise.all( storeIds.map(id => fetch(`/api/payloads/store/${id}/aggregate?group_by=brand&metrics=count,avg_price`) .then(r => r.json()) ) ); // Aggregate brand presence across all stores ``` ### Deal Finder ```javascript // Find high-THC flower under $30 const deals = await fetch( '/api/payloads/store/112/query?category=flower&price_max=30&thc_min=20&in_stock=true&sort=thc&order=desc' ).then(r => r.json()); ``` ### Inventory Tracker ```javascript // Get products that went out of stock const diff = await fetch('/api/payloads/store/112/diff').then(r => r.json()); const outOfStock = diff.details.stockChanges.filter( p => p.newStatus !== 'Active' ); ``` --- ## Rate Limits - Default: 100 requests/minute per API key - Contact support for higher limits ## Error Responses ```json { "success": false, "error": "Error message here" } ``` Common errors: - `404` - Store or payload not found - `400` - Missing required parameter - `401` - Invalid or missing API key - `429` - Rate limit exceeded