Skip to main content
Network calls fail in the middle. Idempotency keys let you retry a POST without worrying about creating the same resource twice.

How it works

Send Idempotency-Key: <opaque-string> on any POST request. Awardee caches the response keyed by (api_key, method, path, idempotency_key) for 24 hours.
  • Same key, same body within the window → the cached response is replayed verbatim. The response carries Idempotent-Replayed: true.
  • Same key, different body → 409 idempotency_conflict. This catches client bugs that reuse a key for unrelated requests.
  • New key → the request runs normally and the result is cached.
GET, PATCH, PUT, and DELETE are naturally idempotent on the server side, so the header is ignored on those methods. Only POST honors it.

When to use it

  • Always, for any POST you might retry — creating chatbots, articles, conversations messages, submissions, etc. The cost of including the header is one UUID per request.
  • Especially for POSTs triggered by user actions (form submits, button clicks), where a network blip otherwise creates duplicates.
  • Especially inside webhook handlers and queue workers, where retries are normal.

Choosing a key

Use a fresh UUIDv4 per logical operation. Generate it on the client and reuse it across retries of the same request.
Persist the idempotency key alongside the work it represents (in your job queue row, your outbox table, the user’s form-submission record). On retry, look it up and reuse it. Generating a fresh UUID per retry defeats the purpose.

Deterministic keys and deleted resources

If you derive keys from content — a source-document hash plus a slug, say — rather than a fresh UUID, the same input produces the same key on every run. That is what you want when retrying a run, but it has a sharp edge: deleting a resource does not invalidate the cached response that created it. Delete a batch of articles and re-import the same source within the 24-hour window and you will get one of two results, neither of them a new resource:
  • Same body → the original 201 is replayed, carrying IDs of records that no longer exist.
  • Changed body (a new parent ID, a reordered field) → 409 idempotency_conflict.
Put a run or version segment in the key and bump it whenever you intend to genuinely re-create resources:
Outside the 24-hour window the key is fresh again and no bump is needed.

Detecting a replay

A replayed response is byte-identical to the original, including the original status code. The only differences are response headers:
The X-Request-Id reflects the replay request, not the original. Log both — the cached request_id inside the JSON body (if your original response had one) ties back to the first attempt.

Conflicts

Reusing a key with a different body within 24 hours returns:
Either generate a new key, or send the same body. If you are re-importing content you previously deleted, see Deterministic keys and deleted resources — the key is still holding the response from the original import.

Window expiration

Cached responses live for 24 hours, sliding from the original request’s completion time. After expiration the key is fresh — sending it again with the same body issues a new request and creates a new cache entry.