Using the APIIdempotency

Idempotency

How the Idempotency-Key header makes every POST safe to retry, and the rules for replays and key reuse.

How it works

Every POST /api/* request must carry an Idempotency-Key header. The first attempt is processed and its response stored. A retry with the same key and the same request replays the stored status code and body, marked with an idempotency-replayed: true header, and runs nothing a second time.

curl -s $BASE/api/customers \
  -H "Api-Key: $KEY" \
  -H "Idempotency-Key: 4f1f86f0-9e37-4c8e-9f2a-1f2b3c4d5e6f" \
  -H "Content-Type: application/json" \
  -d '{"kind": "individual", "first_name": "Ana", "last_name": "Silva",
       "tos_acceptance": {"ip_address": "203.0.113.7"}}'

Generate a fresh key, ideally a UUID, for every new request. Reuse a key only to retry that exact request: the same method, the same path and a byte-identical body.

The rules

SituationResult
Same key, same requestStored response replayed, with idempotency-replayed: true
Same key, different body or different endpoint422 idempotency_key_reused, and the message names where the key was first used
Same key, first attempt still running409 request_in_progress. Wait and retry
First attempt returned 4xxStored and replayed like any answer. Fix the request and send it with a new key
First attempt returned 5xxNot stored. A retry executes fresh
Key revoked or expired since the first attempt401 api_key_revoked or 401 api_key_expired, never the stored response
Missing key on a POST400 missing_idempotency_key

Stored responses are kept for 24 hours. After that the key is forgotten, and a request reusing it runs as new. Retry within 24 hours, and mint a new key for anything later.

Keys are global per partner and environment, not per endpoint. Using Idempotency-Key: test on one endpoint and then on another fails with a message like "This Idempotency-Key was already used for POST /api/tos_links". That is deliberate: it catches key-management bugs instead of silently replaying an unrelated response.

Idempotency below the header

The header protects your retries. The layers underneath are idempotent as well:

  • The same bank notification delivered twice cannot create a second deposit.
  • Re-uploading the same verification document is detected by content hash and skipped.
  • Ledger postings carry deterministic keys, so a retried internal job cannot double-post.
  • Authorizing a wallet operation that is already executing returns its current state rather than authorizing twice.

None of these needs anything from you. They are listed so you can reason about failure modes.

Hosted pages are exempt

The hosted terms acceptance page (/api/tos/accept) is exempt, because a browser form cannot send an Idempotency-Key. It is idempotent by construction instead: refreshing or double-clicking replays the same receipt. The hosted verification page (/api/verifications/collect) is exempt for the same reason. It is reserved for hosted collection, which is not available yet. Every other /api/* POST requires the header.