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 coupon

Fetch one coupon by id, expanded into all matching (coupon, product) rows. Used to refresh a single deal after a clip event, deep-link a shareable coupon URL, or hydrate the body of a coupon detail screen.

GET/v1/coupons/{coupon_id}$0.01
ℹ
Same row shape as List coupons
Each result uses the standard CouponRow shape — see List coupons for the full field reference. This page focuses on what's different about the single-id workflow.

When to use this vs List coupons

Refresh after a clip
After a user clips a coupon in your UI, re-fetch the single coupon to refresh savings_pct, deal_price, and is_clippable state without paying for an entire list refresh.
Deep links
A shareable URL like /coupons/0bc5... in your app should hydrate from this endpoint, not from the cached list — the list might be stale or the deep link might predate the cache.
Multi-UPC coupon detail screen
When a coupon applies to multiple products (e.g. "$1 off any cereal in this brand family"), this endpoint returns one row per matched product so you can render a list of "this deal applies to:" with per-product savings.

Path parameters

NameTypeDescription
coupon_idrequireduuidCoupon id from /v1/coupons. Stable forever — won't change on the next ingestion run.

Request

Request
curl 'https://api.mainmarket.com/v1/coupons/0bc5b1d4-9a25-4d70-8a82-2ab3f6f1d901'

Response

200 OKjson
{
  "count": 2,
  "results": [
    {
      "id": "0bc5b1d4-9a25-4d70-8a82-2ab3f6f1d901",
      "chain_id": "12d6...",
      "chain_name": "HEB",
      "product_id": "9d4e...",
      "product_name": "Cheerios Cereal, 12 oz",
      "discount_type": "dollar_off",
      "discount_value": 1.50,
      "min_purchase_qty": 2,
      "valid_from": "2026-04-29",
      "valid_to": "2026-05-13",
      "savings_pct": 13.65,
      "deal_price": 4.74,
      "role": "buy"
    },
    {
      "id": "0bc5b1d4-9a25-4d70-8a82-2ab3f6f1d901",
      "product_id": "8a1f...",
      "product_name": "Cheerios Cereal, 18 oz",
      "discount_type": "dollar_off",
      "discount_value": 1.50,
      "min_purchase_qty": 2,
      "savings_pct": 11.91,
      "deal_price": 5.49,
      "role": "buy"
    }
  ]
}

Multi-UPC and BOGO expansion

The response wrapper { count, results } lets one coupon expand into many rows. Two cases produce more than one row:

Multi-product coupons
A coupon that targets a brand family (e.g. "$1.50 off any Cheerios cereal") returns one row per matched canonical UPC. Each row carries the same id, chain_id, and discount metadata, but a different product_id + per-product savings math.
BOGO buy/get legs
BOGO coupons return both the buy and get legs as separate rows, each with role: "buy" or role: "get". Pair them in your UI by coupon id — the buy row shows the qualifying product, the get row shows the freebie.
⚠
The id field is not unique within a response
Multiple rows can share the same coupon UUID. Clients keying by id must use the (id, product_id) tuple instead. Coupons with zero matched canonical products are suppressed entirely — you'll never see an empty results array for a real coupon id (you'd get a 404 instead).

Workflow: render a coupon detail screen

Request
import httpx

coupon_id = "0bc5b1d4-9a25-4d70-8a82-2ab3f6f1d901"
r = httpx.get(
    f"https://api.mainmarket.com/v1/coupons/{coupon_id}",
    headers={"Authorization": "Bearer mm_live_..."},
).json()

if not r["results"]:
    raise ValueError("Coupon has no matched products")

# Header — pull from any row (coupon-level fields are identical)
header = r["results"][0]
print(f"{header['chain_name']}: {header['description']}")
print(f"Valid {header['valid_from']} - {header['valid_to']}")

# Per-product breakdown
print("\nApplies to:")
for row in r["results"]:
    role = f" [{row['role']}]" if row.get("role") and row["role"] != "buy" else ""
    print(f"  • {row['product_name']}  ${row['deal_price']:.2f} ({row['savings_pct']:.0f}% off){role}")

Notable behavior

  • No filters. Unlike List coupons, this endpoint takes no query parameters — the coupon id alone is enough. If you need to filter (e.g. "the part of this coupon that applies to UPC X"), filter the results array client-side.
  • No ETag short-circuit. Single-id reads are uncommon enough that caching wasn't wired. If you're polling for clip-state changes, throttle client-side rather than relying on 304s.
  • Coupon-level fields are repeated. Fields like chain_name, valid_to, description are identical across all rows of a multi-row response. Read them from results[0] and don't iterate.
  • Tier-aware? No. Unlike /v1/prices, coupons aren't fallback-resolved across stores — a coupon either applies at its chain_id (or store_id if store-scoped) or it doesn't.

Related

  • List coupons — multi-coupon discovery with chain / store / product / brand / category / geo filters.
  • Coupon savings — focused savings + rating endpoint for one coupon. Faster than this endpoint when you only need the badge.
  • Coupons for product — flip the query: every coupon attached to one product instead of every product attached to one coupon.

Errors

NameTypeDescription
404Not Foundcoupon_id does not exist.
402Payment RequiredPaid route — no payment proof.