409 Conflict, an unknown run
404 Not Found.
Endpoints
- TypeScript
- REST API
replay and retryFromStep return the new run, e.g. { "id": "01HABC...", "status": "queued" }.
Which status accepts which call
The run statuses split into non-terminal (queued, running, waiting, paused,
needs_attention) and terminal (succeeded, failed, cancelled). Every control call is gated on
that split:
paused is non-terminal: a paused run holds no worker but stays cancellable and resumable. Pause
takes effect at the next step boundary - the step in flight finishes and is checkpointed, then the
run stops before its next step; it is not a mid-step interrupt. Resume re-queues the run, which
continues from the next step and replays the already-completed steps from their stored results, so no
prior work runs twice.
Replay semantics
replay acts only on a finished run (succeeded / failed / cancelled); replaying a still-active
run returns 409. It does not mutate the original - that stays as history. Instead it creates a new
run with its own id, copying the original’s workflow, app, input, and runner, and enqueues it from the
start.
The new run records the id of the source run it was forked from in replayOf (set the same way by
retry-from-step and bulk-replay), so the lineage is traceable both ways: a run links back to its
origin, and GET /runs?replayOf=<id> lists every run forked from one source. See
replay->run lineage.
Bulk replay
bulk-replay redrives many runs at once. It selects finished runs by the same axes as the
runs listing - app, workflow, status, runType, and since (project-scoped) - and
forks each, newest first, up to a per-call ceiling (the response sets capped: true when more matched
than were replayed; narrow the filter and call again). Non-terminal matches are counted in skipped,
not replayed. A request with no filter at all is rejected (400), so a replay is always scoped to at
least one of status/since/app/workflow/runType - an empty body never redrives the whole project.
The response is { matched, replayed, skipped, failed, capped }. A suspended project refuses the whole call
(403).
Bulk cancel
bulk-cancel is the mirror of bulk replay: it cancels many non-terminal runs at once. It selects
runs by the same axes as the runs listing - app, workflow, status, runType, and
since - plus tags, so you can cancel, say, every queued run for one customer.
It cancels each match newest first, up to a per-call ceiling (the response sets capped: true when more
matched than were cancelled; narrow the filter and call again). Already-terminal matches are counted in
skipped, not cancelled, and a run that finishes between the match and the cancel is skipped too, so the
call is idempotent. A request with no filter at all is rejected (400) - a cancel is always scoped to at
least one of status/since/app/workflow/runType/tags, so an empty body never cancels the whole
project. The response is { matched, cancelled, skipped, failed, capped }. A suspended project refuses
the whole call (403). Each cancelled run fires its own run.cancelled webhook.
The
tags filter depends on run tags.Retry from a step
retry-from-step is replay with a checkpoint. Like replay it acts only on a finished run and forks a
new run (the original stays as history), but it carries over the completed steps before the named
step and resumes execution from that step. The carried steps replay from their stored results - durable
execution skips them - so an expensive earlier step (a charge, an email) is not run twice. Any step of the
run is a valid boundary, letting you rewind to any point; picking the first step carries nothing and is
equivalent to a full replay. An unknown step name returns 404.
Audit log
Every state-changing call is recorded to a per-project audit log - the control actions above plus the approval decisions. Read it withGET /control-actions, newest first:
Each entry is
{ id, action, runId?, newRunId?, actor?, detail?, createdAt }. actor is the name of
the API key the call authenticated with - so naming your keys per surface (ci, support-tool) is
what makes the log attributable. detail carries action-specific context: the step a retry resumed
from, or a bulk replay’s filter and outcome counts.
Recording is best-effort - a failed audit write is logged but never fails the action it describes, since
the action has already happened.