MainMarketAPI Reference
Get Access
Overview
  • Introduction
  • Authentication
  • Errors
Stores
  • List stores
  • Get a store
  • Store sentiment
  • Store coupons
  • Store aisles
Chains
  • List chains
  • Get a chain
  • Chain aisles
  • Resolve a list
Products
  • Catalog search
  • Get a product
  • Coupons for product
Prices
  • Search prices
  • Prices by UPC
  • Prices at a store
  • Cheapest nearby
Coupons
  • List coupons
  • Get a coupon
  • Coupon savings
Indices
  • Published indices
Discovery & meta
  • Discovery routes
  • OpenAPI spec
  • Agent skill spec
Overview
  • Introduction
  • Authentication
  • Errors
Stores
  • List stores
  • Get a store
  • Store sentiment
  • Store coupons
  • Store aisles
Chains
  • List chains
  • Get a chain
  • Chain aisles
  • Resolve a list
Products
  • Catalog search
  • Get a product
  • Coupons for product
Prices
  • Search prices
  • Prices by UPC
  • Prices at a store
  • Cheapest nearby
Coupons
  • List coupons
  • Get a coupon
  • Coupon savings
Indices
  • Published indices
Discovery & meta
  • Discovery routes
  • OpenAPI spec
  • Agent skill spec

Get a product

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.

GET/v1/products?product_id={id}$0.01
ℹ
One endpoint, four ways to look up a product
There's no separate GET /v1/products/{id} route — single and multi-product lookups all go through GET /v1/products. Use the parameter that matches what you have:

Lookup modes

?product_id=<uuid>
Single canonical product by MainMarket UUID. Cheapest path — direct primary-key lookup.
?ids=<uuid>,<uuid>,...
Batch hydrate. Up to 50 product UUIDs per call. Order is not preserved. Unknown / malformed ids are silently dropped — no 404. Used by the Cart Pantry to hydrate canonical metadata for many rows in one call.
?barcode=<upc>
Lookup by GS1 UPC or EAN. 6-32 digits. Matches both raw upc and zero-padded upc_normalized — pass either format and we'll find it.
?q=<text>
Trigram text search. Best for "find me oat milk" — see Catalog search for the full discovery flow with brand/category filters and KNN ranking.
⚠
At least one filter required
Unfiltered calls return 422 Unprocessable Entity. The catalog has 528k+ rows; a bare SELECT * would be unfair to other tenants.

Request — by product_id

Request
curl 'https://api.mainmarket.com/v1/products?product_id=5f52e31e-c81d-42ff-b4c4-756524b29ba2' \
  -H "Authorization: Bearer mm_live_..."

Request — by barcode

Request
curl 'https://api.mainmarket.com/v1/products?barcode=00016000275287' \
  -H "Authorization: Bearer mm_live_..."

Response — fully enriched product

200 OKjson
{
  "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"
    }
  ]
}

Response — sparsely enriched product

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

200 OK (sparse)json
{
  "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
    }
  ]
}

Field reference

Base catalog fields (always present)

NameTypeDescription
product_iduuidStable canonical product id. Use this everywhere downstream.
namestringCanonical product name as it would appear on the shelf tag.
brandstring | nullManufacturer brand. Sparse for private label.
size_displaystring | nullCustomer-facing size, e.g. "18 oz", "4 ct".
image_urlstring | nullPublic CDN image URL — typically the chain's PDP hero image.
upcstring | nullGS1 UPC, raw chain format. May be missing for products that have only an internal SKU.
categorystring | nullInternal category label (e.g. 'Cereal', 'Dairy & Eggs').
descriptionstring | nullLong-form description from the chain's PDP. Most populated for Whole Foods, Wegmans, H-E-B.

Enrichment fields (sparse — null/empty when not scraped)

NameTypeDescription
ingredientsstring | nullFree-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.
allergensstring[]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_labelsstring[]Tags such as gluten_free, organic, vegan, kosher, heart_healthy. Empty array when none are listed.
snap_eligibleboolean | nullTrue/false when known. Null when not yet classified — column is sparsely populated today.
serving_size, serving_size_unitstring | nullServing size as printed (e.g. "39" + "g"). Combine client-side for display.
ℹ
What we don't return today
Per-nutrient values (calories, fats, sodium, carbs, sugars, protein) are not currently returned — the production catalog only carries free-text ingredients, allergens, dietary labels, SNAP eligibility, and serving-size strings. Per-nutrient extraction is on the roadmap; reach out if your use case needs it.

Notable behavior

  • Trust the UUID, not the UPC. A given UPC may be reused across different products by different manufacturers over time. The product_id UUID is stable forever.
  • Sparse fields default consistently. Arrays return [] (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 aggressively. Catalog metadata is slow-moving. Responses carry Cache-Control: public, max-age=60, stale-while-revalidate=300 — honor that to cut cost.
  • Enrichment coverage is chain-dependent. If you need ingredients for a specific UPC, your odds are best for products carried by Wegmans, H-E-B, or Whole Foods. Filter by chain on /v1/prices first to confirm coverage.

Errors

NameTypeDescription
422Unprocessable EntityNo filter provided, malformed UUID, barcode wrong length, or ids batch over 50.
402Payment RequiredPaid route — no payment proof attached.
500Internal Server ErrorUnexpected server fault. Retry with exponential backoff.

Related

  • Catalog search — multi-product discovery with text search, brand/category filters, and KNN ranking.
  • Coupons for product — every active coupon attached to a single product.
  • Search prices — pricing for this product across stores and chains.