Skip to content
looot docs
Esc
↑↓navigate↵open⌘Jpreview
On this page

Idempotency

Why every run needs an idempotencyKey, what replays and what conflicts, and how a job run's digest differs from a direct run's.

Every POST /v1/runs and MCP run call requires idempotencyKey. It is what makes retrying a run safe.

Same key, same body: replay

Retry with the same key and the identical request, for this workspace, against this endpoint, and you get the original run back, whatever its status (including one still in flight), stamped replayed: true. Nothing runs a second time, and nothing charges twice.

Send it as idempotencyKey in the body, or as the Idempotency-Key header; sending both with different values is a 400. The CLI makes one for you when you do not pass one (looot-<uuid>) and prints it to stderr:

idempotency-key: looot-5b1c...

If a request drops mid-flight, retry it with the exact same key and body. If the first one did reach the gateway, you get that run back and no new run starts.

Same key, different body: conflict

Reuse a key with a different endpointId or input, and you get 409 idempotency_conflict; no new run starts. looot never silently applies a changed body to an old run. Use a fresh key per logical call.

After insufficient_balance, use a new key

A run refused for insufficient_balance is not retried with the same key. Top up, then run again with a new idempotencyKey. The old key stays attached to the refused run.

Job run digests

For a job:<id> run, the digest that decides what counts as “the same call” is built from the job id, the input as sent, prefer, exclude and the fallback settings. It never includes which provider ends up picked. So the same key against the same job always replays the first run, even if a fresh run would now pick a different provider. The same key against a different job id, or against a direct endpoint id, is idempotency_conflict; a job run and a direct run never replay each other, in either direction.

A direct run’s digest is unchanged: endpointId plus input as sent.

Worked example

{
  "endpointId": "job:people.email.verify",
  "input": { "email": "jane.doe@example.com" },
  "idempotencyKey": "verify-jane-doe-1"
}

Send it twice with the same key and input: the second call returns the first run, replayed: true, no new hold. Send it again with {"email": "jane.doe@example.org"} under the same key: 409 idempotency_conflict.

Was this page helpful?