`GET /api/workflows/runs/{run_id}` — the run (status + timestamps) plus each step's task state so the UI can render per-step progress.
const url = 'https://example.com/api/workflows/runs/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0';const options = {method: 'GET'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url https://example.com/api/workflows/runs/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Workflow run id
Responses
Section titled “Responses”A workflow run plus its per-node states — GET /api/workflows/runs/{run_id}.
steps keeps the per-node step_key/status/task_id list the web run
view consumes; nodes is the typed per-node run view so the UI can show
gate branching. Both are built from the workflow run’s child runs.
object
#1052: the run’s own failure reason — the string the engine composes from
the nodes that actually CAUSED the failed verdict at terminalization
(failure_reason::compose_failure_reason, shape Node '<title>' (<key>) failed: <error> (+N more nodes failed)), and the very field the #838
workflow-failure alert reads. A client that reports an outcome — the web
terminal’s workflow trigger watch — can therefore state the SAME
substance as the chat notification instead of re-deriving a lookalike from
nodes, which cannot see the on_failure: Stop filter that decides which
failures are causes. null for a run that is not failed, or whose reason
could not be derived. Additive and nullable, so an older client is
unaffected.
One node’s typed run-state (ADR 0020 V2/V3) — kind and fired_handle let
the UI render which branch a gate or human node took; config carries
a human node’s { title, instructions } approval prompt.
#951 D1: the projection below it computes attempt, deadline_at,
iteration_index, the child run_id, the start/finish timestamps and the
failure diagnostic; this DTO used to drop all seven on the floor, which left
the attempt chip showing a hard-coded 1, and the HITL countdown, the
loop-progress badge and any per-node duration display as unreachable code.
Every field here is additive and nullable, so an older client is unaffected.
object
1-indexed retry attempt on the node’s child run; 1 when never retried.
A waiting human node’s auto-rejection deadline; null otherwise.
The child run’s failure diagnostic, when it failed.
null on an ordinary node row and on a loop FRAME row; set on the
per-iteration ANCHOR rows appended after the node block (WP V16).
The kind of a workflow node.
Doubles as the OpenAPI spec mirror for the closed kind string on the
workflow node/step + run-node DTOs: StepResponse.kind / NodeResponse.kind
narrow to this via #[schema(value_type = NodeKindSpec)]. The variant set is
the workflow_nodes.kind CHECK (migration 157); the serde snake_case
rename matches as_db_str one-for-one, so the spec enum equals what the
handlers serialize.
The child runs row backing this node; null while the node is
pending (the engine has not opened a run for it yet).
The per-node run state of a run — what the UI renders for progress.
step_key is the node’s key; task_id is the materialized step-task for
an agent node (null for gate/human/end nodes).
object
Example
{ "nodes": [ { "kind": "agent" } ]}Structured client error
The canonical JSON body of every error response — the single source of truth
the frontend binds to. Every AppError serializes as this exact shape, and
the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is
what the frontend error schema is generated from, so there is no hand-written
error schema on either end.
object
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}Authentication required
The canonical JSON body of every error response — the single source of truth
the frontend binds to. Every AppError serializes as this exact shape, and
the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is
what the frontend error schema is generated from, so there is no hand-written
error schema on either end.
object
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}Permission denied
The canonical JSON body of every error response — the single source of truth
the frontend binds to. Every AppError serializes as this exact shape, and
the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is
what the frontend error schema is generated from, so there is no hand-written
error schema on either end.
object
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}Run not found
The canonical JSON body of every error response — the single source of truth
the frontend binds to. Every AppError serializes as this exact shape, and
the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is
what the frontend error schema is generated from, so there is no hand-written
error schema on either end.
object
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}Structured server error
The canonical JSON body of every error response — the single source of truth
the frontend binds to. Every AppError serializes as this exact shape, and
the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is
what the frontend error schema is generated from, so there is no hand-written
error schema on either end.
object
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}