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

Search prices

Store-scoped pricing search with tiered fallback (specific → regional → chain). Joins canonical products against the latest store_product_prices materialization. Optional geo sort by user location.

GET/v1/prices$0.01
⚠
Both a product filter and a store filter are required
At least one of q, barcode, product_id, or product_ids and at least one of store_id, store_ids, chain, metro, state, or near must be present. Unfiltered scans return 422 Unprocessable Entity.

Query parameters

Product side (≥1 required)

NameTypeDescription
qstringTrigram text search on product name + brand. 1–100 characters.
barcodestringUPC or EAN. 6–32 digits. Matches both raw upc and zero-padded upc_normalized.
product_iduuidSingle canonical product UUID.
product_idsstringComma-separated canonical product UUIDs.

Store side (≥1 required)

NameTypeDescription
store_iduuidSingle store UUID. Triggers tier resolution if the store has no own prices.
store_idsstringComma-separated store UUIDs. Each one is resolved independently.
chainstringChain slug filter, e.g. wegmans.
metrostringMetro key, e.g. nyc.
statestringTwo-letter state code.
nearstringGeo filter as lat,lng e.g. 40.6892,-73.9942. Filters source stores within radius_mi and sorts by distance ascending.
radius_minumberdefault: 10Radius in miles for the near= filter. Range 0.1–50. Ignored when near is not set.

Other

NameTypeDescription
on_salebooleanWhen true, returns only rows with is_on_sale = true.
normalize_aislesbooleandefault: falseWhen true, returns the aisle field with the same normalization applied by /v1/stores/{id}/aisles. Lets clients join price rows to the aisle picker without separate normalization.
limitintegerdefault: 501–500.
offsetintegerdefault: 0Cursor offset.

Request

Request
curl 'https://api.mainmarket.com/v1/prices?barcode=00016000275287&near=40.6892,-73.9942&radius_mi=5'

Response

200 OKjson
{
  "count": 2,
  "results": [
    {
      "product_id": "9d4e1c80-78a3-4a6d-9b1f-2cdb3d0c7e90",
      "name": "Cheerios Cereal, 18 oz",
      "brand": "Cheerios",
      "size_display": "18 oz",
      "image_url": "https://.../cheerios.jpg",
      "upc": "00016000275287",
      "store_id": "8c1a4d1e-30a7-4d92-9e1c-1cb43c6f2e10",
      "store_name": "Wegmans Brooklyn",
      "chain_slug": "wegmans",
      "price": 4.99,
      "regular_price": 5.49,
      "sale_price": 4.99,
      "is_on_sale": true,
      "unit_price": 0.28,
      "unit_price_uom": "oz",
      "aisle": "Aisle 4",
      "section": "Cereal",
      "available": true,
      "source_store_id": "8c1a4d1e-30a7-4d92-9e1c-1cb43c6f2e10",
      "source_store_name": "Wegmans Brooklyn",
      "source_tier": "specific",
      "source_distance_mi": 0.0,
      "pricing_scope": "per_store",
      "distance_mi": 0.42
    },
    {
      "product_id": "9d4e1c80-78a3-4a6d-9b1f-2cdb3d0c7e90",
      "name": "Cheerios Cereal, 18 oz",
      "brand": "Cheerios",
      "size_display": "18 oz",
      "image_url": "https://.../cheerios.jpg",
      "upc": "00016000275287",
      "store_id": "ee44...",
      "store_name": "HEB Houston Heights",
      "chain_slug": "heb",
      "price": 4.49,
      "regular_price": 4.49,
      "sale_price": null,
      "is_on_sale": false,
      "unit_price": 0.25,
      "unit_price_uom": "oz",
      "aisle": "Aisle 12",
      "section": null,
      "available": true,
      "source_store_id": "ee44...",
      "source_store_name": "HEB Houston Heights",
      "source_tier": "regional",
      "source_distance_mi": 8.7,
      "pricing_scope": "per_store",
      "distance_mi": null
    }
  ]
}

PriceRow fields

NameTypeDescription
product_iduuidCanonical product id.
name, brand, size_display, image_url, upcstring | nullCanonical product fields. size_display falls back to spp.pack_size when the canonical column is null.
store_id, store_namestringThe store the customer asked about (or the only matching store when no store_id was passed).
chain_slugstring | nullChain slug of the source store.
pricenumber | nullEffective price the customer would actually pay (sale_price when on sale, else regular_price).
regular_price, sale_pricenumber | nullBoth rungs from the latest scrape.
is_on_salebooleanTrue when sale_price < regular_price.
unit_pricenumber | nullPrice per unit_price_uom, when the chain exposes it.
unit_price_uomstring | nullUnit, e.g. 'oz', 'lb', 'fl oz'.
aisle, sectionstring | nullWhere the product lives in the store. Aisle is normalized when normalize_aisles=true.
availablebooleanStock signal from the latest scrape.
source_store_id, source_store_namestringThe store that actually supplied the price row. Differs from store_id when source_tier ≠ 'specific'.
source_tierstringOne of specific, regional, chain. See "Tiered fallback" below.
source_distance_minumberMiles between the requested store and the source store. 0.0 when source_tier='specific'.
pricing_scopestring | nullChain-level pricing model: per_store (each store has its own prices), chain_level (one price across the chain), or no_ecommerce (we have no online price source).
distance_minumber | nullUser-to-source-store miles. Populated only when ?near= was supplied. Distinct from source_distance_mi.

Notable behavior

Tiered fallback

When you ask for prices at a store that hasn't been scraped (or that belongs to a chain with chain-wide pricing), the API resolves the request to a sibling store in the same chain. The source_tier field tells you which path was taken:

specific
The requested store has its own scraped prices. source_distance_mi = 0. Surface without caveats.
regional
Nearest same-chain sibling within 50 miles supplies the prices. Common for chains that price uniformly within a metro.
chain
Same-chain sibling further than 50 miles, or non-geocoded fallback. Common for chain_level pricing scopes; for per_store chains this is a directional estimate, not a guarantee.

The fallback is computed at write-time in store_price_source and never crosses chain boundaries. When no store filter is given, every row is labeled specific with zero distance. See Concepts → Pricing tiers and freshness for guidance on when to disclose non-specific tiers in your UI.

Sort behavior

  • ?near= set: results sort by distance_mi ASC, then price ASC. Product detail "Nearby Prices" wants closest-then-cheapest.
  • ?q= set without ?near=: results sort by trigram relevance.
  • Otherwise: results sort by product name then store name (deterministic, page-friendly).

Caching

Responses send Cache-Control: public, max-age=30, stale-while-revalidate=120. Prices change roughly hourly; the short TTL keeps typeahead snappy without showing stale rungs.

ℹ
Soft-deleted ids resolve transparently
Pre-2026-04-25 cached store ids that lost a dedup pass are mapped to their canonical survivor before any query runs. You'll get prices for the right store without doing anything.

Errors

NameTypeDescription
422Unprocessable EntityMissing product or store filter, malformed near=, lat/lng out of range, or radius_mi out of 0.1–50.
402Payment RequiredPaid route — no payment proof.
500Internal Server ErrorUnexpected server fault.