Using the APIIdempotency

Idempotency

Retry any POST safely. The Idempotency-Key header turns "did my request go through?" into a non-question.

How it works

Every POST /api/* request must carry an Idempotency-Key header. The first attempt is processed and its response stored. Any retry with the same key and the same request replays the stored response byte for byte, marked with an idempotency-replayed: true header. No side effects, no duplicates.

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 rules

SituationResult
Same key, same request, within 24 hoursStored 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 5xxNot stored. A retry executes fresh
Key older than 24 hoursForgotten. The request executes as new
Missing key on a POST400 missing_idempotency_key

Keys are global per environment, not per endpoint. Using Idempotency-Key: test on one endpoint and then on another fails loudly, 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. Always mint a unique key per request.

Idempotency below the header

The header protects your retries. The platform is idempotent underneath as well, so replays cannot create duplicates at any layer:

  • A replayed bank callback 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 can never double-post.
  • A wallet operation that is already executing replays its object rather than authorizing twice.

You do not need to do anything to benefit from these. They are listed so you can reason about failure modes with confidence.

The one exemption

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. Every other /api/* POST requires the header.