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 document the strongest replay contract: same key plus the same payload replays the cached response, and same key plus a different payload returns 409 conflict
  • 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 do not currently document the same proxy-strength request-hash replay binding as the proxy routes

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
  • a streaming response is not replayed as a cached event stream; a duplicate streaming claim returns 409 without a second upstream tool effect

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