Idempotency

Safely retry trigger calls without spawning duplicate runs.

POST /api/v1/flows/{flowId}/runs is the only endpoint that creates expensive (and externally-visible) state — a fresh container, a billable invocation, a webhook delivery. Idempotency keys let you retry the call as many times as you want and only ever produce one run.

How to send a key

Two ways — they’re equivalent. Pick whichever your HTTP layer makes easy:

POST /api/v1/flows/{flowId}/runs HTTP/1.1
Authorization: Bearer flo_...
Idempotency-Key: order-9182-attempt-3
Content-Type: application/json

{ "prompt": "Process order 9182" }

Or in the body:

{
  "prompt": "Process order 9182",
  "idempotencyKey": "order-9182-attempt-3"
}

The header wins if both are present.

What you get back

  • First call — fresh run created, returns 201 Created.
  • Repeat within 24 hours, same (projectId, key) — returns 200 OK with the original runId and a reused: true flag in the body:
{
  "data": {
    "runId": "0d29ff4e-...",
    "status": "running",
    "reused": true
  }
}

Your client should treat reused: true as a successful trigger, not an error.

Key requirements

PropertyRule
Length1–200 chars
CharsetAnything UTF-8. We don’t validate further; pick what’s useful for your debugging.
UniquenessUnique per (projectId, key). Reusing the same key across projects is fine — they’re scoped.
TTL24 hours from the first call. After that, the same key starts a new run.

A common pattern: <external-id>-<attempt-no> so retries dedupe but a follow-up “rerun manually” call gets a fresh run.

What gets compared

We compare on (projectId, idempotencyKey) only — not on the request body. If you send the same key with a different prompt on a retry, we return the original run (with the original prompt) and the new prompt is ignored.

This is the standard tradeoff: stable retries vs sneaky body changes. Use a different key when you genuinely want different inputs.

What’s NOT idempotent

Every other endpoint:

  • POST /runs/{runId}/messages — sends the message every time. If your retry hits twice, the agent gets two copies.
  • POST /runs/{runId}/files — writes each file every time. Same path overwrites; different paths accumulate.
  • POST /runs/{runId}/stop — idempotent in effect (already-stopped returns 200 with alreadyStopped: true), so safe to retry.
  • DELETE /runs/{runId} — first 204, subsequent 404.

If you need exactly-once semantics on follow-up messages or file injects, deduplicate on your side before calling.