Skip to content
Get started

Runs lifecycle & statuses

#The status machine

A run moves through exactly one of these statuses at a time:

text
                    ┌───────────────┐
   POST /v1/runs -> │    queued     │
                    └───────┬───────┘
                            │ worker claims the job
                            ▼
                    ┌───────────────┐
              ┌──── │    running    │ ────┐
              │     └───────┬───────┘     │
   pauses on  │             │             │ finishes
   human step │             ▼             │
              │     ┌───────────────┐     │
              │     │  (resumed)    │     │
              ▼     └───────┬───────┘     ▼
   ┌─────────────────┐      │      ┌───────────────┐
   │  needs_input     │      │      │   succeeded   │
   │  needs_approval  │──────┘      ├───────────────┤
   │  needs_review    │             │    failed     │
   └─────────────────┘             ├───────────────┤
              │                     │   cancelled   │
              └── cancel ──────────>└───────────────┘
  • queued — accepted, credits reserved, waiting for apps/worker to claim it.
  • running — the kernel's Diagnose → Assemble → Act → Assess loop is executing.
  • needs_input / needs_approval / needs_review — paused on a person; see Human-in-the-loop. Resuming (or rejecting) sends it back to running, or straight to a terminal status.
  • succeeded / failed / cancelled — terminal. A webhook fires for whichever one it is (and for the three needs_* pauses) if you've subscribed to that event.

Only queued, needs_input, needs_approval, and needs_review runs can be cancelled — cancelling a run already running returns 409 run_currently_running (there's no mid-flight abort signal in this version), and cancelling a terminal run returns 409 run_already_terminal.

#Creating a run

bash
curl -X POST "https://api.agentflowbind.com/v1/runs" \
  -H "Authorization: Bearer afb_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 2fbb0e7a-6b8a-4b0e-8e2a-6d6b6a2b7a10" \
  -d '{
    "agentId": "3c1a9e40-...-77bd",
    "input": { "fields": { "note": "Q1 batch" }, "fileIds": ["b4a0c9e1-...-1f0a"] },
    "options": { "webhookUrl": "https://example.com/hooks/afb" }
  }'

Returns 202 { "id", "status": "queued", "estimateCredits" }, or 402 billing.insufficient_credits with { "required", "available" } if the workspace's balance can't cover the estimate.

options.model overrides the preset's default control-phase model for this run only; options.budget overrides the preset's default { maxCredits, maxIterations, maxDurationMs } caps.

#Reading a run

bash
curl "https://api.agentflowbind.com/v1/runs/5e6a7b8c-...-9d0e" -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"
json
{
  "id": "5e6a7b8c-...-9d0e",
  "workspaceId": "1a2b3c4d-...",
  "profileVersionId": "3c1a9e40-...",
  "status": "succeeded",
  "input": { "fields": { "documentArtifactIds": ["..."] }, "artifacts": [{ "filename": "invoice.pdf", "mime": "application/pdf", "sha256": "..." }] },
  "output": { "supplier": { "name": "Acme GmbH" }, "total": 1249.5 },
  "error": null,
  "budget": null,
  "creditsReserved": 42,
  "creditsSettled": 38,
  "startedAt": "2026-03-14T09:12:03.000Z",
  "finishedAt": "2026-03-14T09:12:41.000Z",
  "createdAt": "2026-03-14T09:12:00.000Z",
  "updatedAt": "2026-03-14T09:12:41.000Z",
  "costCredits": 38,
  "humanRequest": null
}

humanRequest is non-null exactly when status is one of the three needs_* values — it's the pending question/approval/review payload the run is blocked on.

#Listing runs

GET /v1/runs is cursor-paginated, newest first:

bash
curl "https://api.agentflowbind.com/v1/runs?status=succeeded&agentId=3c1a9e40-...-77bd&limit=20" \
  -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"
json
{ "runs": [ { "id": "...", "status": "succeeded", "...": "..." } ], "nextCursor": "eyJjcmVhdGVkQXQiOiI..." }

Pass the previous response's nextCursor back as ?cursor=... to fetch the next page; nextCursor is null on the last page.

#Steps

GET /v1/runs/:id/steps returns the kernel's ordered trace — one row per Diagnose/Assemble/Act/Assess/tool-call/system step:

bash
curl "https://api.agentflowbind.com/v1/runs/5e6a7b8c-...-9d0e/steps?include=io" \
  -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"
json
{
  "steps": [
    {
      "id": "...", "seq": 3, "iteration": 1, "phase": "tool", "tool": "extract_fields",
      "provider": "anthropic", "model": "claude-sonnet-5", "hostRegion": "us",
      "tokens": { "input": 4218, "output": 612, "cached": 0 },
      "cost": 6, "duration": 3120, "status": "ok",
      "createdAt": "2026-03-14T09:12:11.000Z",
      "input": { "artifactId": "...", "schema": { "...": "..." } },
      "output": { "supplier": { "name": "Acme GmbH" } }
    }
  ]
}

Omit ?include=io to leave off the (potentially large) input/output fields.

#Output, artifacts, and cancellation

bash
# Final JSON output
curl "https://api.agentflowbind.com/v1/runs/5e6a7b8c-...-9d0e/output" -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"

# As CSV, if the preset's output flattens cleanly (409 csv_not_supported otherwise)
curl "https://api.agentflowbind.com/v1/runs/5e6a7b8c-...-9d0e/output?format=csv" -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"

# Download a specific artifact the run produced or was given
curl "https://api.agentflowbind.com/v1/runs/5e6a7b8c-...-9d0e/artifacts/b4a0c9e1-...-1f0a" \
  -H "Authorization: Bearer afb_live_xxxxxxxxxxxx" -OJ

# Cancel a queued/paused run
curl -X POST "https://api.agentflowbind.com/v1/runs/5e6a7b8c-...-9d0e/cancel" -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"

GET /v1/runs/:id/output returns 409 output_not_available before the run has produced one — poll or subscribe to events instead of racing this endpoint.

#Live events (SSE)

GET /v1/runs/:id/events streams text/event-stream instead of polling: it replays every stored event (from Last-Event-ID, or the beginning), then streams live ones, with a heartbeat every 15 seconds, and closes once the run reaches a terminal status.

typescript
const events = new EventSource(
  `https://api.agentflowbind.com/v1/runs/${runId}/events`,
  // Browsers can't set an Authorization header on EventSource — front this
  // through your own server, or use the SDK (preview)'s `runs.wait(id, { onEvent })`,
  // which uses `fetch` and can send the header directly.
);
events.addEventListener("status_changed", (event) => {
  const payload = JSON.parse(event.data) as { status: string };
  console.log("run status ->", payload.status);
  if (["succeeded", "failed", "cancelled"].includes(payload.status)) events.close();
});

Each frame's id: is the event's per-run sequence number — a reconnecting client that sends it back as Last-Event-ID resumes exactly where it left off rather than re-processing (or missing) events.

#Feedback

POST /v1/runs/:id/feedback records a rating and/or a corrected output on a finished run:

bash
curl -X POST "https://api.agentflowbind.com/v1/runs/5e6a7b8c-...-9d0e/feedback" \
  -H "Authorization: Bearer afb_live_xxxxxxxxxxxx" -H "Content-Type: application/json" \
  -d '{ "rating": 4, "correctedOutput": { "supplier": { "name": "Acme GmbH" }, "total": 1259.5 }, "comment": "Total was off by 10" }'

Supplying correctedOutput also creates a reusable eval case for this agent (see Evals), unless you pass "useAsExample": false.