Idempotency
The operations that start paid or visible work accept a deduplication key:
A request carrying a key that already names a resource runs nothing and answers with that resource, in whatever state it has reached.
This page is the canonical definition. Module pages describe what their own replay returns and link here.
Operations that accept a key
| Operation | Scope | First request | Retry under the key | Same key, different request |
|---|---|---|---|---|
POST /api/v1/agents/{agent_id}/generate | project | runs as usual | 202 with the generation handle | 409 IDEMPOTENCY_KEY_REUSED |
POST /api/v1/orchestration-runs | project | 201 | 200 with the run | 409 IDEMPOTENCY_KEY_REUSED |
POST /api/v1/sessions/{session_id}/messages | session | 201 | 200 with the original message | 409 IDEMPOTENCY_KEY_REUSED |
POST /api/v1/tasks | project | 201 | 200 with the task as it is now | 409 IDEMPOTENCY_KEY_REUSED |
POST /api/v1/tasks/{task_id}/transitions | task | 200 | 200 with the task as it is now | 409 IDEMPOTENCY_KEY_REUSED |
A retry that arrives while the original is still running replays it too. What each operation compares to decide "same request" is on its module page: agents, orchestrations, sessions, task creates, task transitions.
Sending a key
The key travels in the idempotency_key body field or the Idempotency-Key header:
- CLI
- SDK
- curl
soat start-orchestration-run \
--orchestration-id "$ORCH_ID" \
--idempotency-key "dispatch-2026-09-18-activation-42"
const { data, error } = await soat.orchestrations.startOrchestrationRun({
body: {
orchestration_id: ORCH_ID,
idempotency_key: 'dispatch-2026-09-18-activation-42',
},
});
if (error) throw new Error(JSON.stringify(error));
curl -X POST https://api.example.com/api/v1/orchestration-runs \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: dispatch-2026-09-18-activation-42" \
-d '{ "orchestration_id": "'"$ORCH_ID"'" }'
| Rule | Refusal |
|---|---|
| A non-empty string of at most 255 characters, stored verbatim | 400 VALIDATION_FAILED |
| Header and body field together must name the same key | 400 VALIDATION_FAILED |
| The header is sent once | 400 VALIDATION_FAILED |
MCP tool calls send the body field: tool schemas carry no headers.
How long a key lasts
A key is claimed by the record its first request wrote, for as long as that record exists. There is no expiry window, so a retry never finds the key released and runs a second time.
Keys the server sets
| Where | Key | Purpose |
|---|---|---|
| Orchestration node executions | {orchestration_run_id}:{node_id}:{attempt} | A redelivered node reuses its stored output; an http tool node forwards the key as its Idempotency-Key request header |
| Event trigger firings | <event_id>:<trigger_id> | One firing per event and trigger |