Idempotency
When your API call hits a transient network error mid-flight, you don't know whether the request reached us. Retrying could double-charge a user, double-warn a member, or create two cases instead of one.
Idempotency keys make writes safe to retry. Send the same key with the same body twice and you'll get the same response twice — at most one server-side mutation happens.
When to use them
Send Idempotency-Key on every POST, PATCH, PUT, DELETE. We don't enforce this — the header is optional — but you'd be surprised how often clients need it.
GET and HEAD are naturally idempotent and ignore the header.
How it works
POST /api/v1/economy/adjust HTTP/1.1
Authorization: Bearer phk_live_…
Idempotency-Key: 7c52a3f0-3b8c-4e3d-aa1c-c8a7f4d4c121
Content-Type: application/json
{ "user_id": "123", "delta": 500, "reason": "Tournament prize" }- First time we see this key: the handler runs, the response is cached for 24 hours, and we return it normally.
- Subsequent calls with the same key + same body: we return the cached response without running the handler. Look for
Idempotent-Replay: truein the response headers. - Same key, different body: we return
409 idempotency_key_reused. This catches bugs in your client — you almost certainly meant to use a new key.
Choosing a key
Use UUID v4 unless you have a strong reason not to:
import uuid
headers["Idempotency-Key"] = str(uuid.uuid4())If you're driving from an external event that already has a stable id (a Stripe charge, a queue message), use that as the key. That way the natural retry semantics of the source system give you idempotency for free.
headers["Idempotency-Key"] = f"stripe-charge-{charge.id}"Constraints
- Max key length: 128 characters.
- Keys are scoped to (token, key) — your key cannot collide with another customer's, but it can collide with another key under the same token if you reuse it.
- TTL: 24 hours. After that, the same key is re-runnable.
- Bodies are hashed for comparison — same
method + path + body= same fingerprint.
What gets cached
| Response status | Cached? |
|---|---|
| 2xx (success) | Yes |
| 4xx (client error) | Yes — so a buggy client doesn't get a different validation error on replay |
| 5xx (server error) | No — we assume the issue may have resolved and you should be able to retry |
Common mistakes
Reusing a key for "the same logical operation" with a different body. Don't. Even if the intent is the same, the request fingerprint is body-based. Pick a new key.
Generating a key client-side per retry attempt. That defeats the purpose. The key should be stable across retries of the same request, and only differ between different requests.
Using a sequence number from your DB. Fine if you've truly never reused it, but if your DB ever resets, you'll get a 409 on a "first" call. UUIDs avoid this.
