Skip to content

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

http
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: true in 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:

python
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.

python
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 statusCached?
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.

Phantom is a product of Hydra Labs. The bot is run as a managed service; you do not need to host it yourself.