Idempotency-Key for Agent Completions: reuse the model's tool call id

Retries after a timeout must not start a second paid Sume run. Derive Idempotency-Key from the tool call id your model returned, such as the OpenAI call_id.

5 min readSume
All posts

Use one Idempotency-Key per intended run, and derive it from an identifier the model already gave you, such as the function call's call_id in the OpenAI Responses API. If your server retries after a timeout, Sume returns the original receipt with idempotency_hit: true instead of starting and billing a second run. Reuse the same key with a different payload and you get 409 idempotency_conflict.

What each side guarantees

The Agent Completions page says Idempotency-Key behaves as it does on Action runs: a replay returns the original receipt flagged idempotency_hit: true, and a different payload under the same key returns 409 idempotency_conflict. On the OpenAI side, the function calling guide describes the model returning function_call items that carry a call_id, which you answer with a function_call_output item. That id is stable for the call, which makes it a natural key.

Choosing the key

Key sources and their risks (read 2026-10-04)
Key sourceRetry-safe?Risk
Model tool call_idYes, same call same idNone if payload is unchanged
Random UUID per attemptNoEvery retry is a new paid run
Hash of the instruction textMostlyTwo intended runs with identical text collapse into one
TimestampNoDiffers on every retry

Handling the two replies

Treat idempotency_hit: true as success and carry on with the run id you were given; do not start a fresh run. Treat 409 idempotency_conflict as a bug in your code, not a transient error: the same call id is paired with a changed payload, usually because your handler rewrote the cap or the instruction between attempts. Fix the handler so the payload is built once per call id, then retried unchanged.

Return the run id to the model as the tool output, with a note that the work is asynchronous, so the model polls rather than starting another.

  • Build the request body once, store it with the key, and retry that exact body.
  • Clamp the cap before computing the body, not after.
  • Log key and run id together.
  • Keep one key per intended run, never one per attempt.

Beyond create

The documented read and cancel calls are sent without a key; only the create call starts a run. If you also fire runs from a schedule or a Format, check how those routes treat keys on their own pages; the same name does not guarantee the same window. See Safe automation for the general guardrails.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume