API
Conventions
The rules every endpoint follows, so you learn them once.
Format
- JSON in and out, with camelCase field names. Send
Content-Type: application/jsonon writes. - Timestamps are ISO 8601 in UTC, e.g.
2026-08-28T15:04:05Z. Calendar days areYYYY-MM-DD. - Money is a decimal in dollars, except
savings.cents, which is an integer in cents. Estimates retain up to four decimal places. Rates are fractions:0.1is 10%. - Allow unfamiliar fields and enum values. New ones may appear.
Errors
Every failing response has one shape. message says what went wrong and what to do. details, when present, names the fields at fault by the names you sent.
{
"error": {
"code": "validation_failed",
"message": "This campaign couldn't be saved.",
"details": {
"dates.through": ["A campaign cannot end before it starts."]
}
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | validation_failed | Something in the request is wrong. See details. |
| 401 | unauthenticated | Missing, invalid, or revoked key, or its organization has been deactivated. |
| 403 | forbidden | A read-only key on a write, or an organization the key cannot act for. |
| 404 | not_found | The resource was not found. A resource belonging to another organization also returns 404. |
| 409 | conflict | The change conflicts with the current state, such as an ended campaign or a reference already in use. |
| 429 | rate_limited | Too many requests. Wait for Retry-After. |
| 502 | clearing_network_unavailable | The coupon network did not answer. Retry shortly. |
| 503 | not_configured | This part of Viffy is not set up in this environment. |
| 500 | unexpected_error | Unexpected failure. The outcome of a write may be uncertain; read the resource before retrying. |
Pages
Offer, campaign, creator, and share-link listings take page (from 0) and pageSize (default 50). Values above 200 become 200; zero or negative values use 50. Negative pages become 0; totalCount counts all matching items. Reports state where they use the same wrapper; webhook lists and event types are unpaged.
{ "items": [], "page": 0, "pageSize": 50, "totalCount": 0 }Feeds
Redemptions and events can also be read as a feed, in the order Viffy recorded them. Pass after with the last cursor you were given and limit (default 100, at most 1,000).
{ "items": [], "nextCursor": null }- Process every page, including the one whose
nextCursoris null. That is the last page currently available. - After processing a page, save its non-null cursor. Reuse the last saved cursor on the next run, or omit
afterif you have none. - The final page repeats on the next run. Add or update resources by ID and skip event IDs you have already handled.
- Cursors are opaque. Use the returned nextCursor, not an item id. Keep separate checkpoints per feed, organization, and event-type filter.
Date ranges
Choose a preset or send both a first and last day. Do not combine a preset with explicit dates.
Reports group activity by day using your organization’s reporting time zone from GET /me. Each response includes the date range it used.
| Parameter | What it is |
|---|---|
preset | One of today, 7d, 30d, 90d, this-month, last-month. Default 30d. |
from | First day, YYYY-MM-DD. |
through | Last day, inclusive. Summary reports, campaign reports, and redemption range reads accept at most 366 calendar days including both endpoints. Creator counts and CSV exports have no 366-day cap. |
Retrying writes
A timeout or server failure does not tell you whether a write committed. Check the result before retrying; there is no general Idempotency-Key header.
| Write | Safe recovery |
|---|---|
| Create a campaign | Send a unique externalReference. If a retry returns 409, list campaigns by that reference. Without it, retrying can create a duplicate. |
| Add or update a creator | Repeat PUT with the same externalId and intended fields; unchanged values create no duplicate creator. Inspect every row after a batch. |
| Create a share link | Repeat for the same campaign and creator while the campaign has not ended and the creator is active. An existing link returns 200 without reactivating it. |
| Change status or codeLimit | Send the desired status or absolute total. Repeating a completed change leaves that value unchanged. |
| Queue creator links | Read the job from the 202 response before retrying. A retry can queue another job, return 409 if a job is in progress or the campaign ended, or return 400 if no new links can be assigned. |
| Register, rotate, or test a webhook | These calls create a new endpoint, secret, or sample each time. Inspect the endpoint and deliveries before retrying; a lost secret cannot be read again. |
A forward redemption cursor finds newly recorded redemptions; it does not capture later corrections to older ones. Follow the correction and reconciliation steps.
Identifiers
- Use your own Creator ID,
externalId, to identify creators. - Campaigns have a Viffy id. Give each one an
externalReferenceof your own, unique in your organization, and find it again withGET /campaigns?externalReference=. - Redemptions, webhooks, events, and link jobs use IDs assigned by Viffy.
Versioning
/v1 is in the path. Within v1, fields, enum values and endpoints may be added; nothing is removed, renamed or re-typed, and no default changes meaning. A breaking change would be /v2, run beside v1.