Skip to main content

Idempotency

Retrying a POST /pipelines/from-intent without coordinating with the server creates two pipelines. To avoid that, every mutating Agent API call accepts an idempotency_key field in the body (16–128 characters).

Replays with the same key return the same run_id / pipeline_id / artifact — never a new one.

Where the key lives

EndpointField
POST /pipelines/from-intentbody idempotency_key
POST /posts/{id}/draftbody idempotency_key
POST /artifacts/{id}/publishbody idempotency_key
POST /accounts/connectbody idempotency_key
POST /fetchbody idempotency_key (optional — repeat (url, intent) pairs hit the response cache anyway)

Read-only endpoints (GET /pipelines/{id}, GET /pipelines/{id}/inbox, etc.) ignore the field.

The MCP tools mirror the same parameter — every mutating tool exposes idempotency_key with the same semantics. See MCP · tools.

Format

PropertyDetail
Length16–128 characters. Shorter or longer returns validation_failed.
CharsetFree-form ASCII. UUIDv4 hex (32 chars after stripping dashes) is the recommended shape.
Scope(organization_id, endpoint, key). Two different orgs can use the same key without colliding.
TTLThe server keeps the original response for the lifetime of the underlying resource — replays after the resource is deleted return 404 not_found, not a cached body.

Replay states

SituationOutcome
Same key + same body + original completedReturns the same run_id / resource id as the first call. Side effects do not repeat.
Same key + different bodyThe endpoint returns its normal error (typically validation_failed) — keys aren't bound to body shape, but the underlying invariants still apply.
Same key + original still runningThe call returns the original's run_id. Poll that run; do not generate a new key.

There is no 409 idempotency_request_in_progress HTTP today — from-intent returns immediately with the existing pipeline + the original run id.

  1. Generate a UUIDv4 at the start of the logical attempt.
  2. Reuse it across every retry of the same attempt.
  3. Generate a new one only when the user takes intentional action again (it's a new action, not a retry).
const idempotencyKey = crypto.randomUUID().replaceAll("-", "");
// 32-char hex — safely within the 16–128 range.

await fetch(`${WHET_BASE_URL}/pipelines/from-intent`, {
method: "POST",
headers: {
"Authorization": `Bearer ${WHET_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
handle: "growth_dr",
intent: "Track for analytical drafts, daily.",
idempotency_key: idempotencyKey,
}),
});

What it is not

  • It is not an application cache. Do not use it to "skip" requests because you already know the response — the server still validates invariants.
  • It does not survive forever. Once the underlying resource is deleted, replays return 404 not_found.
  • It does not apply to GET, PATCH, or DELETE. Make sure those retries are idempotent on your side.

See also: Authentication, Error taxonomy.