{"openapi":"3.1.0","info":{"title":"MainMarket API","description":"MainMarket B2B API — grocery shelf intelligence over a unified, canonical product catalog.\n\n**Surface area**\n- `Stores` — locate and inspect retail store records (geo, hours,   enrichment).\n- `Chains` — chain-level metadata + online-shopping config.\n- `Products` — canonical catalog search and hydrate.\n- `Prices` — store-level shelf prices with tiered fallback   (specific → regional → chain).\n- `Coupons` — active retailer coupons with savings math and geo   filtering.\n- `Aisles` — frequency-ranked aisle lists per store/chain.\n- `Index` — published price-index time series + methodology.\n- `Health` — liveness + DB reachability probe.\n- `Internal` — Cart-app-only flows (Kroger OAuth, cart sync).\n\n**Auth**: every route requires `Authorization: Bearer <token>`. Two tiers — internal `mm_internal_<token>` (Cart app + internal services) and B2B `<api_key>` bcrypt-validated against `clients.api_key_hash`. The MPP / x402 payment middleware can short-circuit unpaid hits with a 402 before auth runs.\n\n**Envelope**: every response is wrapped in `{data, meta, error}`. `meta.request_id` is the X-Request-ID header value echoed back for retry correlation.\n\n**Agent discovery**: `/skill.md`, `/llms.txt`, `/prompts.json`, `/.well-known/x402`, `/.well-known/mpp`, and `/methodology.md` are free static reads.","version":"1.0.0"},"paths":{"/v1/stores":{"get":{"tags":["Stores"],"summary":"Geo-search stores by lat/lng, chain, state, or metro","description":"List retail stores filtered by geography (lat/lng + radius), chain slug, state, and/or metro. Results are ordered by distance when geo filters are present; otherwise alphabetically by name.\n\nOpt into nested expansions via `?include=` (comma-separated): `chain_online_config` adds the chain's online-shopping config; `hours` adds weekly hours; `places_enrichment` adds Google-Maps-derived rating, popular_times, highlights, and GPT-generated shopper sentiment.\n\nReturns `count` only when `include_count=true` (extra SQL pass — off by default for latency). Responses are wrapped in the standard `{data, meta, error}` envelope. Cache-Control is `public, max-age=300, stale-while-revalidate=600`.","operationId":"list_stores_v1_stores_get","parameters":[{"name":"lat","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":90,"minimum":-90},{"type":"null"}],"title":"Lat"}},{"name":"lng","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":180,"minimum":-180},{"type":"null"}],"title":"Lng"}},{"name":"radius","in":"query","required":false,"schema":{"type":"number","maximum":50,"minimum":0.1,"description":"Radius in miles","default":5,"title":"Radius"},"description":"Radius in miles"},{"name":"chain","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Chain slug (e.g. 'aldi', 'lidl')","title":"Chain"},"description":"Chain slug (e.g. 'aldi', 'lidl')"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":2},{"type":"null"}],"title":"State"}},{"name":"metro","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Metro"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"include","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated optional expansions. Supported: chain_online_config (adds nested `chain` object), hours (adds 7-row nested `hours` array per store — day_of_week, day_name, open_time HH:MM, close_time, is_closed), places_enrichment (adds nested `places` object — Google rating, popular_times histogram, highlights, GPT-generated shopper sentiment).","title":"Include"},"description":"Comma-separated optional expansions. Supported: chain_online_config (adds nested `chain` object), hours (adds 7-row nested `hours` array per store — day_of_week, day_name, open_time HH:MM, close_time, is_closed), places_enrichment (adds nested `places` object — Google rating, popular_times histogram, highlights, GPT-generated shopper sentiment)."},{"name":"include_count","in":"query","required":false,"schema":{"type":"boolean","description":"When true, runs a second SQL pass to populate `count` with the total matching rows (pre-LIMIT). Off by default — mobile Stores screen doesn't render it, and the count query can't use the gist index so it adds ~50% to per-request latency.","default":false,"title":"Include Count"},"description":"When true, runs a second SQL pass to populate `count` with the total matching rows (pre-LIMIT). Off by default — mobile Stores screen doesn't render it, and the count query can't use the gist index so it adds ~50% to per-request latency."},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Matching stores","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_StoreListResponse_"},"example":{"data":{"stores":[{"id":"a8c1...","name":"Wegmans Brooklyn","chain_name":"Wegmans","chain_slug":"wegmans","address":"21 Flushing Ave","city":"Brooklyn","state":"NY","zip":"11205","lat":40.6989,"lng":-73.9778,"is_active":true}]},"meta":{"request_id":"abc123"}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/stores/{store_id}":{"get":{"tags":["Stores"],"summary":"Get a single store by UUID","description":"Fetch one store by UUID. Soft-deleted duplicate ids transparently resolve to the surviving canonical record (post-2026-04-25 dedup pass) — clients holding pre-dedup ids should compare the response `id` to the requested id and migrate their cache when they differ.\n\nSupports the same `?include=` expansions as `GET /v1/stores`: `chain_online_config`, `hours`, `places_enrichment`.\n\nReturns 404 when no live row matches the id.","operationId":"get_store_v1_stores__store_id__get","parameters":[{"name":"store_id","in":"path","required":true,"schema":{"type":"string","title":"Store Id"}},{"name":"include","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated optional expansions. Supported: chain_online_config, hours, places_enrichment.","title":"Include"},"description":"Comma-separated optional expansions. Supported: chain_online_config, hours, places_enrichment."},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Store found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_StoreResponse_"},"example":{"data":{"id":"a8c1...","name":"Wegmans Brooklyn","chain_slug":"wegmans","city":"Brooklyn","state":"NY","is_active":true},"meta":{"request_id":"abc123"}}}}},"404":{"description":"Store not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/prices":{"get":{"tags":["Prices"],"summary":"Search store-level shelf prices with tiered fallback","description":"Returns store-scoped prices for products. Requires at least ONE product-side filter (`q`, `barcode`, `product_id`, `product_ids`) AND at least one store-side filter (`store_id`, `store_ids`, `chain`, `metro`, `state`, `near`) to prevent unbounded scans — the API responds 422 when neither side is set.\n\n**Tiered source resolution** (when a store filter is supplied):\n- `specific` — the requested store has its own scrape\n- `regional` — nearest same-chain sibling within 50mi supplies   prices\n- `chain` — same-chain sibling beyond 50mi (or non-geocoded)\n\nEach row carries provenance (`source_store_id`, `source_store_name`, `source_tier`, `source_distance_mi`) plus `pricing_scope` to indicate whether the chain prices per-store, chain-wide, or not online. When `?near=lat,lng` is supplied, results sort by distance ascending and the row carries a `distance_mi` field.\n\nCache-Control is `public, max-age=30, stale-while-revalidate=120`.","operationId":"list_prices_v1_prices_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":100},{"type":"null"}],"description":"Text search on product name","title":"Q"},"description":"Text search on product name"},{"name":"barcode","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":6,"maxLength":32},{"type":"null"}],"description":"UPC/EAN lookup","title":"Barcode"},"description":"UPC/EAN lookup"},{"name":"product_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single product UUID","title":"Product Id"},"description":"Single product UUID"},{"name":"product_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated product UUIDs","title":"Product Ids"},"description":"Comma-separated product UUIDs"},{"name":"store_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single store UUID","title":"Store Id"},"description":"Single store UUID"},{"name":"store_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated store UUIDs","title":"Store Ids"},"description":"Comma-separated store UUIDs"},{"name":"chain","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Chain slug, e.g. 'wegmans'","title":"Chain"},"description":"Chain slug, e.g. 'wegmans'"},{"name":"metro","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Metro area, e.g. 'nyc'","title":"Metro"},"description":"Metro area, e.g. 'nyc'"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":2},{"type":"null"}],"description":"Two-letter state code","title":"State"},"description":"Two-letter state code"},{"name":"near","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"lat,lng pair, e.g. '40.6892,-73.9942'. Filters source stores within radius_mi and sorts results by distance.","title":"Near"},"description":"lat,lng pair, e.g. '40.6892,-73.9942'. Filters source stores within radius_mi and sorts results by distance."},{"name":"radius_mi","in":"query","required":false,"schema":{"type":"number","maximum":50.0,"minimum":0.1,"description":"Radius in miles for near=. Ignored when near is not set.","default":10.0,"title":"Radius Mi"},"description":"Radius in miles for near=. Ignored when near is not set."},{"name":"on_sale","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"On Sale"}},{"name":"normalize_aisles","in":"query","required":false,"schema":{"type":"boolean","description":"When true, returns the aisle field with the same normalization applied by /v1/stores/{id}/aisles (strip 'Aisle ' prefix, strip ', Shelf N' suffix, drop PoS-noise, re-prefix pure aisle codes as 'Aisle N'). Lets mobile clients join price rows to the aisle pick list without separate client-side normalization. Default off — preserves raw aisle strings for B2B clients.","default":false,"title":"Normalize Aisles"},"description":"When true, returns the aisle field with the same normalization applied by /v1/stores/{id}/aisles (strip 'Aisle ' prefix, strip ', Shelf N' suffix, drop PoS-noise, re-prefix pure aisle codes as 'Aisle N'). Lets mobile clients join price rows to the aisle pick list without separate client-side normalization. Default off — preserves raw aisle strings for B2B clients."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Matching prices (may be empty)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_PricesResponse_"},"example":{"data":{"count":1,"results":[{"product_id":"9b0c...","name":"Wegmans Organic Whole Milk","brand":"Wegmans","upc":"077890123456","store_id":"a8c1...","store_name":"Wegmans Brooklyn","chain_slug":"wegmans","price":5.99,"regular_price":5.99,"is_on_sale":false,"aisle":"Aisle 7","available":true,"source_tier":"specific","source_distance_mi":0.0,"pricing_scope":"per_store"}]},"meta":{"request_id":"abc123"}}}}},"422":{"description":"Missing required product- or store-side filter"}}}},"/v1/products":{"get":{"tags":["Products"],"summary":"Search the canonical product catalog (no store/price context)","description":"Catalog-only search — no store or shelf-price context. Requires at least one filter (`q`, `barcode`, `product_id`, `ids`, `brand`, or `category`) to prevent unbounded scans (422 otherwise).\n\n**Filter precedence** (only one mode should be used per call):\n1. `ids=` — comma-separated UUID batch hydrate (cap 50).    Overrides every other filter; unknown / malformed UUIDs are    silently dropped (no 404).\n2. `q=` — trigram KNN against name + brand, with optional    `brand` / `category` post-filtering on a 4x oversampled    candidate pool.\n3. Direct filters — `barcode`, `product_id`, `brand`,    `category`.\n\nResponse fields cover always-present catalog metadata (name, brand, size, image, UPC, category) plus sparse PDP enrichment (ingredients, allergens, dietary labels, SNAP eligibility, serving size). Q-only searches are cached in-process for 60s. Cache-Control is `public, max-age=60, stale-while-revalidate=300`.","operationId":"list_products_v1_products_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":100},{"type":"null"}],"description":"Text search on product name","title":"Q"},"description":"Text search on product name"},{"name":"barcode","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":6,"maxLength":32},{"type":"null"}],"title":"Barcode"}},{"name":"product_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Id"}},{"name":"ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated canonical_product_ids for batch hydrate. Cap 50 per request. Unknown / malformed ids are silently dropped from the response (no 404). Used by the Cart Pantry to hydrate canonical metadata for many rows in one call.","title":"Ids"},"description":"Comma-separated canonical_product_ids for batch hydrate. Cap 50 per request. Unknown / malformed ids are silently dropped from the response (no 404). Used by the Cart Pantry to hydrate canonical metadata for many rows in one call."},{"name":"brand","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Brand"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Category"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Matching products","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_ProductsResponse_"},"example":{"data":{"count":1,"results":[{"product_id":"9b0c...","name":"Wegmans Organic Whole Milk","brand":"Wegmans","size_display":"1 gal","upc":"077890123456","category":"Dairy","allergens":["milk"],"dietary_labels":["organic"],"snap_eligible":true}]},"meta":{"request_id":"abc123"}}}}},"422":{"description":"At least one filter is required"},"400":{"description":"Too many ids (cap 50)"}}}},"/v1/coupons":{"get":{"tags":["Coupons"],"summary":"List active coupons with savings math and geo filtering","description":"Active retailer coupons filtered by chain, store, product, brand, type, valid date, geo radius, and free-text. Each row is one (coupon × product) pair — multi-product coupons emit N rows (one per matched canonical product).\n\n**Geo path**: when `lat` + `lng` are supplied, the server filters by `radius_mi` (default 25mi, cap 100mi) and sorts in SQL — collapses the legacy per-store fan-out into one round trip. Default `limit` jumps from 50 to 200 in geo mode.\n\n**`with_prices=true`** inlines shelf prices for each coupon's product at the supplied `price_store_id` / `price_store_ids`, saving a follow-up `/v1/prices` round trip.\n\n**`mode=list`** returns a slim card-only payload (no description, fine print, deep_link_url, coupon_code) for feed views — detail modals fetch the full shape via `GET /v1/coupons/{id}`.\n\nETag + `If-None-Match` 304 is supported. Cache-Control is `public, max-age=300, stale-while-revalidate=600` in geo mode, else `max-age=60`. `Vary: Authorization, Accept-Encoding`.","operationId":"list_coupons_v1_coupons_get","parameters":[{"name":"chain_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Chain UUID","title":"Chain Id"},"description":"Chain UUID"},{"name":"chain_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated chain UUIDs (multi-chain filter; mutually exclusive with chain_id)","title":"Chain Ids"},"description":"Comma-separated chain UUIDs (multi-chain filter; mutually exclusive with chain_id)"},{"name":"priority_chain_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated chain UUIDs to float to the top of the result order (favorited-store prioritization). Reorders only — never filters the result set.","title":"Priority Chain Ids"},"description":"Comma-separated chain UUIDs to float to the top of the result order (favorited-store prioritization). Reorders only — never filters the result set."},{"name":"ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated coupon UUIDs (bulk-fetch by id — used by the marketing Clipped tab to render coupons that aren't in the current feed slice).","title":"Ids"},"description":"Comma-separated coupon UUIDs (bulk-fetch by id — used by the marketing Clipped tab to render coupons that aren't in the current feed slice)."},{"name":"store_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Store UUID","title":"Store Id"},"description":"Store UUID"},{"name":"product_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Canonical product UUID","title":"Product Id"},"description":"Canonical product UUID"},{"name":"brand_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Brand UUID (matches coupons.brand_id)","title":"Brand Id"},"description":"Brand UUID (matches coupons.brand_id)"},{"name":"product_brand","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Canonical product brand string (exact match against canonical_products.brand)","title":"Product Brand"},"description":"Canonical product brand string (exact match against canonical_products.brand)"},{"name":"discount_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"percent_off|dollar_off|bogo|bundle|loyalty_price|featured","title":"Discount Type"},"description":"percent_off|dollar_off|bogo|bundle|loyalty_price|featured"},{"name":"discount_types","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated discount_type values (multi-type filter; mutually exclusive with discount_type)","title":"Discount Types"},"description":"Comma-separated discount_type values (multi-type filter; mutually exclusive with discount_type)"},{"name":"category_slugs","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated department slugs (fresh, pantry, beverages, alcohol, snacks, frozen, household, personal-care, health, baby, pet, other). Each (coupon × product) row's department is derived by coupon_category_slug().","title":"Category Slugs"},"description":"Comma-separated department slugs (fresh, pantry, beverages, alcohol, snacks, frozen, household, personal-care, health, baby, pet, other). Each (coupon × product) row's department is derived by coupon_category_slug()."},{"name":"exclude_featured","in":"query","required":false,"schema":{"type":"boolean","description":"Exclude discount_type='featured' (weekly-ad placements, fuel points, storewide %)","default":false,"title":"Exclude Featured"},"description":"Exclude discount_type='featured' (weekly-ad placements, fuel points, storewide %)"},{"name":"is_digital_only","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Digital Only"}},{"name":"requires_loyalty","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Requires Loyalty"}},{"name":"valid_on","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Date the coupon must be valid on (defaults to today)","title":"Valid On"},"description":"Date the coupon must be valid on (defaults to today)"},{"name":"lat","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":90,"minimum":-90},{"type":"null"}],"title":"Lat"}},{"name":"lng","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":180,"minimum":-180},{"type":"null"}],"title":"Lng"}},{"name":"radius_mi","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.1},{"type":"null"}],"description":"Inclusive radius cap in miles. Defaults to 25 when lat/lng are supplied. Rejected with 400 when > 100.","title":"Radius Mi"},"description":"Inclusive radius cap in miles. Defaults to 25 when lat/lng are supplied. Rejected with 400 when > 100."},{"name":"with_prices","in":"query","required":false,"schema":{"type":"boolean","description":"Inline current/regular shelf price for each coupon's product at the supplied price store(s).","default":false,"title":"With Prices"},"description":"Inline current/regular shelf price for each coupon's product at the supplied price store(s)."},{"name":"price_store_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single store id to source shelf prices from when with_prices=true.","title":"Price Store Id"},"description":"Single store id to source shelf prices from when with_prices=true."},{"name":"price_store_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated store ids; multi-store mode returns prices_by_store keyed by id.","title":"Price Store Ids"},"description":"Comma-separated store ids; multi-store mode returns prices_by_store keyed by id."},{"name":"sort","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"best_discount (default) | most_savings | ending_soon | closest","title":"Sort"},"description":"best_discount (default) | most_savings | ending_soon | closest"},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search across product name, brand, and chain. Case-insensitive substring match.","title":"Q"},"description":"Free-text search across product name, brand, and chain. Case-insensitive substring match."},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":500,"minimum":1},{"type":"null"}],"description":"Default 50 (200 when lat/lng supplied).","title":"Limit"},"description":"Default 50 (200 when lat/lng supplied)."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"mode","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"'list' returns a slim card-only payload. Omit for the full detail shape.","title":"Mode"},"description":"'list' returns a slim card-only payload. Omit for the full detail shape."},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Matching coupons","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_CouponsResponse_"}}}},"304":{"description":"Not Modified (ETag matched If-None-Match)"},"400":{"description":"radius_mi > 100"},"422":{"description":"Bad discount_type or with_prices without price_store"}}}},"/v1/coupons/chains":{"get":{"tags":["Coupons"],"summary":"Distinct-chain coupon counts for filter UIs","description":"Per-chain coupon counts that pass the same eligibility filters as `GET /v1/coupons` (active + valid + has product scope). Powers chain-selector UI without the 500-row best-deals cap skewing visibility. Sorted by count DESC, then name ASC. Cache-Control is `public, max-age=300, stale-while-revalidate=600`.","operationId":"list_coupon_chains_v1_coupons_chains_get","parameters":[{"name":"valid_on","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Date the coupon must be valid on (defaults to today)","title":"Valid On"},"description":"Date the coupon must be valid on (defaults to today)"},{"name":"is_active","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Is Active"}},{"name":"exclude_featured","in":"query","required":false,"schema":{"type":"boolean","description":"Exclude discount_type='featured'","default":false,"title":"Exclude Featured"},"description":"Exclude discount_type='featured'"},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Per-chain coupon counts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_CouponChainsResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/coupons/brands":{"get":{"tags":["Coupons"],"summary":"Distinct-brand coupon counts for filter UIs","description":"Per-brand coupon counts using the same eligibility filters as `GET /v1/coupons`. Optional `chain_id` narrows to one chain. Powers brand-selector UI in the consumer coupons feed.","operationId":"list_coupon_brands_v1_coupons_brands_get","parameters":[{"name":"valid_on","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Date the coupon must be valid on (defaults to today)","title":"Valid On"},"description":"Date the coupon must be valid on (defaults to today)"},{"name":"is_active","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Is Active"}},{"name":"exclude_featured","in":"query","required":false,"schema":{"type":"boolean","description":"Exclude discount_type='featured'","default":false,"title":"Exclude Featured"},"description":"Exclude discount_type='featured'"},{"name":"chain_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Restrict aggregation to one chain","title":"Chain Id"},"description":"Restrict aggregation to one chain"},{"name":"chain_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated chain UUIDs","title":"Chain Ids"},"description":"Comma-separated chain UUIDs"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"description":"Max distinct brands returned","default":500,"title":"Limit"},"description":"Max distinct brands returned"},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Per-brand coupon counts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_CouponBrandsResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/coupons/types":{"get":{"tags":["Coupons"],"summary":"Per-discount-type coupon counts for filter UIs","description":"Per-discount-type counts (`percent_off`, `dollar_off`, `bogo`, `bundle`, `loyalty_price`, `featured`, etc.) using the same eligibility filters as `GET /v1/coupons`. Powers the discount-type filter checkboxes in the consumer coupons feed. Cache-Control is `public, max-age=300, stale-while-revalidate=600`.","operationId":"list_coupon_types_v1_coupons_types_get","parameters":[{"name":"valid_on","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Date the coupon must be valid on (defaults to today)","title":"Valid On"},"description":"Date the coupon must be valid on (defaults to today)"},{"name":"is_active","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Is Active"}},{"name":"exclude_featured","in":"query","required":false,"schema":{"type":"boolean","description":"Exclude discount_type='featured'","default":false,"title":"Exclude Featured"},"description":"Exclude discount_type='featured'"},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Per-type coupon counts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_CouponTypesResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/coupons/categories":{"get":{"tags":["Coupons"],"summary":"Per-department coupon counts for filter UIs","description":"Per-department deal counts (fresh, pantry, beverages, alcohol, snacks, frozen, household, personal-care, health, baby, pet, other) using the same eligibility filters as `GET /v1/coupons`. Each (coupon × product) row's department is derived by `coupon_category_slug()` (migration 348). Powers the category sidebar in the consumer coupons feed. Cache-Control is `public, max-age=300, stale-while-revalidate=600`.","operationId":"list_coupon_categories_v1_coupons_categories_get","parameters":[{"name":"valid_on","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Date the coupon must be valid on (defaults to today)","title":"Valid On"},"description":"Date the coupon must be valid on (defaults to today)"},{"name":"exclude_featured","in":"query","required":false,"schema":{"type":"boolean","description":"Exclude discount_type='featured'","default":false,"title":"Exclude Featured"},"description":"Exclude discount_type='featured'"},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Per-department coupon counts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_CouponCategoriesResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/coupons/{coupon_id}":{"get":{"tags":["Coupons"],"summary":"All deals (one row per matched product) for a single coupon","description":"Returns every matched product for one coupon as separate rows (same shape as `GET /v1/coupons`). Used by the consumer detail view to render the header plus the full list of products the coupon applies to.\n\nReturns `count=0` (NOT 404) when the coupon is missing, inactive, or has zero matched canonical products.\n\nFor `discount_type='bogo_cross_product'`, every row carries a `qualifiers` array listing the products the user must buy to unlock the reward (the row's primary product). Other discount types get an empty `qualifiers` array.","operationId":"get_coupon_v1_coupons__coupon_id__get","parameters":[{"name":"coupon_id","in":"path","required":true,"schema":{"type":"string","title":"Coupon Id"}},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Coupon deal rows (may be empty)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_CouponsResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/coupons/{coupon_id}/savings":{"get":{"tags":["Coupons"],"summary":"Compute dollar savings + rating for one coupon","description":"Compute total dollar savings, deal price, and a rating tier (`A`/`B`/`C`) for one coupon against our scraped catalog price (chain-average `regular_price` across stores).\n\nUse this for deal-quality ranking, CPG intelligence, and consumer-facing 'You save $X' CTAs. Sets `computable=false` with a human-readable `reason` when math can't be performed — e.g. loyalty prices (not exposed by retailer APIs), unparseable bundles, or products with no shelf-price coverage yet.\n\nReturns 404 when the coupon doesn't exist, is inactive, or has no `product_id`.","operationId":"get_coupon_savings_v1_coupons__coupon_id__savings_get","parameters":[{"name":"coupon_id","in":"path","required":true,"schema":{"type":"string","title":"Coupon Id"}},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Savings computation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_CouponSavingsResponse_"}}}},"404":{"description":"Coupon not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/products/{product_id}/coupons":{"get":{"tags":["Coupons"],"summary":"Active coupons that apply to a specific product","description":"Active coupons matching a canonical product id — covers both single-product coupons (`coupons.product_id`) and multi-UPC coupons via `coupon_products`. Multi-UPC coupons don't leak sibling SKUs (rows filtered to this product only).\n\nOptional `exclude_featured` strips weekly-ad placements and storewide-percent rows. Cache-Control is `public, max-age=60, stale-while-revalidate=300`.","operationId":"list_coupons_for_product_v1_products__product_id__coupons_get","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"string","title":"Product Id"}},{"name":"valid_on","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Date the coupon must be valid on (defaults to today)","title":"Valid On"},"description":"Date the coupon must be valid on (defaults to today)"},{"name":"exclude_featured","in":"query","required":false,"schema":{"type":"boolean","description":"Exclude discount_type='featured'","default":false,"title":"Exclude Featured"},"description":"Exclude discount_type='featured'"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Coupons for this product","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_CouponsResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/stores/{store_id}/coupons":{"get":{"tags":["Coupons"],"summary":"Active coupons valid at one store (chain-wide + store-scoped)","description":"Active coupons that apply at a store — both chain-wide entries (coupon `store_id IS NULL` matching the store's chain) and store-scoped entries (`store_id` equal to the requested id).\n\nSort order: store-scoped coupons appear before chain-wide ones, then by `savings_pct DESC`. Featured (weekly-ad) rows fall to the end unless `exclude_featured=true` removes them entirely.\n\nSoft-deleted duplicate store ids resolve transparently to their canonical survivor.","operationId":"list_coupons_for_store_v1_stores__store_id__coupons_get","parameters":[{"name":"store_id","in":"path","required":true,"schema":{"type":"string","title":"Store Id"}},{"name":"valid_on","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Date the coupon must be valid on (defaults to today)","title":"Valid On"},"description":"Date the coupon must be valid on (defaults to today)"},{"name":"discount_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Discount Type"}},{"name":"exclude_featured","in":"query","required":false,"schema":{"type":"boolean","description":"Exclude discount_type='featured' (weekly-ad placements, fuel points, storewide %)","default":false,"title":"Exclude Featured"},"description":"Exclude discount_type='featured' (weekly-ad placements, fuel points, storewide %)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Coupons valid at this store","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_CouponsResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/coupons/split-basket":{"get":{"tags":["Coupons"],"summary":"Cheapest-store-per-item breakdown of a clipped basket","description":"Computes the optimal multi-store split for a basket of products (the user's clipped coupons, by product_id) and returns a per-store grouping plus dollar savings versus shopping the same basket at a single best store.\n\nDifferentiator: leverages multi-chain pricing on every canonical product via `store_product_prices`. For each product the cheapest representative-store price across chains is picked; ties break on store-with-most-items-already-assigned, then alphabetical (chain_name, store_name) for determinism.\n\nPass `product_ids` as a comma-separated list of canonical product UUIDs (capped at 100). Cached for 60s per unique sorted set of product_ids.","operationId":"split_basket_v1_coupons_split_basket_get","parameters":[{"name":"product_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated canonical product UUIDs (max 100).","title":"Product Ids"},"description":"Comma-separated canonical product UUIDs (max 100)."},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Split-basket breakdown","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_SplitBasketResponse_"}}}},"422":{"description":"Missing or malformed product_ids"}}}},"/v1/chains":{"get":{"tags":["Chains"],"summary":"Bulk lookup of chain metadata + online-shopping config","description":"Return chain metadata in bulk. Primary use case: mobile / B2B client has a set of chain slugs and needs each chain's online-shopping config to decide which Shop Online flow to surface.\n\n**Modes**:\n- `slugs=` (comma-separated, max 50) — exact-match lookup;   returns every requested slug regardless of enabled state.\n- No `slugs` and `online_only=true` — filter to   `online_shopping_enabled=true` chains.\n- No `slugs` and `online_only=false` — falls through to the   online-only safety net to prevent accidental full-table   enumeration.\n\nReturns ordered by `slug` ASC.","operationId":"list_chains_v1_chains_get","parameters":[{"name":"slugs","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated chain slugs to look up in bulk (e.g. ?slugs=kroger,safeway,heb). Max 50 slugs per call. When omitted, only chains with online_shopping_enabled=true are returned.","title":"Slugs"},"description":"Comma-separated chain slugs to look up in bulk (e.g. ?slugs=kroger,safeway,heb). Max 50 slugs per call. When omitted, only chains with online_shopping_enabled=true are returned."},{"name":"online_only","in":"query","required":false,"schema":{"type":"boolean","description":"When true, filters to chains with online_shopping_enabled=true. Ignored when slugs is provided — slug lookups always return an exact match regardless of enabled state.","default":false,"title":"Online Only"},"description":"When true, filters to chains with online_shopping_enabled=true. Ignored when slugs is provided — slug lookups always return an exact match regardless of enabled state."},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Matching chains","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_ChainListResponse_"},"example":{"data":{"chains":[{"slug":"wegmans","name":"Wegmans","logo_url":"https://...","online_shopping_enabled":true,"requires_auth":false,"search_url_template":"https://shop.wegmans.com/search?search_query={q}"}],"count":1},"meta":{"request_id":"abc123"}}}}},"400":{"description":"Maximum 50 slugs per request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/chains/{slug}":{"get":{"tags":["Chains"],"summary":"Get a single chain by slug","description":"Fetch one chain's metadata + online-shopping config by slug (case-insensitive). Returns 404 when no chain matches. Same shape as one element of `GET /v1/chains` `chains` array.","operationId":"get_chain_v1_chains__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Chain found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_ChainResponse_"}}}},"404":{"description":"Chain not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/chains/{slug}/resolve-list":{"post":{"tags":["Chains"],"summary":"Resolve shopping-list items to canonical UPCs + retailer PDP URLs","description":"Given a list of items (free-text names and/or canonical product ids) and a target store, return per-item resolution: canonical UPC, retailer PDP URL, in-stock flag, and shelf price. Powers the Cart 'Send to chain' WebView flow so the app can deep-link past the search step.\n\n**Match paths**:\n1. Items WITH `canonical_product_id` — direct join.\n2. Items WITHOUT — fuzzy name match (pg_trgm, threshold 0.3),    with word-boundary tiebreaker so 'milk' picks 'Whole Milk'    over 'Milka'.\n\nResolved items carry `match_method` (`canonical_id` | `fuzzy_name`), and fuzzy matches expose `matched_name` + `match_confidence` so the client can render confirmation UI. Unresolved items come back with `resolved=false` and a `reason` string — the caller falls back to Phase 1 name-search URLs.\n\nCap: 50 items per request. `?include=coupons` is accepted but returns empty arrays (MVP — coupon-by-UPC join lands in a future release). Returns 404 when the chain doesn't exist, 400 when the chain has no online-shopping config or the store doesn't belong to it.","operationId":"resolve_list_v1_chains__slug__resolve_list_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"include","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated optional expansions. Supported: coupons (adds matching_coupons array per item; MVP returns []).","title":"Include"},"description":"Comma-separated optional expansions. Supported: coupons (adds matching_coupons array per item; MVP returns [])."},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveRequest"}}}},"responses":{"200":{"description":"Per-item resolution results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_ResolveResponse_"}}}},"400":{"description":"Chain not online-enabled, or store mismatch"},"404":{"description":"Chain not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/chains/kroger/oauth/authorize-url":{"get":{"tags":["Internal"],"summary":"Build the Kroger OAuth2 authorize URL (Cart app)","description":"INTERNAL — Cart-app-only. Build the Kroger OAuth2 authorize URL for the user-linking flow. Mobile opens the returned URL in `ASWebAuthenticationSession`; Kroger handles user sign-in and scope consent, then redirects to `redirect_uri` with a short-lived `code` plus the echoed `state` (CSRF token).\n\nScopes requested: `cart.basic:write`, `profile.compact`. Token exchange happens server-side via `POST .../oauth/exchange`.","operationId":"oauth_authorize_url_v1_chains_kroger_oauth_authorize_url_get","parameters":[{"name":"redirect_uri","in":"query","required":true,"schema":{"type":"string","description":"Mobile's custom URL scheme callback, e.g. cartbymainmarket://oauth/kroger","title":"Redirect Uri"},"description":"Mobile's custom URL scheme callback, e.g. cartbymainmarket://oauth/kroger"},{"name":"state","in":"query","required":true,"schema":{"type":"string","description":"CSRF token mobile generates; Kroger echoes it on redirect","title":"State"},"description":"CSRF token mobile generates; Kroger echoes it on redirect"},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Authorize URL ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_AuthorizeUrlResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/chains/kroger/oauth/exchange":{"post":{"tags":["Internal"],"summary":"Exchange a Kroger OAuth code for stored tokens","description":"INTERNAL — Cart-app-only. Exchange the short-lived authorization code from Kroger for access + refresh tokens. Tokens are stored server-side in `kroger_user_tokens` keyed by `cart_user_id`; mobile never sees the raw tokens. On conflict the row UPSERTs, so a re-link transparently replaces the previous tokens.\n\nReturns 502 when Kroger's token service is unreachable, 400 when Kroger rejects the code (expired, replayed, or wrong `redirect_uri`).","operationId":"oauth_exchange_v1_chains_kroger_oauth_exchange_post","parameters":[{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExchangeRequest"}}}},"responses":{"200":{"description":"Tokens stored","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_ExchangeResponse_"}}}},"400":{"description":"Kroger rejected the authorization code"},"502":{"description":"Kroger token service unreachable"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/chains/kroger/connection":{"get":{"tags":["Internal"],"summary":"Report whether a Cart user has linked Kroger","description":"INTERNAL — Cart-app-only. Report whether the Cart user has linked their Kroger account, including granted scopes and access-token expiry timestamp. Tokens themselves are NEVER returned (server-side only).\n\nUsed by the Cart Profile screen to render 'Connected to Kroger' vs 'Connect Kroger' CTAs without exposing token state.","operationId":"connection_status_v1_chains_kroger_connection_get","parameters":[{"name":"cart_user_id","in":"query","required":true,"schema":{"type":"string","description":"Cart app user identifier","title":"Cart User Id"},"description":"Cart app user identifier"},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Connection status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_ConnectionResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/chains/kroger/oauth/unlink":{"post":{"tags":["Internal"],"summary":"Unlink a Cart user's Kroger account","description":"INTERNAL — Cart-app-only. Delete the user's stored Kroger tokens. Idempotent: succeeds (returns `success=true`) even when the user wasn't linked. The next cart-sync attempt will 404 until the user re-links.","operationId":"oauth_unlink_v1_chains_kroger_oauth_unlink_post","parameters":[{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnlinkRequest"}}}},"responses":{"200":{"description":"Tokens removed (or were already absent)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_UnlinkResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/chains/kroger/cart/sync":{"post":{"tags":["Internal"],"summary":"Push Cart list items to a user's Kroger cart","description":"INTERNAL — Cart-app-only. Resolve list items to UPCs and post them to the user's Kroger cart via Kroger's `PUT /v1/cart/add`. Refreshes the user's stored access token automatically when expired.\n\nItems can carry a direct `upc` (skips the DB join) OR a `canonical_product_id` (resolved to UPC in bulk). Items without a resolvable UPC come back as `added=false, reason='no_upc'` — mobile surfaces these as 'not found' so the user can search manually.\n\nCap: 50 items per call. `modality` must be `PICKUP` or `DELIVERY`. Response includes a `cart_url` mobile opens in Safari so the user can review + check out.\n\nReturns 401 when the user's Kroger session is expired and the refresh failed (user must re-link), 404 when the user has never linked, 502 when Kroger's cart service is unreachable.","operationId":"cart_sync_v1_chains_kroger_cart_sync_post","parameters":[{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CartSyncRequest"}}}},"responses":{"200":{"description":"Per-item add results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_CartSyncResponse_"}}}},"400":{"description":"Too many items or bad modality"},"401":{"description":"Kroger session expired — user must re-link"},"404":{"description":"User has not linked Kroger"},"502":{"description":"Kroger cart service unreachable"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/stores/{store_id}/aisles":{"get":{"tags":["Aisles"],"summary":"Frequency-ranked aisle list for one store","description":"Distinct, normalized aisle labels for a store, ranked by product count. Output normalization (server-side):\n- Strip 'Aisle ' prefix; trim.\n- Strip ', Shelf N' suffix (collapses Dairy Shelf 1..5 →   'Dairy').\n- Drop empties and PoS-noise ('Front End', 'Customer   Service', etc.).\n- Re-prefix pure aisle codes ('1', '06A') as 'Aisle N' so   labels match overhead-sign navigation.\n\n**Resolution order**:\n1. Aggregate this store's SPP rows. When kept-row coverage ≥    30%, return `source='store'`.\n2. Otherwise aggregate across the chain → `source='chain'`.\n3. Otherwise `source='generic'`, empty list, coverage=0.\n\nResponse includes `aisle_counts` keyed by canonical label so the client can render '432 products' subtitles. Soft-deleted duplicate store ids resolve transparently. Cache-Control is `public, max-age=3600, stale-while-revalidate=7200`.","operationId":"get_store_aisles_v1_stores__store_id__aisles_get","parameters":[{"name":"store_id","in":"path","required":true,"schema":{"type":"string","title":"Store Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Max aisle entries to return, frequency-ranked. Default 20.","default":20,"title":"Limit"},"description":"Max aisle entries to return, frequency-ranked. Default 20."},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Aisle list (may be empty if source='generic')","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_AislesResponse_"}}}},"404":{"description":"Store not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/chains/{slug}/aisles":{"get":{"tags":["Aisles"],"summary":"Frequency-ranked aisle list for a whole chain","description":"Distinct, normalized aisle labels aggregated across the chain. Uses the same normalization pipeline as the per-store endpoint but draws from a SQL-level 50K-row SPP sample for bounded latency (~100ms vs ~2.7s on a full scan). Per-aisle counts are proportional-to-sample, not absolute.\n\nResponse always carries `source='chain'` (or `'generic'` when no aisles survive normalization). Cache-Control is `public, max-age=3600, stale-while-revalidate=7200`.","operationId":"get_chain_aisles_v1_chains__slug__aisles_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Max aisle entries to return. Default 50.","default":50,"title":"Limit"},"description":"Max aisle entries to return. Default 50."},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Aisle list (may be empty if source='generic')","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_AislesResponse_"}}}},"404":{"description":"Chain not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/indices/{slug}":{"get":{"tags":["Index"],"summary":"Read published index time series (egg, soda, etc.)","description":"Read the published monthly time series for one price index (e.g. `egg`, `soda`). Snapshots are precomputed by the cron (`scripts/compute_index_snapshot.py`); this endpoint only reads them. Methodology is published at `/methodology.md`.\n\n**Default view** is the clean institutional series: `publish_status` in (`auto`, `forced_publish`), `revoked=false`, rows ordered by period ASC.\n\nToggles widen the response progressively:\n- `include_beta=true` — surface pre-inception trial values.\n- `include_revoked=true` — include revoked snapshots (audit).\n- `include_preview=true` — include `forced_hide` / `draft`   (internal QA).\n\nDefault time window is the trailing 12 months. `?from=` / `?to=` accept `YYYY-MM` strings only (422 on bad format). `region` defaults to `national`; valid set is enumerated in `api/indices/regions.py`.\n\nReturns 404 when the slug is unknown. Returns 200 with an empty `data` and `meta.warning` when the index exists but is inactive (curation pending) or no snapshots match the window. Cache-Control is `public, max-age=300, stale-while-revalidate=600`.","operationId":"get_index_v1_indices__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"region","in":"query","required":false,"schema":{"type":"string","description":"One of: national, northeast, midwest, south, west","default":"national","title":"Region"},"description":"One of: national, northeast, midwest, south, west"},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start period (YYYY-MM). Defaults to today minus 12 months.","title":"From"},"description":"Start period (YYYY-MM). Defaults to today minus 12 months."},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End period (YYYY-MM). Defaults to today's month.","title":"To"},"description":"End period (YYYY-MM). Defaults to today's month."},{"name":"include_beta","in":"query","required":false,"schema":{"type":"boolean","description":"Include snapshots with publish_status='beta'. Beta snapshots are pre-inception trial-run values published for methodology validation only.","default":false,"title":"Include Beta"},"description":"Include snapshots with publish_status='beta'. Beta snapshots are pre-inception trial-run values published for methodology validation only."},{"name":"include_revoked","in":"query","required":false,"schema":{"type":"boolean","description":"Include snapshots that were revoked after publication. The default response excludes them; toggle on for transparency / audit reads.","default":false,"title":"Include Revoked"},"description":"Include snapshots that were revoked after publication. The default response excludes them; toggle on for transparency / audit reads."},{"name":"include_preview","in":"query","required":false,"schema":{"type":"boolean","description":"Include 'forced_hide' and 'draft' snapshots. Off by default; internal use for QA.","default":false,"title":"Include Preview"},"description":"Include 'forced_hide' and 'draft' snapshots. Off by default; internal use for QA."},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Index time series","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_IndexResponse_"},"example":{"data":{"data":[{"period":"2026-04","value_usd":4.12,"value_indexed":102.5,"n_observations":1234,"n_stores":87,"n_chains":12,"coverage_score":0.91,"publish_status":"auto","flagged_for_review":false,"revoked":false,"methodology_version":1}],"meta":{"slug":"egg","name":"MainMarket Large Grade-A Egg Index","methodology_url":"https://api.mainmarket.com/methodology.md","methodology_version":1,"inception_period":"2026-04"}},"meta":{"request_id":"abc123"}}}}},"404":{"description":"Index slug not found"},"422":{"description":"Invalid region or period format"}}}},"/v1/zip/{zip_code}/locate":{"get":{"tags":["Stores"],"summary":"Resolve a US ZIP to a centroid lat/lng","description":"Resolve a 5-digit US zip code to a `{lat, lng}` centroid by averaging the coordinates of live stores in that zip. Powers the marketing `/coupons` 'Near Me' sort — the client passes the resolved lat/lng to `/v1/coupons?lat=&lng=&sort=near_me`.\n\nv1 covers the ~80% of US zips that have at least one chain store in our catalog. Uncovered zips return 404; the caller falls back to `best_discount` sort. ZIP+4 inputs are accepted and truncated to the 5-digit prefix.\n\nCache-Control is `public, max-age=86400, stale-while-revalidate=604800` (zip centroids only shift when we add stores in new zips).","operationId":"locate_zip_v1_zip__zip_code__locate_get","parameters":[{"name":"zip_code","in":"path","required":true,"schema":{"type":"string","title":"Zip Code"}},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Centroid resolved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_ZipLocateResponse_"},"example":{"data":{"zip":"11205","lat":40.6989,"lng":-73.9778,"source":"stores_avg","store_count":4},"meta":{"request_id":"abc123"}}}}},"404":{"description":"No active stores in this zip"},"422":{"description":"zip must be 5 digits"}}}},"/v1/highlight":{"get":{"tags":["Products"],"summary":"Random product with prices across 6+ chains (marketing widget)","description":"Random eligible product (chain coverage ≥ 6) with per-chain average regular prices. Powers the `ProductHighlightCard` on the marketing landing page — surfaces a 'compare this product across retailers' teaser.\n\nEligibility is read from the daily `mv_product_chain_coverage` materialized view; per-chain averages are computed live from `store_product_prices`. Returns up to 6 chains sorted by `avg_price` ASC. Cache-Control is `public, max-age=600, stale-while-revalidate=1800`.\n\nReturns 404 in the rare case the eligible pool is empty (should not happen in steady state).","operationId":"get_highlight_v1_highlight_get","parameters":[{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Highlight product + per-chain prices","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_HighlightResponse_"}}}},"404":{"description":"No eligible products with 6+ chain coverage"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/menu-offers":{"get":{"tags":["menu-offers"],"summary":"List Menu Offers","description":"List QSR menu offers with chain/geo/daypart/fulfillment filters.\n\nThe geo model is bbox + haversine: when lat/lng are supplied we first\nfilter to offer rows whose `geo_lat/geo_lng` fall within a bounding box\nsized for `radius_mi`, then compute the precise distance in SQL. Rows\nfurther than the offer's own `geo_radius_miles` from the caller's point\n(the offer's serving radius — typically 8mi for McD) are dropped.\n\nMarketing's coupons page UNIONs this response with /v1/coupons; the row\nshapes diverge because QSR offers carry daypart/fulfillment/geo fields\nthat grocery coupons don't.","operationId":"list_menu_offers_v1_menu_offers_get","parameters":[{"name":"chain_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated store_chains UUIDs to restrict the list. Multiple values OR-combined.","title":"Chain Ids"},"description":"Comma-separated store_chains UUIDs to restrict the list. Multiple values OR-combined."},{"name":"lat","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":90,"minimum":-90},{"type":"null"}],"title":"Lat"}},{"name":"lng","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":180,"minimum":-180},{"type":"null"}],"title":"Lng"}},{"name":"radius_mi","in":"query","required":false,"schema":{"type":"number","maximum":200,"minimum":0.1,"description":"Radius around lat/lng to filter offers, in miles. Ignored when lat/lng are missing.","default":25.0,"title":"Radius Mi"},"description":"Radius around lat/lng to filter offers, in miles. Ignored when lat/lng are missing."},{"name":"daypart","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":3,"minimum":1},{"type":"null"}],"description":"Filter to offers whose daypart_filters array contains this value (1=breakfast, 2=lunch, 3=dinner).","title":"Daypart"},"description":"Filter to offers whose daypart_filters array contains this value (1=breakfast, 2=lunch, 3=dinner)."},{"name":"fulfillment","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to offers whose fulfillment_types array contains this value (pickup/delivery/eatin).","title":"Fulfillment"},"description":"Filter to offers whose fulfillment_types array contains this value (pickup/delivery/eatin)."},{"name":"offer_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One of: ['bogo', 'combo_bundle', 'dollar_off', 'featured', 'free_item', 'percent_off', 'spend_threshold']","title":"Offer Type"},"description":"One of: ['bogo', 'combo_bundle', 'dollar_off', 'featured', 'free_item', 'percent_off', 'spend_threshold']"},{"name":"active_only","in":"query","required":false,"schema":{"type":"boolean","description":"When true (default) only return rows with is_active=true AND valid_from <= now() AND (valid_to IS NULL OR valid_to > now()).","default":true,"title":"Active Only"},"description":"When true (default) only return rows with is_active=true AND valid_from <= now() AND (valid_to IS NULL OR valid_to > now())."},{"name":"sort","in":"query","required":false,"schema":{"type":"string","description":"One of: ['best_discount', 'ending_soon', 'nearest']. 'nearest' requires lat/lng.","default":"best_discount","title":"Sort"},"description":"One of: ['best_discount', 'ending_soon', 'nearest']. 'nearest' requires lat/lng."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_MenuOffersResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/menu-offers/{offer_id}":{"get":{"tags":["menu-offers"],"summary":"Get Menu Offer","description":"Fetch one menu_offer by id. Returns the same row shape as the list\nendpoint, sans `distance_miles` (no caller coordinates to compute it\nagainst here).","operationId":"get_menu_offer_v1_menu_offers__offer_id__get","parameters":[{"name":"offer_id","in":"path","required":true,"schema":{"type":"string","title":"Offer Id"}},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseEnvelope_MenuOfferRow_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/internal/compute-monthly-snapshot":{"post":{"tags":["Internal"],"summary":"Compute Monthly Snapshot","description":"Compute index_snapshots for the given period (default = prior month)\nacross every active `published_indices` × declared region.\n\nReplaces the Railway-cron + scripts/compute_monthly_snapshot_cron.sh\nsetup documented in supabase/migrations/CRON_README.md §2. Wired to\npg_cron via migration 342 — fires `0 4 1 * *`.","operationId":"compute_monthly_snapshot_v1_internal_compute_monthly_snapshot_post","parameters":[{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM; defaults to prior month","title":"Period"},"description":"YYYY-MM; defaults to prior month"},{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}},{"name":"authorization","in":"header","required":true,"schema":{"type":"string","title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Compute Monthly Snapshot V1 Internal Compute Monthly Snapshot Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"tags":["Health"],"summary":"Liveness + DB reachability check","description":"Returns `{\"status\": \"ok\"}` on success. Verifies the FastAPI process is alive AND that the Postgres pool can serve a `SELECT 1`. Used by Railway's built-in health monitor to auto-restart the service when the DB is unreachable — a bare process-alive ping is useless because uvicorn keeps answering HTTP after the DB goes away. Returns 503 when the DB ping fails.","operationId":"health_health_get","responses":{"200":{"description":"Service healthy","content":{"application/json":{"schema":{},"example":{"status":"ok"}}}},"503":{"description":"Service degraded — DB unreachable","content":{"application/json":{"example":{"meta":{"request_id":"abc123"},"error":{"code":503,"message":"db unreachable"}}}}}}}}},"components":{"schemas":{"AislesResponse":{"properties":{"aisles":{"items":{"type":"string"},"type":"array","title":"Aisles"},"source":{"type":"string","title":"Source"},"coverage":{"type":"number","title":"Coverage"},"aisle_counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Aisle Counts","default":{}},"chain_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Slug"},"store_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Store Id"}},"type":"object","required":["aisles","source","coverage"],"title":"AislesResponse"},"AuthorizeUrlResponse":{"properties":{"authorize_url":{"type":"string","title":"Authorize Url"},"state":{"type":"string","title":"State"},"scopes_requested":{"items":{"type":"string"},"type":"array","title":"Scopes Requested"}},"type":"object","required":["authorize_url","state","scopes_requested"],"title":"AuthorizeUrlResponse"},"BaseEnvelope_AislesResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/AislesResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[AislesResponse]"},"BaseEnvelope_AuthorizeUrlResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/AuthorizeUrlResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[AuthorizeUrlResponse]"},"BaseEnvelope_CartSyncResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/CartSyncResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[CartSyncResponse]"},"BaseEnvelope_ChainListResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/ChainListResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[ChainListResponse]"},"BaseEnvelope_ChainResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/ChainResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[ChainResponse]"},"BaseEnvelope_ConnectionResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/ConnectionResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[ConnectionResponse]"},"BaseEnvelope_CouponBrandsResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/CouponBrandsResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[CouponBrandsResponse]"},"BaseEnvelope_CouponCategoriesResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/CouponCategoriesResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[CouponCategoriesResponse]"},"BaseEnvelope_CouponChainsResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/CouponChainsResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[CouponChainsResponse]"},"BaseEnvelope_CouponSavingsResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/CouponSavingsResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[CouponSavingsResponse]"},"BaseEnvelope_CouponTypesResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/CouponTypesResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[CouponTypesResponse]"},"BaseEnvelope_CouponsResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/CouponsResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[CouponsResponse]"},"BaseEnvelope_ExchangeResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/ExchangeResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[ExchangeResponse]"},"BaseEnvelope_HighlightResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/HighlightResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[HighlightResponse]"},"BaseEnvelope_IndexResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/IndexResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[IndexResponse]"},"BaseEnvelope_MenuOfferRow_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/MenuOfferRow"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[MenuOfferRow]"},"BaseEnvelope_MenuOffersResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/MenuOffersResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[MenuOffersResponse]"},"BaseEnvelope_PricesResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/PricesResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[PricesResponse]"},"BaseEnvelope_ProductsResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/ProductsResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[ProductsResponse]"},"BaseEnvelope_ResolveResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/ResolveResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[ResolveResponse]"},"BaseEnvelope_SplitBasketResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/SplitBasketResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[SplitBasketResponse]"},"BaseEnvelope_StoreListResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/StoreListResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[StoreListResponse]"},"BaseEnvelope_StoreResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/StoreResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[StoreResponse]"},"BaseEnvelope_UnlinkResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/UnlinkResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[UnlinkResponse]"},"BaseEnvelope_ZipLocateResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/ZipLocateResponse"},{"type":"null"}]},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorBlock"},{"type":"null"}]}},"type":"object","required":["meta"],"title":"BaseEnvelope[ZipLocateResponse]"},"CartSyncItem":{"properties":{"client_id":{"type":"string","title":"Client Id"},"canonical_product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Canonical Product Id"},"upc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upc"},"quantity":{"type":"integer","title":"Quantity","default":1}},"type":"object","required":["client_id"],"title":"CartSyncItem"},"CartSyncRequest":{"properties":{"cart_user_id":{"type":"string","title":"Cart User Id"},"store_id":{"type":"string","title":"Store Id"},"items":{"items":{"$ref":"#/components/schemas/CartSyncItem"},"type":"array","title":"Items"},"modality":{"type":"string","title":"Modality","description":"PICKUP or DELIVERY per Kroger's cart API","default":"PICKUP"}},"type":"object","required":["cart_user_id","store_id","items"],"title":"CartSyncRequest"},"CartSyncResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/CartSyncResultItem"},"type":"array","title":"Results"},"added_count":{"type":"integer","title":"Added Count"},"skipped_count":{"type":"integer","title":"Skipped Count"},"cart_url":{"type":"string","title":"Cart Url","description":"URL for mobile to open in Safari so user can review + check out.","default":"https://www.kroger.com/cart"}},"type":"object","required":["results","added_count","skipped_count"],"title":"CartSyncResponse"},"CartSyncResultItem":{"properties":{"client_id":{"type":"string","title":"Client Id"},"added":{"type":"boolean","title":"Added"},"upc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upc"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},"type":"object","required":["client_id","added"],"title":"CartSyncResultItem"},"ChainListResponse":{"properties":{"chains":{"items":{"$ref":"#/components/schemas/ChainResponse"},"type":"array","title":"Chains"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["chains","count"],"title":"ChainListResponse"},"ChainOnlineConfig":{"properties":{"slug":{"type":"string","title":"Slug"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"},"online_shopping_enabled":{"type":"boolean","title":"Online Shopping Enabled"},"online_search_url_prefix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Online Search Url Prefix"},"online_search_space_char":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Online Search Space Char"},"online_search_url_suffix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Online Search Url Suffix"},"storefront_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Storefront Url"},"search_url_template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search Url Template"},"login_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Login Url"},"requires_auth":{"type":"boolean","title":"Requires Auth"},"session_cookie_domains":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Session Cookie Domains"},"user_agent_override":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Agent Override"},"add_to_cart_detection_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Add To Cart Detection Type"},"add_to_cart_detection_pattern":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Add To Cart Detection Pattern"},"online_shopping_referral_params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Online Shopping Referral Params"},"ios_app_url_scheme":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ios App Url Scheme"},"android_app_url_scheme":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Android App Url Scheme"},"config_updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Config Updated At"}},"type":"object","required":["slug","name","logo_url","online_shopping_enabled","online_search_url_prefix","online_search_space_char","online_search_url_suffix","storefront_url","search_url_template","login_url","requires_auth","session_cookie_domains","user_agent_override","add_to_cart_detection_type","add_to_cart_detection_pattern","online_shopping_referral_params","ios_app_url_scheme","android_app_url_scheme","config_updated_at"],"title":"ChainOnlineConfig"},"ChainResponse":{"properties":{"slug":{"type":"string","title":"Slug"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"},"website_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website Url"},"online_shopping_enabled":{"type":"boolean","title":"Online Shopping Enabled"},"online_search_url_prefix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Online Search Url Prefix"},"online_search_space_char":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Online Search Space Char"},"online_search_url_suffix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Online Search Url Suffix"},"storefront_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Storefront Url"},"search_url_template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search Url Template"},"login_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Login Url"},"requires_auth":{"type":"boolean","title":"Requires Auth"},"session_cookie_domains":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Session Cookie Domains"},"user_agent_override":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Agent Override"},"add_to_cart_detection_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Add To Cart Detection Type"},"add_to_cart_detection_pattern":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Add To Cart Detection Pattern"},"online_shopping_referral_params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Online Shopping Referral Params"},"ios_app_url_scheme":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ios App Url Scheme"},"android_app_url_scheme":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Android App Url Scheme"},"config_updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Config Updated At"}},"type":"object","required":["slug","name","logo_url","website_url","online_shopping_enabled","online_search_url_prefix","online_search_space_char","online_search_url_suffix","storefront_url","search_url_template","login_url","requires_auth","session_cookie_domains","user_agent_override","add_to_cart_detection_type","add_to_cart_detection_pattern","online_shopping_referral_params","ios_app_url_scheme","android_app_url_scheme","config_updated_at"],"title":"ChainResponse"},"ConnectionResponse":{"properties":{"linked":{"type":"boolean","title":"Linked"},"scopes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Scopes"},"access_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Access Expires At"},"linked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linked At"}},"type":"object","required":["linked"],"title":"ConnectionResponse"},"CouponBrandChain":{"properties":{"name":{"type":"string","title":"Name"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"}},"type":"object","required":["name"],"title":"CouponBrandChain"},"CouponBrandOption":{"properties":{"name":{"type":"string","title":"Name"},"count":{"type":"integer","title":"Count"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"},"product_count":{"type":"integer","title":"Product Count","default":0},"avg_savings_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Savings Pct"},"chain_count":{"type":"integer","title":"Chain Count","default":0},"chains":{"items":{"$ref":"#/components/schemas/CouponBrandChain"},"type":"array","title":"Chains","default":[]}},"type":"object","required":["name","count"],"title":"CouponBrandOption"},"CouponBrandsResponse":{"properties":{"count":{"type":"integer","title":"Count"},"results":{"items":{"$ref":"#/components/schemas/CouponBrandOption"},"type":"array","title":"Results"}},"type":"object","required":["count","results"],"title":"CouponBrandsResponse"},"CouponCategoriesResponse":{"properties":{"count":{"type":"integer","title":"Count"},"results":{"items":{"$ref":"#/components/schemas/CouponCategoryOption"},"type":"array","title":"Results"}},"type":"object","required":["count","results"],"title":"CouponCategoriesResponse"},"CouponCategoryOption":{"properties":{"category_slug":{"type":"string","title":"Category Slug"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["category_slug","count"],"title":"CouponCategoryOption"},"CouponChainOption":{"properties":{"chain_id":{"type":"string","title":"Chain Id"},"name":{"type":"string","title":"Name"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"},"count":{"type":"integer","title":"Count"},"chain_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Type"},"market_position":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Position"},"store_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Store Count"},"avg_savings_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Savings Pct"},"total_savings":{"type":"number","title":"Total Savings","default":0.0}},"type":"object","required":["chain_id","name","logo_url","count"],"title":"CouponChainOption"},"CouponChainsResponse":{"properties":{"count":{"type":"integer","title":"Count"},"results":{"items":{"$ref":"#/components/schemas/CouponChainOption"},"type":"array","title":"Results"}},"type":"object","required":["count","results"],"title":"CouponChainsResponse"},"CouponRow":{"properties":{"id":{"type":"string","title":"Id"},"chain_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Id"},"chain_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Name"},"chain_logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Logo Url"},"store_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Store Id"},"cta_kind":{"type":"string","title":"Cta Kind"},"cta_label":{"type":"string","title":"Cta Label"},"cta_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cta Url"},"product_id":{"type":"string","title":"Product Id"},"product_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Name"},"product_brand":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Brand"},"product_image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Image Url"},"discount_type":{"type":"string","title":"Discount Type"},"discount_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Discount Value"},"min_purchase_qty":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Purchase Qty"},"description":{"type":"string","title":"Description"},"fine_print":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fine Print"},"coupon_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coupon Code"},"valid_from":{"type":"string","format":"date","title":"Valid From"},"valid_to":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Valid To"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"},"is_digital_only":{"type":"boolean","title":"Is Digital Only"},"requires_loyalty":{"type":"boolean","title":"Requires Loyalty"},"is_clippable":{"type":"boolean","title":"Is Clippable"},"coupon_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coupon Type"},"deep_link_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deep Link Url"},"source":{"type":"string","title":"Source"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"},"size_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Size Text"},"value_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value Text"},"catalog_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Catalog Price"},"total_savings":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Savings"},"total_savings_basis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Total Savings Basis"},"savings_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Savings Pct"},"rating":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rating"},"deal_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Deal Price"},"bogo_get_qty":{"type":"integer","title":"Bogo Get Qty"},"min_purchase_amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Purchase Amount"},"role":{"type":"string","title":"Role"},"qualifier_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Qualifier Text"},"reward_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reward Text"},"source_type":{"type":"string","title":"Source Type","default":"coupon"},"qualifiers":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Qualifiers"},"current_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Current Price"},"regular_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Regular Price"},"is_on_sale":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is On Sale"},"price_store_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price Store Id"},"prices_by_store":{"anyOf":[{"additionalProperties":{"$ref":"#/components/schemas/StorePrice"},"type":"object"},{"type":"null"}],"title":"Prices By Store"}},"type":"object","required":["id","chain_id","chain_name","chain_logo_url","store_id","cta_kind","cta_label","cta_url","product_id","product_name","product_brand","product_image_url","discount_type","discount_value","min_purchase_qty","description","fine_print","coupon_code","valid_from","valid_to","image_url","is_digital_only","requires_loyalty","is_clippable","coupon_type","deep_link_url","source","source_url","size_text","value_text","catalog_price","total_savings","total_savings_basis","savings_pct","rating","deal_price","bogo_get_qty","min_purchase_amount","role"],"title":"CouponRow","description":"A single coupon-applied-to-a-product deal row.\n\nEach row is one (coupon, product) pair. A coupon that applies to N\ndistinct canonical products produces N rows — one per product, each\nwith its own shelf price and savings math. A coupon with zero matched\ncanonical products is suppressed entirely (no row), since we can't\nshow original price or savings without a product to anchor them.\n\nThe `id` field is the coupon UUID, so it is NOT unique within a list\nresponse — clients keying by `id` must use the (id, product_id) tuple\ninstead."},"CouponSavingsResponse":{"properties":{"coupon_id":{"type":"string","title":"Coupon Id"},"chain_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Id"},"chain_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Name"},"product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Id"},"product_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Name"},"discount_type":{"type":"string","title":"Discount Type"},"discount_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Discount Value"},"catalog_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Catalog Price"},"total_savings":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Savings"},"deal_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Deal Price"},"calculation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Calculation"},"savings_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Savings Pct"},"rating":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rating"},"catalog_store_count":{"type":"integer","title":"Catalog Store Count"},"computable":{"type":"boolean","title":"Computable"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},"type":"object","required":["coupon_id","chain_id","chain_name","product_id","product_name","discount_type","discount_value","catalog_price","total_savings","deal_price","calculation","savings_pct","rating","catalog_store_count","computable","reason"],"title":"CouponSavingsResponse"},"CouponTypeOption":{"properties":{"discount_type":{"type":"string","title":"Discount Type"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["discount_type","count"],"title":"CouponTypeOption"},"CouponTypesResponse":{"properties":{"count":{"type":"integer","title":"Count"},"results":{"items":{"$ref":"#/components/schemas/CouponTypeOption"},"type":"array","title":"Results"}},"type":"object","required":["count","results"],"title":"CouponTypesResponse"},"CouponsResponse":{"properties":{"count":{"type":"integer","title":"Count"},"results":{"items":{"$ref":"#/components/schemas/CouponRow"},"type":"array","title":"Results"},"fuzzy":{"type":"boolean","title":"Fuzzy","default":false}},"type":"object","required":["count","results"],"title":"CouponsResponse"},"ErrorBlock":{"properties":{"detail":{"title":"Detail","description":"Human-readable error description. Mirrors the `detail` passed to `HTTPException`. May be a string or, on validation errors, a structured list (FastAPI/Pydantic convention)."},"status_code":{"type":"integer","title":"Status Code","description":"HTTP status code echoed into the body for parity with the response status."}},"additionalProperties":true,"type":"object","required":["detail","status_code"],"title":"ErrorBlock","description":"Error body carried inside `BaseEnvelope.error` on 4xx/5xx responses.\n\n`detail` and `status_code` are always present; `detail` mirrors the\nstring a `HTTPException(detail=...)` would have surfaced, and\n`status_code` mirrors the HTTP status the response carries. Extra\nfields are allowed so the payment middleware can attach the x402/MPP\nchallenge metadata (`amount`, `currency`, `accepts`, …) on a 402\nwithout us versioning a new model per protocol upgrade."},"ExchangeRequest":{"properties":{"cart_user_id":{"type":"string","title":"Cart User Id","description":"Cart app's user identifier; opaque to us, unique per Cart user"},"code":{"type":"string","title":"Code"},"redirect_uri":{"type":"string","title":"Redirect Uri"}},"type":"object","required":["cart_user_id","code","redirect_uri"],"title":"ExchangeRequest"},"ExchangeResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes"},"access_expires_at":{"type":"string","title":"Access Expires At"}},"type":"object","required":["success","scopes","access_expires_at"],"title":"ExchangeResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HighlightChainPrice":{"properties":{"chain_id":{"type":"string","title":"Chain Id"},"chain_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Name"},"chain_logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Logo Url"},"avg_price":{"type":"number","title":"Avg Price"},"sample_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sample State"},"n_stores":{"type":"integer","title":"N Stores"}},"type":"object","required":["chain_id","chain_name","chain_logo_url","avg_price","sample_state","n_stores"],"title":"HighlightChainPrice"},"HighlightProduct":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"brand":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand"},"size_display":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Size Display"},"upc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upc"},"n_chains":{"type":"integer","title":"N Chains"}},"type":"object","required":["id","name","brand","size_display","upc","n_chains"],"title":"HighlightProduct"},"HighlightResponse":{"properties":{"product":{"$ref":"#/components/schemas/HighlightProduct"},"prices":{"items":{"$ref":"#/components/schemas/HighlightChainPrice"},"type":"array","title":"Prices"}},"type":"object","required":["product","prices"],"title":"HighlightResponse"},"HourBucket":{"properties":{"time":{"type":"string","title":"Time"},"busyness_score":{"type":"integer","title":"Busyness Score"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"}},"type":"object","required":["time","busyness_score","label"],"title":"HourBucket"},"IndexMeta":{"properties":{"as_of":{"type":"string","format":"date-time","title":"As Of"},"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"index_type":{"type":"string","title":"Index Type"},"method":{"type":"string","title":"Method"},"source_methodology":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Methodology"},"methodology_url":{"type":"string","title":"Methodology Url"},"methodology_version":{"type":"integer","title":"Methodology Version"},"inception_period":{"type":"string","title":"Inception Period"},"base_period":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Base Period"},"base_value_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Base Value Usd"},"regions":{"items":{"type":"string"},"type":"array","title":"Regions"},"constituents":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Constituents"},"chain_filter":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Chain Filter"},"request_id":{"type":"string","title":"Request Id"},"warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning"}},"type":"object","required":["as_of","slug","name","description","index_type","method","source_methodology","methodology_url","methodology_version","inception_period","base_period","base_value_usd","regions","constituents","chain_filter","request_id"],"title":"IndexMeta","description":"Per-request metadata. Stable across snapshots so the agent / client\ncan cache index identity separately from the time-series payload."},"IndexResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/SnapshotRow"},"type":"array","title":"Data"},"meta":{"$ref":"#/components/schemas/IndexMeta"}},"type":"object","required":["data","meta"],"title":"IndexResponse"},"MatchingCoupon":{"properties":{"coupon_id":{"type":"string","title":"Coupon Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"savings":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Savings"}},"type":"object","required":["coupon_id","title","savings"],"title":"MatchingCoupon"},"MenuOfferRow":{"properties":{"id":{"type":"string","title":"Id"},"chain_id":{"type":"string","title":"Chain Id"},"chain_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Name"},"chain_logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Logo Url"},"source":{"type":"string","title":"Source"},"native_offer_id":{"type":"string","title":"Native Offer Id"},"name":{"type":"string","title":"Name"},"short_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Short Description"},"long_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Long Description"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"},"image_source_basename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Source Basename"},"valid_from":{"type":"string","format":"date-time","title":"Valid From"},"valid_to":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Valid To"},"offer_type":{"type":"string","title":"Offer Type"},"min_spend_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Spend Cents"},"discount_amount_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Discount Amount Cents"},"discount_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Discount Pct"},"linked_menu_item_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linked Menu Item Id"},"linked_menu_item_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linked Menu Item Name"},"daypart_filters":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Daypart Filters"},"day_of_week_filters":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Day Of Week Filters"},"fulfillment_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Fulfillment Types"},"offer_bucket":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Offer Bucket"},"max_redemptions_per_day":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Redemptions Per Day"},"max_redemptions_per_week":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Redemptions Per Week"},"geo_lat":{"type":"number","title":"Geo Lat"},"geo_lng":{"type":"number","title":"Geo Lng"},"geo_radius_miles":{"type":"number","title":"Geo Radius Miles"},"distance_miles":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Distance Miles"},"is_active":{"type":"boolean","title":"Is Active"},"scraped_at":{"type":"string","format":"date-time","title":"Scraped At"}},"type":"object","required":["id","chain_id","chain_name","chain_logo_url","source","native_offer_id","name","short_description","long_description","image_url","image_source_basename","valid_from","valid_to","offer_type","min_spend_cents","discount_amount_cents","discount_pct","linked_menu_item_id","linked_menu_item_name","daypart_filters","day_of_week_filters","fulfillment_types","offer_bucket","max_redemptions_per_day","max_redemptions_per_week","geo_lat","geo_lng","geo_radius_miles","is_active","scraped_at"],"title":"MenuOfferRow"},"MenuOffersResponse":{"properties":{"count":{"type":"integer","title":"Count"},"results":{"items":{"$ref":"#/components/schemas/MenuOfferRow"},"type":"array","title":"Results"}},"type":"object","required":["count","results"],"title":"MenuOffersResponse"},"PopularTimes":{"properties":{"current_day":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Day"},"graph":{"additionalProperties":{"items":{"$ref":"#/components/schemas/HourBucket"},"type":"array"},"type":"object","title":"Graph"}},"type":"object","required":["current_day","graph"],"title":"PopularTimes"},"PriceRow":{"properties":{"product_id":{"type":"string","title":"Product Id"},"name":{"type":"string","title":"Name"},"brand":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand"},"size_display":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Size Display"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"},"upc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upc"},"store_id":{"type":"string","title":"Store Id"},"store_name":{"type":"string","title":"Store Name"},"chain_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Slug"},"price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Price"},"regular_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Regular Price"},"sale_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sale Price"},"is_on_sale":{"type":"boolean","title":"Is On Sale"},"unit_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Unit Price"},"unit_price_uom":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unit Price Uom"},"aisle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Aisle"},"section":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Section"},"available":{"type":"boolean","title":"Available"},"source_store_id":{"type":"string","title":"Source Store Id"},"source_store_name":{"type":"string","title":"Source Store Name"},"source_tier":{"type":"string","title":"Source Tier"},"source_distance_mi":{"type":"number","title":"Source Distance Mi"},"pricing_scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pricing Scope"},"distance_mi":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Distance Mi"}},"type":"object","required":["product_id","name","brand","size_display","image_url","upc","store_id","store_name","chain_slug","price","regular_price","sale_price","is_on_sale","unit_price","unit_price_uom","aisle","section","available","source_store_id","source_store_name","source_tier","source_distance_mi","pricing_scope"],"title":"PriceRow"},"PricesResponse":{"properties":{"count":{"type":"integer","title":"Count"},"results":{"items":{"$ref":"#/components/schemas/PriceRow"},"type":"array","title":"Results"}},"type":"object","required":["count","results"],"title":"PricesResponse"},"Product":{"properties":{"product_id":{"type":"string","title":"Product Id"},"name":{"type":"string","title":"Name"},"brand":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand"},"size_display":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Size Display"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"},"upc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upc"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"ingredients":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ingredients"},"allergens":{"items":{"type":"string"},"type":"array","title":"Allergens"},"dietary_labels":{"items":{"type":"string"},"type":"array","title":"Dietary Labels"},"snap_eligible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Snap Eligible"},"serving_size":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Serving Size"},"serving_size_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Serving Size Unit"},"n_chains":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Chains"}},"type":"object","required":["product_id","name","brand","size_display","image_url","upc","category","description","ingredients","allergens","dietary_labels","snap_eligible","serving_size","serving_size_unit"],"title":"Product"},"ProductsResponse":{"properties":{"count":{"type":"integer","title":"Count"},"results":{"items":{"$ref":"#/components/schemas/Product"},"type":"array","title":"Results"}},"type":"object","required":["count","results"],"title":"ProductsResponse"},"ResolveItem":{"properties":{"client_id":{"type":"string","title":"Client Id","description":"Opaque token the client uses to correlate request items with response results. Cart list item UUIDs work but any unique string is fine."},"name":{"type":"string","title":"Name"},"canonical_product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Canonical Product Id"}},"type":"object","required":["client_id","name"],"title":"ResolveItem"},"ResolveRequest":{"properties":{"store_id":{"type":"string","title":"Store Id"},"items":{"items":{"$ref":"#/components/schemas/ResolveItem"},"type":"array","title":"Items"}},"type":"object","required":["store_id","items"],"title":"ResolveRequest"},"ResolveResponse":{"properties":{"chain_slug":{"type":"string","title":"Chain Slug"},"store_id":{"type":"string","title":"Store Id"},"results":{"items":{"$ref":"#/components/schemas/ResolvedItem"},"type":"array","title":"Results"}},"type":"object","required":["chain_slug","store_id","results"],"title":"ResolveResponse"},"ResolvedItem":{"properties":{"client_id":{"type":"string","title":"Client Id"},"resolved":{"type":"boolean","title":"Resolved"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"canonical_product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Canonical Product Id"},"upc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upc"},"retailer_product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Retailer Product Id"},"pdp_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pdp Url"},"in_stock":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"In Stock"},"price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Price"},"sale_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sale Price"},"on_sale":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"On Sale"},"matching_coupons":{"anyOf":[{"items":{"$ref":"#/components/schemas/MatchingCoupon"},"type":"array"},{"type":"null"}],"title":"Matching Coupons"},"match_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Match Method"},"matched_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matched Name"},"match_confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Match Confidence"}},"type":"object","required":["client_id","resolved"],"title":"ResolvedItem"},"ResponseMeta":{"properties":{"request_id":{"type":"string","title":"Request Id","description":"uuid4 hex set by the request id middleware. Echoed in the `X-Request-ID` response header. Use to correlate client logs with server logs for support requests."},"billed_amount_usd":{"type":"string","title":"Billed Amount Usd","description":"Decimal string ($0.01-style). Free routes report '0.00'; paid routes report the price charged for this call. String (not float) to dodge sub-cent binary-float drift, mirroring the wire format of x402/MPP."},"source_tier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Tier","description":"How the underlying data was sourced for this response. 'specific' = requested store had its own scraped rows; 'regional' = nearest same-chain sibling within 50mi; 'chain' = same-chain sibling beyond 50mi or non-geocoded. Null on routes whose payload doesn't depend on a single tier (e.g. `/v1/products` catalog search, `/v1/chains`)."},"coverage_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Coverage Score","description":"Fraction (0.0-1.0) of requested data points the response could fulfill. For aggregate price queries it's matched_rows / candidate_rows; for store-aisle responses it's kept_aisles / total_spp_rows. Null when not applicable."},"freshness_p50_hours":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Freshness P50 Hours","description":"Median age (hours) of the underlying scrape rows that contributed to this response. Derived from `store_product_prices.scraped_at` for price/coupon routes and from per-row source timestamps elsewhere. Null on routes whose payload has no per-row freshness signal."},"freshness_p95_hours":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Freshness P95 Hours","description":"95th-percentile age (hours) of the underlying scrape rows. Tail freshness lets agents detect 'mostly fresh, one stale outlier' situations that a median alone hides. Null when `freshness_p50_hours` is null."},"store_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Store Count","description":"Number of distinct stores represented in `data`. For single-store responses this is 1; for chain/metro/geo responses it's the post-filter store cardinality. Null on routes whose payload has no store dimension (e.g. `/v1/products` catalog search)."}},"type":"object","required":["request_id","billed_amount_usd"],"title":"ResponseMeta","description":"Per-request envelope metadata.\n\nRequired fields are always populated. Optional fields are populated\nonly when they're meaningful for the route's payload — e.g.\n`freshness_p50_hours` only on aggregate price/coupon routes that read\n`store_product_prices.scraped_at`."},"Sentiment":{"properties":{"score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Score"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"axes":{"additionalProperties":{"$ref":"#/components/schemas/SentimentAxis"},"type":"object","title":"Axes"}},"type":"object","required":["score","label","summary","axes"],"title":"Sentiment"},"SentimentAxis":{"properties":{"score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Score"},"evidence":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evidence"}},"type":"object","required":["score","evidence"],"title":"SentimentAxis"},"SnapshotRow":{"properties":{"period":{"type":"string","title":"Period"},"value_usd":{"type":"number","title":"Value Usd"},"value_indexed":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Value Indexed"},"n_observations":{"type":"integer","title":"N Observations"},"n_stores":{"type":"integer","title":"N Stores"},"n_chains":{"type":"integer","title":"N Chains"},"coverage_score":{"type":"number","title":"Coverage Score"},"publish_status":{"type":"string","title":"Publish Status"},"flagged_for_review":{"type":"boolean","title":"Flagged For Review"},"flag_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flag Reason"},"revoked":{"type":"boolean","title":"Revoked"},"methodology_version":{"type":"integer","title":"Methodology Version"},"computed_at":{"type":"string","format":"date-time","title":"Computed At"}},"type":"object","required":["period","value_usd","value_indexed","n_observations","n_stores","n_chains","coverage_score","publish_status","flagged_for_review","flag_reason","revoked","methodology_version","computed_at"],"title":"SnapshotRow","description":"One published value for a (slug, period, region, methodology_version).\n\n`value_indexed` is computed at query time from the index's frozen\n`base_value_usd` (NULL until the first publishable snapshot is\nchosen as the base — beta snapshots return null indexed values)."},"SplitBasketGroup":{"properties":{"chain":{"$ref":"#/components/schemas/SplitChain"},"store_id":{"type":"string","title":"Store Id"},"store_name":{"type":"string","title":"Store Name"},"items":{"items":{"$ref":"#/components/schemas/SplitBasketItem"},"type":"array","title":"Items"},"subtotal_cents":{"type":"integer","title":"Subtotal Cents"},"savings_vs_single_store_cents":{"type":"integer","title":"Savings Vs Single Store Cents"}},"type":"object","required":["chain","store_id","store_name","items","subtotal_cents","savings_vs_single_store_cents"],"title":"SplitBasketGroup"},"SplitBasketItem":{"properties":{"product_id":{"type":"string","title":"Product Id"},"name":{"type":"string","title":"Name"},"price_cents":{"type":"integer","title":"Price Cents"},"savings_cents":{"type":"integer","title":"Savings Cents"}},"type":"object","required":["product_id","name","price_cents","savings_cents"],"title":"SplitBasketItem"},"SplitBasketResponse":{"properties":{"splits":{"items":{"$ref":"#/components/schemas/SplitBasketGroup"},"type":"array","title":"Splits"},"total_subtotal_cents":{"type":"integer","title":"Total Subtotal Cents"},"total_savings_cents":{"type":"integer","title":"Total Savings Cents"},"n_chains":{"type":"integer","title":"N Chains"},"n_items":{"type":"integer","title":"N Items"},"n_unmatched":{"type":"integer","title":"N Unmatched"}},"type":"object","required":["splits","total_subtotal_cents","total_savings_cents","n_chains","n_items","n_unmatched"],"title":"SplitBasketResponse"},"SplitChain":{"properties":{"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"name":{"type":"string","title":"Name"}},"type":"object","required":["slug","name"],"title":"SplitChain"},"StoreHoursDay":{"properties":{"day_of_week":{"type":"integer","title":"Day Of Week"},"day_name":{"type":"string","title":"Day Name"},"open_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Open Time"},"close_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Close Time"},"is_closed":{"type":"boolean","title":"Is Closed"}},"type":"object","required":["day_of_week","day_name","open_time","close_time","is_closed"],"title":"StoreHoursDay"},"StoreListResponse":{"properties":{"stores":{"items":{"$ref":"#/components/schemas/StoreResponse"},"type":"array","title":"Stores"},"count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Count"}},"type":"object","required":["stores"],"title":"StoreListResponse"},"StorePlacesEnrichment":{"properties":{"rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating"},"review_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Review Count"},"typical_time_spent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Typical Time Spent"},"price_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price Level"},"highlights":{"items":{"type":"string"},"type":"array","title":"Highlights"},"service_options":{"anyOf":[{"additionalProperties":{"type":"boolean"},"type":"object"},{"type":"null"}],"title":"Service Options"},"popular_times":{"anyOf":[{"$ref":"#/components/schemas/PopularTimes"},{"type":"null"}]},"review_topics":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Review Topics"},"sentiment":{"anyOf":[{"$ref":"#/components/schemas/Sentiment"},{"type":"null"}]},"enriched_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enriched At"}},"type":"object","required":["rating","review_count","typical_time_spent","price_level","highlights","service_options","popular_times","review_topics","sentiment","enriched_at"],"title":"StorePlacesEnrichment","description":"Google-Maps-derived store metadata + GPT-generated shopper sentiment.\nSourced from SerpAPI google_maps engine + an LLM sentiment pass. One-time\nseed (no monthly refresh) — see `places.enriched_at` for freshness."},"StorePrice":{"properties":{"current_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Current Price"},"regular_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Regular Price"},"is_on_sale":{"type":"boolean","title":"Is On Sale"}},"type":"object","required":["current_price","regular_price","is_on_sale"],"title":"StorePrice"},"StoreResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"banner_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Banner Name"},"web_external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Web External Id"},"chain_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Name"},"chain_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Slug"},"chain_logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Logo Url"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"address_line_2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address Line 2"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"zip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zip"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"},"lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lng"},"metro":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Metro"},"borough":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Borough"},"neighborhood":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Neighborhood"},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"has_pickup":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Pickup"},"has_delivery":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Delivery"},"is_dark_store":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Dark Store"},"is_active":{"type":"boolean","title":"Is Active"},"chain":{"anyOf":[{"$ref":"#/components/schemas/ChainOnlineConfig"},{"type":"null"}]},"hours":{"anyOf":[{"items":{"$ref":"#/components/schemas/StoreHoursDay"},"type":"array"},{"type":"null"}],"title":"Hours"},"places":{"anyOf":[{"$ref":"#/components/schemas/StorePlacesEnrichment"},{"type":"null"}]}},"type":"object","required":["id","name","display_name","banner_name","web_external_id","chain_name","chain_slug","chain_logo_url","address","address_line_2","city","state","zip","country","lat","lng","metro","borough","neighborhood","format","phone","has_pickup","has_delivery","is_dark_store","is_active"],"title":"StoreResponse"},"UnlinkRequest":{"properties":{"cart_user_id":{"type":"string","title":"Cart User Id"}},"type":"object","required":["cart_user_id"],"title":"UnlinkRequest"},"UnlinkResponse":{"properties":{"success":{"type":"boolean","title":"Success"}},"type":"object","required":["success"],"title":"UnlinkResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"ZipLocateResponse":{"properties":{"zip":{"type":"string","title":"Zip"},"lat":{"type":"number","title":"Lat"},"lng":{"type":"number","title":"Lng"},"source":{"type":"string","title":"Source"},"store_count":{"type":"integer","title":"Store Count"}},"type":"object","required":["zip","lat","lng","source","store_count"],"title":"ZipLocateResponse"}}},"tags":[{"name":"Stores","description":"Retail store discovery and detail. Geo search by lat/lng + radius, plus opt-in expansions for hours, chain online config, and Google-Maps-derived enrichment (rating, popular times, shopper sentiment)."},{"name":"Chains","description":"Store-chain metadata: slugs, names, logos, online-shopping config (search-URL templates, login URLs, app URL schemes). Supports bulk lookup. Includes the Cart resolve-list flow for mapping shopping-list items to canonical UPCs + retailer PDP URLs."},{"name":"Products","description":"Canonical product catalog — name, brand, size, UPC, category, plus sparse PDP enrichment (ingredients, allergens, dietary labels, SNAP eligibility). Trigram KNN search on name + brand, barcode lookup, and batch hydrate by `ids=`. Also surfaces a curated `highlight` product for marketing widgets."},{"name":"Prices","description":"Store-level shelf pricing with tiered source fallback: `specific` (the store has its own scrape), `regional` (nearest same-chain sibling within 50mi), `chain` (any other sibling). Includes sale / regular / unit price, aisle, section, and on-sale flags. Optional `?near=` filter sorts by distance for product-detail nearby lookups."},{"name":"Coupons","description":"Active retailer coupons with discount type, savings math, valid date ranges, and per-product matching. Supports geo filtering (radius around lat/lng), chain / brand / type aggregations for filter UIs, per-store and per-product lookups, and inline shelf-price joins. Savings endpoint computes deal price + rating against the scraped catalog."},{"name":"Aisles","description":"Frequency-ranked aisle lists per store or chain, normalized to overhead-sign labels (strip 'Aisle ' prefix and ', Shelf N' suffix, drop PoS-noise, re-prefix pure aisle codes). Cascades store → chain → generic when store-level aisle coverage is below 30%."},{"name":"Index","description":"Published price indices — institutional-grade time-series reads of `index_snapshots`. Snapshots are precomputed monthly by a cron job (this router never recomputes on the fly). Methodology is published at `/methodology.md`."},{"name":"Health","description":"Service liveness + DB reachability probe used by Railway auto-restart."},{"name":"Internal","description":"Cart-app-only flows. Token storage and OAuth handoffs for chains that support direct cart sync (Kroger today; others to follow). Not for external B2B agents — these are per-user and stateful."}]}