Skip to main content

Idempotency and Retries

For supported API-key writes, send one unique Idempotency-Key per logical write. Retry the same request with the same key after a connection failure. The key is scoped to the authenticating API key, not just to an endpoint or agent. External Security Halt has a separate service-identity/event retry contract.

Supported operations​

General service credentials have a separate, identity-scoped namespace and support the exact service mutation matrix in that guide. Rotation preserves their namespace. Expiry, disable/revoke, scope removal, resource-grant loss or ownership change denies replay; all involved agents are rechecked under the authority transaction. The original member-key namespaces and 24-hour lifetime remain unchanged; gated additional operations are listed below. Service receipts retain encrypted successful results for the same default 24-hour window and do not replay issuance tokens. Local deduplication does not guarantee exactly-once external actions.

Paths below are relative to /api/v1.

Method and routeReplay supported
POST /agents/Yes
POST /agents/browser-use/Yes; personal keys only
POST /agents/{id}/messages/Yes
POST /agents/{id}/contacts/Yes
POST /agents/{id}/webhooks/Yes
POST /agents/{id}/inbound-webhooks/Yes
POST /agents/{id}/peer-links/Yes
PATCH /agents/{id}/peer-links/{linkId}/Yes
DELETE /agents/{id}/peer-links/{linkId}/Yes
MCP create_agent, send_agent_messageYes, using the HTTP header on tools/call
POST /security/interventionsNo header-based replay; uses the service identity and body external_event_id as described below
POST /tasks/browser-use/, POST /agents/browser-use/{agentId}/tasks/Gated; asynchronous creation only, omit wait
POST /webhooks/inbound/agents/{webhookId}/?t=…Gated; webhook-ID namespace, seven-day default
POST /agents/{id}/pending-actions/{actionId}/resolve/Gated; current human/type-specific authority required
POST /agents/{id}/webhooks/{webhookId}/rotate-secret/, POST /agents/{id}/inbound-webhooks/{webhookId}/rotate-secret/Gated; superseded rotations conflict
Other MCP tools and other REST writesNo header-based replay

Some other operations have repeatable state effects, such as stopping an idle agent or restoring an already-restored agent. That does not mean they store and replay the first HTTP response. Deleting a peer link without a replay key returns 404 on a subsequent deletion.

Request identity and retention​

The header must be nonblank after trimming, at most 255 characters, and contain no control characters. Keep the HTTP method, path, operation, and JSON body unchanged. Object-key ordering does not matter; array ordering and values do. For member/service writes, query strings are not part of the request hash; creation options belong in the body. Protected inbound webhooks include meaningful query parameters and exclude only the authentication parameter t.

The default retention is 24 hours, configurable by your installation operator with API_IDEMPOTENCY_RETENTION_HOURS. Once a record expires, its key may create new work. Do not depend on replay after the retention window. A new API key also has a different replay namespace.

Message subjects are part of request identity: changing a subject under the same key conflicts. Identical authorized retries return the original encrypted acceptance snapshot, including pre-fix missing email subjects or formerly accepted non-email subjects. Current recipient ownership, sender allowance and retained-message authority are still checked. Neither a deployment nor a later history repair silently adds content to an old replay response.

When replay protection is used, the first completed response includes:

Idempotency-Replayed: false

A replay returns the original status and body with Idempotency-Replayed: true. Location and Retry-After are preserved when present. Response bodies, including secret-bearing webhook responses, are encrypted at rest. Protect replayed responses as carefully as the original.

Reusing the key with a different method, path, or payload returns 409. Do not generate a fresh key merely to get past an uncertain timeout: doing so can duplicate the write.

Responses returned by an operation, including returned business failures, can be retained. Validation exceptions that roll back the transaction are not completed replay records. If a previously returned state failure keeps replaying after you fix the state, reconcile the original outcome before issuing a new logical request and key.

For MCP, JSON-RPC request id is not part of the logical payload hash. A retry can use a new request id; the replay response echoes that new ID. Tool name and arguments must remain the same, and tool errors returned as results can also be retained.

This feature applies to API-key authentication. Browser session requests do not gain replay protection from this header.

