Single-product lookup against the canonical catalog. Returns one row per matched product with every field — base catalog fields (always present) plus sparse enrichment (ingredients, allergens, dietary labels, SNAP eligibility, serving size) when scraped from a chain that exposes it.
/v1/products?product_id={id}$0.01GET /v1/products/{id} route — single and multi-product lookups all go through GET /v1/products. Use the parameter that matches what you have:?product_id=<uuid>?ids=<uuid>,<uuid>,...?barcode=<upc>upc and zero-padded upc_normalized — pass either format and we'll find it.?q=<text>422 Unprocessable Entity. The catalog has 528k+ rows; a bare SELECT * would be unfair to other tenants.curl 'https://api.mainmarket.com/v1/products?product_id=5f52e31e-c81d-42ff-b4c4-756524b29ba2' \
-H "Authorization: Bearer mm_live_..."curl 'https://api.mainmarket.com/v1/products?barcode=00016000275287' \
-H "Authorization: Bearer mm_live_..."{
"count": 1,
"results": [
{
"product_id": "5f52e31e-c81d-42ff-b4c4-756524b29ba2",
"name": "Cheerios Whole Grain Oats Cereal, 18 oz",
"brand": "Cheerios",
"size_display": "18 oz",
"image_url": "https://.../cheerios.jpg",
"upc": "00016000275287",
"category": "Cereal",
"description": "Whole grain oats cereal. Heart-healthy. America's favorite cereal.",
"ingredients": "Whole grain oats, corn starch, sugar, salt, tripotassium phosphate, vitamin E (mixed tocopherols) added to preserve freshness.",
"allergens": ["wheat"],
"dietary_labels": ["heart_healthy", "kosher"],
"snap_eligible": true,
"serving_size": "39",
"serving_size_unit": "g"
}
]
}Most products in the catalog look like this — base fields populated, enrichment fields null or empty. Coverage varies by chain (Wegmans / H-E-B / Whole Foods scrape PDP enrichment; Kroger / Lidl typically don't).
{
"count": 1,
"results": [
{
"product_id": "5f52e31e-c81d-42ff-b4c4-756524b29ba2",
"name": "Cheerios Whole Grain Oats Cereal, 18 oz",
"brand": "Cheerios",
"upc": "00016000275287",
"size_display": "18 oz",
"category": "Cereal",
"image_url": "https://.../cheerios.jpg",
"description": null,
"ingredients": null,
"allergens": [],
"dietary_labels": [],
"snap_eligible": null,
"serving_size": null,
"serving_size_unit": null
}
]
}| Name | Type | Description |
|---|---|---|
product_id | uuid | Stable canonical product id. Use this everywhere downstream. |
name | string | Canonical product name as it would appear on the shelf tag. |
brand | string | null | Manufacturer brand. Sparse for private label. |
size_display | string | null | Customer-facing size, e.g. "18 oz", "4 ct". |
image_url | string | null | Public CDN image URL — typically the chain's PDP hero image. |
upc | string | null | GS1 UPC, raw chain format. May be missing for products that have only an internal SKU. |
category | string | null | Internal category label (e.g. 'Cereal', 'Dairy & Eggs'). |
description | string | null | Long-form description from the chain's PDP. Most populated for Whole Foods, Wegmans, H-E-B. |
| Name | Type | Description |
|---|---|---|
ingredients | string | null | Free-text ingredient list as printed on the package. Sourced from chains that expose ingredients on PDPs (Wegmans, H-E-B, Whole Foods primarily); null elsewhere. |
allergens | string[] | Allergen tags. Empty array (not null) when none are listed. Includes both clean tags ('wheat', 'milk') and raw label-disclosure phrases — clients should treat the array as an unordered set. |
dietary_labels | string[] | Tags such as gluten_free, organic, vegan, kosher, heart_healthy. Empty array when none are listed. |
snap_eligible | boolean | null | True/false when known. Null when not yet classified — column is sparsely populated today. |
serving_size, serving_size_unit | string | null | Serving size as printed (e.g. "39" + "g"). Combine client-side for display. |
product_id UUID is stable forever.[] (not null) when nothing is on file — you can iterate without null-checks. Scalar fields return null to clearly signal "no data" vs "empty value."Cache-Control: public, max-age=60, stale-while-revalidate=300 — honor that to cut cost.chain on /v1/prices first to confirm coverage.| Name | Type | Description |
|---|---|---|
422 | Unprocessable Entity | No filter provided, malformed UUID, barcode wrong length, or ids batch over 50. |
402 | Payment Required | Paid route — no payment proof attached. |
500 | Internal Server Error | Unexpected server fault. Retry with exponential backoff. |