Overview
The Shopping Agent API provides two endpoints: one to initiate a real-time menu scan via a Trigger.dev task, and one to retrieve a cached scan result. The scan is an asynchronous operation — the mobile app connects to it via WebSocket usinguseRealtimeTaskTrigger and receives progress events as each pipeline stage completes.
Endpoints
| Endpoint | Method | Description |
|---|---|---|
/api/v1/shopping/scan-token | POST | Create a Trigger.dev token and initiate a menu scan |
/api/v1/shopping/scan/:domain/cached | GET | Retrieve a cached scan result for a dispensary domain |
POST /api/v1/shopping/scan-token
Creates a public Trigger.dev access token and triggers theshopping-menu-scan task. If a valid cached scan already exists for the requested dispensary, the token is returned alongside the cached result and no new task is started. If a scan for the same dispensary is already running (de-duplication check), the token for that active run is returned instead.
Request
Request Body Schema
Responses
200 OK — Cache Hit A valid scan was found in cache. No new task is triggered.GET /api/v1/shopping/scan/:domain/cached
Retrieves the most recent cached scan result for a dispensary domain. Returnsnull if no valid cache entry exists or if the cache has expired (4-hour TTL).
Request
Path Parameters
| Parameter | Type | Description |
|---|---|---|
domain | string | The root domain of the dispensary (e.g., greenthumpdispensary.com) |
Response
200 OK — Cache HitData Schemas
Product
A matched menu product with strain data and personalization tags.Recommendation
An AI-generated top recommendation from the current menu.Discovery
An unmatched product flagged for potential database addition.WebSocket / Real-Time Progress
The mobile app does not poll for scan results. Instead, it subscribes to the Trigger.dev run viauseRealtimeTaskTrigger from @trigger.dev/react-hooks, using the publicAccessToken returned by /scan-token.
Progress Event Shape
Trigger.dev emits run metadata updates as the task progresses. The Shopping Agent task emits structured metadata at each stage:React Native Integration
The shopping-menu-scan Trigger.dev Task
The Trigger.dev task runs in the @tiwih/trigger package and executes six stages in sequence.
Task ID
Trigger.dev Dashboard
View task in dashboardStage Details
| Stage | Progress Range | Description |
|---|---|---|
cache-check | 0–5% | Queries menu_scans in Supabase for an unexpired entry matching website_domain |
scrape | 5–40% | Tool-based extraction with cascading fallback: Extract (wildcard) → Scrape+JSON → Scrape+AI. Supports multi-page menus via wildcard URL patterns. Falls back to sitemap discovery if initial extraction returns 0 products. |
match | 40–60% | Three-tier strain matching: exact → slug → trigram (pg_trgm similarity > 0.4) |
personalize | 60–85% | Claude Sonnet generates ranked recommendations using user context and matched strain data |
cache-save | 85–95% | Upserts results into menu_scans with a 4-hour TTL |
complete | 100% | Returns the full payload as task output |
Input Schema
Output Schema
Strain Matching Algorithm
The match stage runs three passes in sequence, stopping as soon as a match is found for each product.Pass 1: Exact Match
high
Pass 2: Slug Match
high
Pass 3: Trigram Match
medium if similarity > 0.6, low if 0.4–0.6
Products with no match across all three passes are placed in the discoveries array.
Database
Scan results are stored in themenu_scans table in Supabase.
Table: menu_scans
| Column | Type | Description |
|---|---|---|
id | uuid | Primary key |
website_domain | text | Dispensary root domain |
categories_hash | text | Hash of extracted categories (used for deduplication) |
scanned_at | timestamptz | When the scan was performed |
expires_at | timestamptz | Cache expiry (scanned_at + 4 hours) |
total_products | int | Total extracted products |
matched_count | int | Products matched to a strain |
unmatched_count | int | Products with no strain match |
products_json | jsonb | Full product array |
recommendations_json | jsonb | AI recommendations array |
discoveries_json | jsonb | Unmatched discoveries array |
UNIQUE (website_domain, categories_hash) — prevents duplicate scans for the same dispensary and menu composition.
Error Codes
| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR | 400 | Missing or invalid request fields |
UNAUTHORIZED | 401 | Missing or invalid auth token |
MENU_NOT_ACCESSIBLE | 422 | The menu URL could not be reached or scraped |
SCAN_TIMEOUT | 408 | The Firecrawl agent did not complete within the time limit |
TRIGGER_UNAVAILABLE | 503 | Trigger.dev task could not be initiated |
