Skip to main content
Networks fail. A timeout after you sent POST /consumer/v1/redemptions does not tell you whether the voucher was redeemed. The Idempotency-Key header makes such a retry safe: the platform runs the request once and answers every retry with the stored response.

When to use idempotency keys

  • Every POST of every surface accepts the optional Idempotency-Key header: creates, commands such as :void and :revoke, and batches.
  • Send one on every POST that has an effect you must not repeat. For redemptions it is essential: without a key, a retry after a timeout can redeem a second voucher.
  • Without a key the request is processed normally, every time.
  • GET, PUT, PATCH and DELETE ignore the header; the response then carries Idempotency-Key-Ignored: true. PUT, PATCH and DELETE are naturally repeatable; see Concurrent changes.

Choosing a key

  • A key is 1 to 255 printable ASCII characters (! to ~), without spaces. An empty key, a longer one, or one with a space or a non-ASCII character is answered with 400 invalid-idempotency-key.
  • Use a UUID (v4 or v7) or a deterministic business key such as order-8812:line-1.
  • Use one key per intent (one customer action, one order line) and reuse it for every retry of that intent. Never reuse a key for a different request.
  • Never put personal data into a key: keys are stored and can be read back with the redemption.

How replays work

The platform fingerprints each request: method, route, path parameters, query parameters and the canonical JSON body. Key order and whitespace in the body do not matter; numeric literals do (1.0 and 1 are different bodies), so resend identical values. Stored outcomes. Successful responses (2xx) and final client errors are stored and replayed: 400 (malformed-json, invalid-header, data-failed-validation), 404, domain 409 conflicts, 410 and 422. A stored domain 409 such as voucher-set-depleted is final for that key: a retry with the same key replays the 409 even after new stock arrived. A new attempt needs a new key. Not stored (a retry with the same key is safe and runs again): 409 concurrent-update, 409 idempotency-key-in-use, 422 idempotency-key-reused, every 5xx, timeouts and network failures. 401 and 403 are decided before the key is claimed and are never stored. Show-once values. Responses that contain a secret shown only once (API key create and roll, webhook endpoint create and secret rotation, the SEPA setup clientSecret) are stored with that secret set to null. A replay returns null there and carries Idempotent-Replay-Redacted: true. If you lost the original response, revoke the key or rotate the secret and create a new one. Issued vouchers replay in full. A redemption’s issued.code and issued.link are not show-once: a replay of POST /consumer/v1/redemptions or :batch returns the identical body, issued values included. Asynchronous jobs. A replayed 202 shows the job as it was when it was accepted. Follow Location for its current state. Keys are kept for 24 hours. After that the same key executes again, which is another reason never to reuse keys across intents.

Key scope and key rotation

Keys are scoped to the caller:
  • For API keys, the scope is the API key’s roll chain. A key created with :roll shares the scope of the key it replaces, so retrying an intent through the rolled key with the same Idempotency-Key replays: there is no second effect.
  • An independently created API key has its own scope. Retrying an intent through it with the same Idempotency-Key is not a replay and executes again; for a redemption that means a second voucher.
  • A dashboard user’s keys are scoped to that user and partner.
Rule for integrations: rotate API keys with :roll, never by creating a fresh key and switching over in the middle of an intent. If you did switch to an independent key, or lost track of an intent, reconcile before you retry (see below).

Errors

Retry strategy

Retry with the same key on network errors, timeouts, 409 idempotency-key-in-use, 409 concurrent-update and 5xx, with exponential backoff and jitter, honouring Retry-After. Do not retry other 4xx responses without changing the request; when you change it, use a new key.
Persist the key together with the intent (for example on your order line) before the first attempt, so a retry after a crash of your own service uses the same key.

Where to use it in the Consumer API

Send a key on every: Reconciling a redemption intent. Every redemption stores the Idempotency-Key it was created with (a batch stores its key on each of its redemptions). If you are unsure whether an intent ran, for example after moving to an independently created API key, look it up:
  • A non-empty page means the intent already ran: use those redemptions and do not redeem again.
  • An empty page means it did not run: retry with the same key.
A 409 price-acceptance-required or 409 voucher-set-depleted is stored for its key. After you accepted the price or new stock arrived, redeem again with a new key.