Public previewBetter every weekSee what changed →
All guides

Syncing the catalog

The products list returns one page of deals inside your inventory scope. Load the catalog into your own database once, then pull only what changed every few hours. Your site renders deals from your database, never live from Groupon. The full response shape, with every field, is in the API reference.

How to use the fields

  • Price: option.price.retail is the per-unit price the shopper pays. option.price.original is the strike-through price.
  • What to list: show a product only when status is active and availabilityRequired is false. Show an option only when its active is not false (null means unknown: treat it as sellable). sold_out and expired products cannot be bought.
  • Products with availabilityRequired: true need a time-slot booking and cannot be added to a cart. They are rare. Skip them.
  • Quantity limits: enforce minUnits and maxUnits from the option in your UI (null maximum means no maximum).
  • Promotion: option.promotion is a promotional price the shopper gets by entering promotion.promoCode on Groupon's checkout page before promotion.endsAt. It is informational: show it as "Use code SAVE15 at checkout." Cart totals always use retail. promotion can be null at any time.
  • Images: each entry in a product's media has a role that names a size (small, medium, large, extra_large, wide_crop). Every entry is the same picture at a different size, and not every size is present on every deal. Pick by role, for example large for a product page and medium for a grid, falling back to the entry with the largest width. width and height can be null.
  • A page can contain fewer products than limit, or even none, while hasMore is true. Keep paging until hasMore is false.

Keeping your catalog fresh

Deals keep changing: prices move, and deals are activated and deactivated. Load the catalog once, then pull only what changed every few hours using updatedSince. Store the time of your last refresh so the next pull knows where to start.

Step 1: initial full load, once

text
params = { country: "US", limit: 10 }               # no updatedSince
cursor = none
repeat:
    page = GET https://api-staging-core.livingsocial.com/partner_storefront/products?params (+ cursor if set)
    upsert every product in page.data by its id
    cursor = page.nextCursor
until page.hasMore == false
lastRefreshAt = page.timestamp - 10 minutes    # the final page's timestamp; persist ONLY after
                                                # the whole walk succeeds

Step 2: delta refresh, every few hours

text
params = { country: "US", limit: 10, updatedSince: lastRefreshAt }    # do NOT send active
cursor = none
repeat:
    page = GET https://api-staging-core.livingsocial.com/partner_storefront/products?params (+ cursor if set)
    upsert every product in page.data by its id    # replace options, prices, status
    cursor = page.nextCursor
until page.hasMore == false
lastRefreshAt = page.timestamp - 10 minutes    # the final page's timestamp; persist ONLY after
                                                # the whole walk succeeds

Store lastRefreshAt durably, as a database row. If a walk fails part-way, do not advance it: restart the walk from the previous lastRefreshAt.

The 10-minute overlap is intended. Upserts are idempotent, so re-reading a deal is harmless, and missing one is not.

Take the time from the final page's timestamp (Groupon's clock), not your own clock: an updatedSince in the future is rejected.

Do not send active=true on a delta. A deal that was deactivated since your last refresh comes back with a status other than active; you need that row to stop listing it.

After each upsert, recompute whether the product is listable (see "How to use the fields" above).

A delta can legitimately return the whole catalog. When a promotion starts, many prices change at once without touching each deal's own update time, so Groupon ignores updatedSince for that walk. The decision is made once, on the first page, and carried in the cursor, so a walk is never half incremental and half full. Your code must handle a delta of any size.

You can repeat the full load at any time, for example to rebuild your database.

Rules for every walk

  • Page sequentially. Do not request pages in parallel.
  • Send the same parameters on every page of a walk. Only cursor changes. Changing a filter mid-walk invalidates the cursor.
  • A cursor is valid for 24 hours.
  • If a page returns BAD_REQUEST with a cursor problem, discard the cursor and restart that walk from page 1.
  • On a retryable failure, retry the same page with backoff. See Handling errors.
  • The catalog in your database is a cache. The cart endpoints re-check price and availability live, so they are the final authority at add-to-cart time.

Example request

bash
curl -s "https://api-staging-core.livingsocial.com/partner_storefront/products?country=US&limit=10&active=true" \
  -H "Authorization: Bearer $GROUPON_API_KEY" \
  -H "User-Agent: MyStore/1.0 (+https://mystore.example)" \
  -H "x-request-id: $(uuidgen)"