External Security Halt​

The External Security Halt API deduplicates by authenticated service identity and external_event_id for the Organization lifetime. The Idempotency-Key header and 24-hour retention above do not apply. Credential rotation preserves the service identity and its event namespace.

The event ID, reason and optional incident reference are NFC-normalized and trimmed before comparison; an omitted, null or blank reference has the same semantics as an empty string. Rotation within the original identity preserves replay, while a replacement identity has a different event namespace. Removing the target from the identity's scope blocks even an identical replay with 404; disabling response, disabling the identity or revoking its credential blocks authentication with 401. Existing holds survive those changes. Restore authorized access or use human recovery rather than submitting the event under another identity to resolve uncertain acceptance.

After an ambiguous timeout, resend the same event ID and semantic payload. An identical retry returns 200 with the original intervention's current state and replayed: true; a new accepted event returns 202. Changing the target agent, reason, or incident reference under the same event ID returns 409 external_event_id_conflict.

Replaying a released intervention does not create another hold. A deliberate new halt requires a new event ID. Acceptance and replay responses describe durable intervention state, not proof that all previously admitted work stopped.

Gated extended retries and reconciliation​

New protected ingress, browser-task creates, human decisions and webhook rotations require the deployment setting API_EXTENDED_RETRIES_ENABLED=true. It defaults false. Unavailable new admissions return 503 idempotency_unavailable; the server never silently ignores the header. Retained identical retries continue returning their original outcomes while admission is disabled, and changed semantics still return 409 idempotency_conflict. Current authentication, operation scope, owner and resource authority are rechecked. Services retain the service-credential exact allowlist: these new task, webhook-management and decision mutations remain unavailable to services.

A protected asynchronous task create returns the original 201 body and task ID on replay. Any explicit wait, including 0 or null, with this header returns 400 unsupported_idempotent_wait before creating work. Ordinary no-key synchronous waits remain supported. Use the task read/result endpoint for current execution state.

A protected decision retains action identity, decision and supplied Outbox content version. A changed decision, value or version conflicts. Another key sees the existing type-specific stale/already-resolved result. OpenAPI decisions require a current configured human approver on every replay. Secret values are encrypted by their domain service and never stored in generic outcome metadata. Protected response bodies are encrypted and bounded to 1 MiB.

Secret rotations replay the original encrypted result and revision. If any later rotation supersedes that revision, replay returns 409 rotation_superseded; it never presents an obsolete secret as current. Old inbound URLs stop authenticating immediately. No dual-secret grace period is introduced.

Protected member/service responses expose Operation-Id and Idempotency-Expires-At. GET /api/v1/operations/{operation_id}/ returns the operation, resource references, outcome, original response status and expiry. For covered creates it also returns publication state and a safe blocked reason. It omits response bodies, URLs, secret values and caller event keys. Lookup is limited to safe-retry member outcomes and service receipts, requires the original credential owner (stable identity for services), read scope plus current original-operation scope and resource authority, and returns 404 after expiry or loss of access. A superseded rotation is reported as outcome: "superseded". URL-secret callers use their original returned messageId through an independently authorized timeline; they receive no broad operation lookup/list authority.

Message/task creation, receipt and durable publication intent commit together. Recovery runs every minute in bounded batches, with expiring claims; broker/model work runs after the transaction. A lost callback or broker outage can publish the same local input without creating another message/task. Worker claims and consumer ownership guards remain authoritative. A consumer receipt also proves delivery and prevents subsequent publication recovery after a lost broker acknowledgement. A broker acknowledgement lost before settlement can cause duplicate queue deliveries. This does not guarantee exactly-once external execution, recall already admitted work, or authorize automatic retries of unknown external effects.

Deploy migration 0578, upgraded writers/readers, recovery workers and Beat with new admission off; drain older writers and validate in a non-production environment before enabling the gate. Rollback keeps retained readers and recovery running through the latest retained receipt expiry, including earlier longer retention settings, and until pending publication intents have settled. Turning the gate off does not delete accepted intent. Emergency-held receipts remain historical acceptance; halt/reset never promotes them into a later generation. Receipt cleanup, message/content retention and publication recovery are independent.

