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

List coupons

Active grocery coupons across all tracked chains. Filterable by chain, store, product, brand, category, discount type, and geography. ETag-aware for cheap re-polls.

GET/v1/coupons$0.01

Query parameters

Resource filters

NameTypeDescription
chain_iduuidLimit to one chain.
store_iduuidLimit to coupons valid at one store (chain-wide + store-scoped).
product_iduuidLimit to coupons matching one canonical product.
brand_iduuidLimit to one brand.
category_iduuidLimit to one category.

Discount filters

NameTypeDescription
discount_typestringOne of percent_off, dollar_off, bogo, bundle, loyalty_price, featured.
exclude_featuredbooleandefault: falseDrop discount_type='featured' rows.
is_digital_onlybooleanFilter on whether the coupon clips digitally only (vs. cut-out / paper).
requires_loyaltybooleanFilter on loyalty-program requirement.
valid_ondateISO date YYYY-MM-DD. Filters to coupons whose valid_from ≤ valid_on ≤ valid_to. Defaults to today.
is_activebooleandefault: trueWhen false, includes expired/disabled coupons.

Geo

NameTypeDescription
latnumberLatitude, paired with lng + radius_mi.
lngnumberLongitude.
radius_minumberdefault: 25Miles. Range 0.1–50.

With-prices join

NameTypeDescription
with_pricesbooleandefault: falseWhen true, joins each coupon to live store_product_prices and adds current_price, regular_price, is_on_sale, and prices_by_store fields.
price_store_iduuidSingle store id to source the joined prices from.
price_store_idsstringComma-separated store ids; populates prices_by_store with one entry per store.

Pagination

NameTypeDescription
limitintegerdefault: 50Range 1–200 (geo calls allow 200 to fan out farther).
offsetintegerdefault: 0Cursor offset.
ℹ
ETag + If-None-Match
Responses include an ETag hash. Subsequent calls with the same params and If-None-Match: <etag> return 304 Not Modified with no body. Use this to keep a coupon list fresh without paying for unchanged responses.

Request

Request
curl 'https://api.mainmarket.com/v1/coupons?chain_id=12d6...&exclude_featured=true&limit=50'

Response

200 OKjson
{
  "count": 1,
  "results": [
    {
      "id": "0bc5b1d4-9a25-4d70-8a82-2ab3f6f1d901",
      "chain_id": "12d6...",
      "chain_name": "HEB",
      "chain_logo_url": "https://.../heb.png",
      "store_id": null,
      "product_id": "9d4e...",
      "product_name": "HEB Whole Milk",
      "product_brand": "HEB",
      "product_image_url": "https://.../milk.jpg",
      "discount_type": "dollar_off",
      "discount_value": 1.00,
      "min_purchase_qty": 1,
      "description": "$1 off any HEB gallon milk",
      "fine_print": "Limit 4 per transaction",
      "coupon_code": null,
      "valid_from": "2026-04-29",
      "valid_to": "2026-05-13",
      "image_url": "https://.../coupon.png",
      "is_digital_only": true,
      "requires_loyalty": true,
      "is_clippable": true,
      "deep_link_url": "heb://coupons/0bc5...",
      "source": "heb",
      "source_url": "https://www.heb.com/coupons/...",
      "size_text": "1 gal",
      "value_text": "$1 off",
      "catalog_price": 4.49,
      "total_savings": 1.00,
      "total_savings_basis": "catalog_price",
      "savings_pct": 22.27,
      "rating": "good",
      "deal_price": 3.49,
      "bogo_get_qty": null,
      "min_purchase_amount": null,
      "role": "buy"
    }
  ]
}

CouponRow fields

NameTypeDescription
iduuidStable coupon id.
chain_id, chain_name, chain_logo_urlmixedOwning chain metadata.
store_iduuid | nullStore id when the coupon is store-scoped; null for chain-wide coupons.
product_id, product_name, product_brand, product_image_urlmixedLinked canonical product, when matched. Null for unmatched / multi-product coupons.
discount_typestringOne of percent_off, dollar_off, bogo, bundle, loyalty_price, featured.
discount_valuenumberMagnitude. % for percent_off, $ for dollar_off, multiplier for bogo.
min_purchase_qtyinteger | nullMinimum item count to trigger the coupon.
min_purchase_amountnumber | nullMinimum basket subtotal in USD.
bogo_get_qtyinteger | nullGet-leg quantity for BOGO coupons (e.g. buy 2 get 1 free → bogo_get_qty=1).
description, fine_printstring | nullCustomer-facing copy.
coupon_codestring | nullCode to enter at checkout, when applicable.
valid_from, valid_todateInclusive validity window.
image_urlstring | nullCoupon artwork.
is_digital_only, requires_loyalty, is_clippablebooleanBehavior flags.
deep_link_urlstring | nullDeep link into the chain's app or coupon-clip flow.
source, source_urlstring | nullWhere we scraped the coupon from.
size_text, value_textstring | nullFree-text size and value labels as printed (e.g. '1 gal', '$1 off').
catalog_pricenumber | nullBaseline regular price used for savings_pct.
total_savingsnumber | nullBest-case dollar savings.
total_savings_basisstring | nullWhat total_savings was derived from: catalog_price, store_price, or null.
savings_pctnumber | null0.0–100.0. Null when no baseline available.
ratingstring | nullInternal label: great, good, okay, poor.
deal_pricenumber | nullEffective per-unit price after the coupon clips.
rolestringFor BOGO/bundle: buy (qualifies the coupon), get (the freebie), or either.

with_prices=true response shape

200 OK (with_prices=true)json
{
  "count": 1,
  "results": [
    {
      "id": "0bc5...",
      "product_name": "HEB Whole Milk",
      "discount_type": "dollar_off",
      "discount_value": 1.00,
      "deal_price": 3.49,
      "current_price": 4.49,
      "regular_price": 4.49,
      "is_on_sale": false,
      "price_store_id": "8c1a...",
      "prices_by_store": {
        "8c1a...": { "price": 4.49, "is_on_sale": false }
      }
    }
  ]
}
NameTypeDescription
current_pricenumber | nullLive effective price at the joined store.
regular_pricenumber | nullLive regular price (pre-sale).
is_on_salebooleanTrue when current_price < regular_price.
price_store_iduuidStore id the price was joined from (single-store mode).
prices_by_storeobjectWhen price_store_ids is supplied, a map of store_id → { price, is_on_sale } entries.

Notable behavior

  • Each row is one (coupon, product) pair, not one coupon. A coupon that applies to N distinct canonical products produces N rows — one per product, each with its own catalog_price and savings math. The id field is the coupon UUID, so it is not unique within a response. Clients keying by id should use the (id, product_id) tuple. Coupons with zero matched canonical products are suppressed entirely.
  • Geo filter has two arms. Store-scoped coupons (store_id set) match when their store is within radius. Chain-wide coupons (store_id null) match when any store in that chain is within radius. Both arms hit the GiST index on stores.location; the response shape is the same either way.
  • Default sort is valid_to ASC (soonest expiry first), then savings_pct DESC.
  • Geo calls (lat+lng+radius_mi) bump the limit ceiling to 200 to give clients a wider radius without paginating.
  • 304 short-circuits do not consume payment. Use the ETag aggressively on long-poll home screens.

Errors

NameTypeDescription
422Unprocessable EntityBad date format, lat/lng out of range, or limit out of range.
402Payment RequiredPaid route — no payment proof.
500Internal Server ErrorUnexpected server fault.