Skip to Content
Idempotency

Idempotency

Keel documents idempotency separately for the canonical permit flow and the execution routes.

At a glance

Route familyWhere idempotency goes
POST /v1/permitsJSON body idempotency_key
POST /v1/permits/{permit_id}/usageJSON body usage_idempotency_key
POST /v1/executionsIdempotency-Key header
POST /v1/executeIdempotency-Key header
POST /v1/proxy/*Idempotency-Key header
POST /v1/mcp/servers/{server_ref}/tools/{tool_name}:callJSON body idempotency_key or Idempotency-Key header

Permit idempotency

Route:

  • POST /v1/permits

Canonical field:

  • idempotency_key in the JSON body

Behavior:

  • scope is per project
  • same key plus the same semantic payload replays the prior permit response
  • same key plus a different semantic payload returns 409 conflict
  • if the client omits idempotency_key, Keel generates a server-side idempotency key for that permit
  • replay is still route-dependent: stale allow permits with inactive reservations can expire or revalidate instead of replaying the original allow body

Permit usage closeout idempotency

Route:

  • POST /v1/permits/{permit_id}/usage

Canonical field:

  • usage_idempotency_key in the JSON body

Behavior:

  • use the same key when retrying the same permit closeout
  • same key plus the same closeout values replays the existing closeout response
  • same key plus different closeout values returns 409 conflict
  • this is a narrower permit-first closeout contract, not proxy-strength execution replay binding

Execution idempotency

Routes:

  • POST /v1/executions
  • POST /v1/execute
  • POST /v1/proxy/openai
  • POST /v1/proxy/anthropic
  • POST /v1/proxy/google
  • POST /v1/proxy/xai
  • POST /v1/proxy/meta

Canonical field:

  • Idempotency-Key HTTP header

Behavior:

  • reuse the same header value when retrying the same execution request
  • if the header is omitted, the request still runs, but retries are treated as new executions
  • proxy routes bind replay to the same key and payload; the same key plus a different payload returns 409 conflict
  • a proxy stream replay requires complete original SSE bytes in the bounded response store; unavailable or incomplete stream bytes return 409 without another provider dispatch, rather than an empty successful stream
  • historical normalized-only stream caches cannot reconstruct the original SSE and are refused with 409. Do not switch to a new idempotency key to force a redispatch; reconcile the original outcome first.
  • execution routes use the same header-based retry pattern, but you should still treat the header as part of the public route contract rather than assume identical semantics across every route family
  • /v1/executions and /v1/execute also bind replay to a hash of the request; the same key plus different request content returns 409 with an execution envelope (error.code: "idempotency_conflict"); while the first request with that key is still running, a retry returns 503 idempotency_in_progress with Retry-After — retry with the same key

MCP tool-call idempotency

Route:

  • POST /v1/mcp/servers/{server_ref}/tools/{tool_name}:call

Canonical fields:

  • idempotency_key in the JSON body, or
  • Idempotency-Key HTTP header

Behavior:

  • the key is bound to the project, calling API-key actor, MCP server, tool, semantic request, workflow and session context, and whether streaming was requested
  • a non-stream retry may replay the stored result only after Keel performs a fresh authorization check for the retrying actor
  • the same key with different bound request content returns 409 conflict
  • after a complete streaming response has been stored, an exact retry replays the same event-stream bytes and original Permit reference without another upstream tool call
  • a concurrent or incomplete streaming claim returns 409; an outcome-unknown call requires reconciliation using its original arguments and key

Why the fields differ

Permit creation uses a body field. Execution routes use a header. Use the documented field for each route family.

Last updated on Edit this page on GitHub