Protected attachment admission currently requires both default and malware-quarantine storage to use Django's local FileSystemStorage. New protected uploads on a remote/custom storage backend return 503 idempotent_attachment_storage_unavailable before receipt/message/intake creation; retained identical retries remain readable. Remote object-store prestaging and cleanup must be implemented and validated before enabling that content profile, because the existing intake may write/promote blobs inside the message transaction. JSON, text and attachment-free forms do not have this restriction. Local physical blob writes still follow existing orphan/quarantine cleanup; only database intake rows share the atomic receipt/message/publication commit.

New extended member-operation parameter fingerprints are keyed with the response-encryption facility, protecting low-entropy provisioning values from a publicly guessable digest. Keep that encryption material stable through retained receipt expiry. Existing member/service operation hashes and namespaces are unchanged. Organization-owned publication uses the active Organization as owner; a historical human creator is not an alternate principal.

Application request admission​

POST /agents/{id}/requests/ requires Idempotency-Key, uses existing member-key or stable service-identity namespaces, and commits input, immutable attachment references, request, acceptance receipt and safe-retry publication intent together. Its default-off admission gate preserves matching authorized replay/reads. Original acceptance is replayed within the existing retention window; detail supplies current state. A descriptive client_reference is not a deduplication key. Read-only request waits are not replay mutations. See Application Requests.

Automatic events and recovery​

Delivery recovery requires Idempotency-Key, expected_revision, a bounded nonempty reason and duplicate-risk acknowledgement for unknown acceptance. An eligible failed/unknown row gets one audited new cycle; competing keys at the same revision conflict. Prior attempt evidence and event ID/body/occurrence time remain unchanged. Existing 24-hour member-key/service-identity replay namespaces apply with current authority, retention and original generation checked even on replay. Delivered/cancelled/stale work cannot be resent. See webhook operations and deployment limits.

General admission limits​

Deployments may enable shared request quotas, concurrent-wait capacity and queued-input backpressure. Accepted replays do not reserve new work capacity; ordinary HTTP quotas still apply. Honor Retry-After on 429/503 and keep the original protected-write key/body. Pending publication remains accepted input and is recovered independently of the caller. Enforced legacy task submission requires omission of wait; shared application/timeline waits remain bounded to 30 seconds. Validate throughput in your own environment.

Conditional setup and schedules​

The REST configuration surface requires retry keys for named-schedule create/change/delete/enable/disable, inactive configuration and placement/binding writes. Existing-resource changes additionally require exact If-Match. Body, query parameters and the conditional header are fingerprinted together. An authorized matching replay returns its historical body/ETag before checking that historical condition; retrieve current state. A new-key stale condition returns 412, a missing condition 428. The same encrypted safe-retry namespaces/lifetime/outcome service apply; replay never bypasses current agent, destination or credential authority.

REST file intake​

REST Files upload/replacement requires Idempotency-Key and binds exact bytes, explicit name/parent, content type and route. Replay returns original 202 acceptance, so poll file status separately. Current file ownership, scopes/grants and retention are checked even when new intake is disabled. A concurrent duplicate may temporarily stage private bytes, but only one pending generation/node is accepted and the losing staging is cleaned up. Expired keys are not a permanent deduplication guarantee.

Optional application result_schema is part of replay identity together with its canonical hash and fixed profile version. Key ordering is canonical; changing schema constraints or adding/removing the contract under the same unexpired key conflicts. Replay returns original acceptance metadata; retrieve current result separately. Disabling new typed admission preserves retained authorized replay. Formatting submission publishes no external effects and never replays successful tools.

Ordinary agent PUT/PATCH selected with X-API-Contract: 2026-10-14 requires Idempotency-Key plus exact If-Match. The safe-retry 24-hour member-key/service-identity ledger stores the original acceptance; version/body/query/precondition changes conflict. Identical currently authorized retry can return the original ETag after subsequent state advances. Use a fresh key only after deliberately reconciling the newer state. Idempotency prevents duplicate acceptance; the shared agent revision prevents stale conditional writes. Legacy unconditional writes remain last-write-wins. See conditional configuration.