Skip to content
Browse the docs

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/json on writes.
  • Timestamps are ISO 8601 in UTC, e.g. 2026-08-28T15:04:05Z. Calendar days are YYYY-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.1 is 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.

400 response
{
  "error": {
    "code": "validation_failed",
    "message": "This campaign couldn't be saved.",
    "details": {
      "dates.through": ["A campaign cannot end before it starts."]
    }
  }
}
StatusCodeMeaning
400validation_failedSomething in the request is wrong. See details.
401unauthenticatedMissing, invalid, or revoked key, or its organization has been deactivated.
403forbiddenA read-only key on a write, or an organization the key cannot act for.
404not_foundThe resource was not found. A resource belonging to another organization also returns 404.
409conflictThe change conflicts with the current state, such as an ended campaign or a reference already in use.
429rate_limitedToo many requests. Wait for Retry-After.
502clearing_network_unavailableThe coupon network did not answer. Retry shortly.
503not_configuredThis part of Viffy is not set up in this environment.
500unexpected_errorUnexpected 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.

Response
{ "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).

Response
{ "items": [], "nextCursor": null }
  • Process every page, including the one whose nextCursor is 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 after if 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.

ParameterWhat it is
presetOne of today, 7d, 30d, 90d, this-month, last-month. Default 30d.
fromFirst day, YYYY-MM-DD.
throughLast 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.

WriteSafe recovery
Create a campaignSend a unique externalReference. If a retry returns 409, list campaigns by that reference. Without it, retrying can create a duplicate.
Add or update a creatorRepeat PUT with the same externalId and intended fields; unchanged values create no duplicate creator. Inspect every row after a batch.
Create a share linkRepeat 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 codeLimitSend the desired status or absolute total. Repeating a completed change leaves that value unchanged.
Queue creator linksRead 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 webhookThese 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 externalReference of your own, unique in your organization, and find it again with GET /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.

Build for additions: unknown fields and values should pass through your code, not break it.