API
Redemptions
One endpoint, two ways to read it: everything in order, or a range with filters.
Endpoints
/redemptionsAs a feed (after, limit) or by range (preset or from/through, plus filters)./redemptions/{id}One redemption.Keeping a complete copy
With no parameters, the feed starts at the beginning and walks every redemption in the order Viffy recorded it. Save the cursor between runs.
cursor = load_saved_cursor() // none on the first run
loop:
page = GET /redemptions?limit=1000&after=cursor // omit after on the first run
store_and_commit(page.items) // upsert by id before checkpointing
if page.nextCursor is null: break // the last page; caught up
cursor = page.nextCursor
save_cursor(cursor)- Exclusions and restorations update the same redemption, but its recordedAt and feed position do not change. Forward polling alone misses corrections to rows before your cursor.
- Subscribe to redemption.excluded and redemption.restored, or read those changes from the event log. Fetch
GET /redemptions/{id}after a correction so out-of-order events cannot overwrite newer state. - Order is by
recordedAt, when Viffy learned of the redemption.
Reconcile corrections
Keep the last non-null cursor; if the feed has never returned one, start again without after. Event-log checkpoints are separate from redemption-feed checkpoints.
Reconcile your copy with creator counts before crediting creators. The event log retains 30 days; after a longer interruption, re-read affected redemptions or the full feed to recover current exclusions and restorations.
Excluding and restoring redemptions are actions for your team in Viffy. The partner API reads those changes but has no write endpoint for them.
Reading a range
Range mode filters by redeemedAt, the sale time, and lists the newest sales first using your reporting time zone. Send any parameter below to use range mode.
| Parameter | What it is |
|---|---|
preset / from / through | Choose a preset or a first and last day. |
brandId / campaignId / countingState | Filter by brand, campaign, or counting status. |
page / pageSize / asOf | Choose a page and keep the same asOf value on later pages. |
Do not combine these with the feed parameters after or limit.
GET /redemptions?preset=7d&countingState=counted&page=0&pageSize=200{
"range": {
"from": "2026-09-05",
"through": "2026-09-11",
"timeZone": "America/Chicago",
"preset": "7d"
},
"asOf": "2026-09-11T15:04:05Z",
"items": [
{
"id": "e3a954b1-820c-49d7-a056-cc7f2d9e613a",
"serializedGs1": "8112000000000000000000000000",
"redeemedAt": "2026-09-11T14:59:12Z",
"recordedAt": "2026-09-11T15:04:05Z",
"campaign": {
"id": "7c1e29a4-5b68-4d02-91f3-8a4c6e0b752d",
"externalReference": "fall-launch-2026",
"name": "Fall launch"
},
"offer": {
"baseGs1": "8112000000000000000",
"title": "$1 off any two"
},
"brand": {
"id": "9a2b835d-674e-48ab-b012-a93b6407c2e8",
"name": "Acme Foods"
},
"creator": {
"externalId": "hb-1042",
"name": "Jane Doe"
},
"retailer": null,
"countingState": "counted",
"exclusion": null
}
],
"page": 0,
"pageSize": 200,
"totalCount": 1
}Reuse the returned asOf on later pages to leave out redemptions whose recordedAt is later than that time. Later corrections and name changes can still appear; restart if a correction affects your filters.
Range mode accepts up to 366 calendar days, including the first and last day.
A redemption
{
"id": "e3a954b1-820c-49d7-a056-cc7f2d9e613a",
"serializedGs1": "8112000000000000000000000000",
"redeemedAt": "2026-09-11T14:59:12Z",
"recordedAt": "2026-09-11T15:04:05Z",
"campaign": {
"id": "7c1e29a4-5b68-4d02-91f3-8a4c6e0b752d",
"externalReference": "fall-launch-2026",
"name": "Fall launch"
},
"offer": {
"baseGs1": "8112000000000000000",
"title": "$1 off any two"
},
"brand": {
"id": "9a2b835d-674e-48ab-b012-a93b6407c2e8",
"name": "Acme Foods"
},
"creator": {
"externalId": "hb-1042",
"name": "Jane Doe"
},
"retailer": null,
"countingState": "counted",
"exclusion": null
}| Field | Type | Meaning |
|---|---|---|
id | string (uuid) | Redemption ID; distinct from the envelope event ID. |
serializedGs1 | string | The redeemed code. Keep it as text to preserve every digit. |
redeemedAt | string (date-time) | When the redemption happened. |
recordedAt | string (date-time) | When Viffy first recorded the redemption; the redemption feed sorts by this time. |
campaign | CampaignReference | Campaign reference; fields are defined below. |
offer | OfferReference | Offer identifier and title. |
brand | BrandReference | Brand identifier and name. |
creator | CreatorReference | null | Attributed creator; null only when the code reached no shopper. |
retailer | RetailerReference | null | Campaign retailer restriction, or null when unrestricted. |
Nested campaign, offer, brand, creator, and retailer fields are defined in Shared objects.
| Field | What it is |
|---|---|
countingState | counted, excluded, or test_code. Only counted rows contribute to verified totals. Viffy does not issue payments; use creator-counts to reconcile creator credit. |
exclusion | Object { excludedAt: UTC timestamp, reason: string or null }, or null when not excluded. excluded takes precedence over test_code as a countingState; an excluded test-code redemption has this exclusion object. |
creator | The creator whose link handed out the code, fixed at that moment. Null only for a code that reached no shopper. |
retailer | The campaign’s retailer restriction, when set. It does not identify the store where the purchase happened. |
serializedGs1 | The redeemed code. The partner API does not return unredeemed codes. |