Idempotency

Replay-safe POST and PATCH with the Idempotency-Key header — what gets cached, what rejects, and what to use as the key.

Updated 2026-07-15

POST and PATCH endpoints accept an Idempotency-Key header. If the same key arrives twice within 24 hours, the second request returns the cached response instead of running the operation again.

How it works

POST /api/v1/organizations/{org}/forms/{form}/records
Idempotency-Key: a96b7e7f-9c4a-4e3d-9e6a-9c8b...   <- any unique string you generate
Content-Type: application/json

{ "created_by": "...", "field_values": { ... } }

If you retry the same request (network blip, redeploy mid-flight, queue re-drive), send the same Idempotency-Key. The second response will be:

  • Identical body to the first response
  • X-Idempotent-Replay: true header attached so you can detect it
  • Same status code (success only — see below)

What gets cached

  • Successful responses (2xx) only. A failed POST is NOT cached; retry with the same key triggers the real operation again, which is what you want — the first attempt didn't take effect.
  • The response body is cached verbatim.
  • Cache is scoped per-API-key. Two integrators can reuse the same opaque key string without colliding.

What rejects

If you send the same Idempotency-Key with a different request body, you get 409 conflict. The server hashes the body bytes — even whitespace differences trigger the conflict guard.

What to use as the key

  • Anything opaque, ≤ 255 characters.
  • A UUID is the canonical choice (uuidgen, crypto.randomUUID()).
  • Tie it to your internal job/request id so retries from your queue carry the same value.

When to use it

  • POSTs that create resources (records, report templates, webhook subscriptions, org members) — to avoid duplicate rows on retry.
  • POSTs to action endpoints that have observable side effects (publish, merge, retry) — to avoid double-firing.
  • PATCHes — usually safe to retry, but adding the header gives you a guaranteed-stable response body.

For pure GET reads, idempotency keys are ignored.

Lifetime

At least 24 hours from the first request. A periodic cleanup job clears expired keys, so a key can keep replaying for a few hours past the 24-hour mark before a fresh POST with the same value runs the operation again.