Computed savings rating for one coupon. Use to surface a quality badge ('great deal', 'okay') without recomputing client-side.
/v1/coupons/{coupon_id}/savings$0.01| Name | Type | Description |
|---|---|---|
coupon_idrequired | uuid | Coupon id from /v1/coupons. |
curl 'https://api.mainmarket.com/v1/coupons/0bc5b1d4-9a25-4d70-8a82-2ab3f6f1d901/savings'{
"coupon_id": "0bc5b1d4-9a25-4d70-8a82-2ab3f6f1d901",
"savings_pct": 22.27,
"rating": "good",
"computable": true,
"reason": null
}{
"coupon_id": "5e1a2b3c-4d5e-6f70-8901-2a3b4c5d6e7f",
"savings_pct": null,
"rating": null,
"computable": false,
"reason": "missing_catalog_price"
}| Name | Type | Description |
|---|---|---|
coupon_id | uuid | Echo of the requested coupon id. |
savings_pct | number | null | 0.0–100.0. Null when computable=false. |
rating | string | null | One of great, good, okay, poor. Null when computable=false. |
computable | boolean | True when we have a baseline price to compute savings against. |
reason | string | null | Diagnostic when computable=false. Common values: missing_catalog_price, missing_discount_value, unknown_discount_type, expired. |
great ≥ 50%, good 20–50%, okay 10–20%, poor< 10%. Thresholds are deliberately conservative for grocery — anything above 50% off the regular price is rare outside of clearance and usually indicates either a clipper-only loyalty deal or an outlier that earned the "great" badge but should be sanity-checked before display.Every row from List coupons already carries savings_pct, rating, total_savings, and deal_price inline. So when do you call this dedicated route?
/v1/coupons row has savings_pct: null, this endpoint's reason field tells you why — useful for support tickets or QA dashboards.The savings math depends on discount_type on the parent coupon. The formula is the same one used to populate savings_pct on the inline /v1/coupons response — exposing it here so you can audit or reproduce client-side:
dollar_offsavings_pct = discount_value / catalog_price × 100. e.g. $1 off a $4.49 product = 22.27%.percent_offsavings_pct = discount_value directly (already a percentage).bogo (buy N, get M free)savings_pct = bogo_get_qty / (min_purchase_qty + bogo_get_qty) × 100. Buy 1 get 1 = 50%; buy 4 get 1 = 20%. Returns computable=false when both legs aren't priced.bundle / loyalty_pricesavings_pct = (catalog_price − bundle_unit_price) / catalog_price × 100. Requires both the bundle's effective unit price and the regular catalog price to be present.featuredcomputable=false always — featured coupons are a marketing surface in the chain's app, not an actual discount.import httpx
# Step 1: pull a list (savings inline). Use this most of the time.
list_resp = httpx.get(
"https://api.mainmarket.com/v1/coupons",
params={"chain_id": "12d6...", "limit": 50},
headers={"Authorization": "Bearer mm_live_..."},
).json()
# Step 2: only call /savings when you want a fresh, focused decoration
# (e.g. after a user clip event invalidates the cached rating)
def fetch_rating(coupon_id):
r = httpx.get(
f"https://api.mainmarket.com/v1/coupons/{coupon_id}/savings",
headers={"Authorization": "Bearer mm_live_..."},
).json()
if not r["computable"]:
return None, r.get("reason")
return r["rating"], None
for c in list_resp["results"][:10]:
rating, reason = fetch_rating(c["id"])
badge = {"great": "🔥", "good": "✓", "okay": "·", "poor": "·"}.get(rating, "?")
note = f" ({reason})" if reason else ""
print(f"{badge} {c['product_name']} {c['value_text']}{note}")/savings for every coupon in a 50-row list is 50 paid calls. The inline savings_pct on /v1/coupons is one paid call total. Only use the dedicated route for targeted refreshes (post-clip, post-edit, deep-link landing).rating to a color: great=green, good=blue, okay=neutral, poor=muted-gray. Don't show "poor" coupons in red — they're not bad, just low-savings.computable=false, hide the badge entirely rather than showing "?" — the absence of a rating is meaningful information for shoppers but rarely something they want to think about.missing_catalog_price is the most common reason coupons aren't computable. It usually clears within a week as the product gets scraped at the coupon's home chain — worth re-fetching periodically.catalog_price when available, otherwise against the live store_product_prices.regular_price at the coupon's home chain. The basis is exposed via total_savings_basis on the parent coupon row (one of catalog_price, store_price, or null).computable=false when only one leg has a baseline. The reason field will be missing_catalog_price in this case.savings_pct on every row. Start here unless you have a focused reason not to.| Name | Type | Description |
|---|---|---|
404 | Not Found | coupon_id does not exist. |
402 | Payment Required | Paid route — no payment proof. |