Skip to main content
Send Idempotency-Key on any operation that lists it, and a retry becomes safe.
Idempotency is opt-in. A caller who sends no key gets a fresh execution every time. Use a value you generate per logical operation — a UUID is fine. Reuse it across retries of that operation and never across two different ones.

Three rules

The key is looked up after the agent has been resolved and the tenant asserted, and the record is keyed on the identity that just passed those checks.The opposite ordering has a real failure mode: replaying a stored payload before any permission check runs means a forbidden caller receives the permitted caller’s result. That bug existed once on this platform and was fixed; this ordering is why it cannot come back.
Byte for byte the stored response, including its HTTP status.A retry that produces a different answer is not idempotent even when both answers are correct.
Never a replay. Returning the earlier response for a request that does not match it is what turns a client-side key collision into a silently wrong answer.

What counts as “the same request”

A stable hash of the agent, the input and the options — canonical JSON with sorted keys and no whitespace. Two semantically identical bodies that differ only in key order are one request, not two.

A replay may answer 200 where the first call answered 201 or 202

The stored status is replayed as it was recorded, so this is expected:
The second call created nothing, and saying otherwise would be a lie a client acts on.

Telling a replay from a fresh execution

The header is not spelled the same on every endpoint. Read the operation you are calling rather than assuming one name across the surface.A client checking for the wrong header reads every replay as a fresh execution — which for a create is the one conclusion idempotency exists to prevent.
The safe client check is: look for either header, and treat a 200 on POST /documents as a replay.
This is a known inconsistency, stated in the generated reference on each operation rather than smoothed over. The generated document reads the header out of each handler, so the name on an operation is the name that endpoint actually sets.

The window

A key stays replayable for 24 hours. Long enough to cover a client’s retry budget and a queue’s redelivery; short enough that the idempotency table is not a second, longer-lived copy of your responses. An unfinished claim may be taken over after 15 minutes. A worker that died mid-run would otherwise leave a record that 409s the retry forever — which is the opposite of what an idempotency key is for.

Keys are compartmented, never global

A key is scoped by endpoint, environment and caller identity — never by workspace alone. So two end users of one embedded application, both sending Idempotency-Key: 1, cannot meet. Nor can the same key on POST /responses and POST /documents. The identity part is the end-user principal when a delegated token named one, and otherwise the credential prefix. Nothing in the compartment would be a secret if it appeared in a log line.

Where to send it

The header is Idempotency-Key on every endpoint that supports it. POST /jobs additionally accepts idempotency_key in the body, because a caller using a generated client that cannot set headers is not a caller who should have to give up idempotency. idempotency_key is refused as a runtime option with a 400 telling you to send the header instead.

Longer-lived deduplication

For conversations there is a better tool than an idempotency key. external_ref is your own id for a thread: resolving the same one twice returns the same conversation rather than a second one beside it, and it has no 24-hour window. See Conversations.

Which operations support it

Every POST that creates something: /responses, /conversations, /conversations/{id}/messages, /agents/{id}/runs, /documents, /extractions, /reports, /knowledge/ingestions, /jobs. Each operation in the generated reference lists Idempotency-Key explicitly when it reads one.
Cancellation endpoints need no key. POST /responses/{id}/cancel and POST /jobs/{id}/cancel are idempotent by construction: doing either twice is indistinguishable from doing it once.