workflows:read on the agent; payloads additionally need workflows:read-outputs. Starting, listing, and recovering runs are on Workflows, which also holds the error codes and the base URL, scopes, and envelope pointer; human input is on Workflow approvals.
Verified against lua-cli 3.33.0.
Steps
GET /workflows/:agentId/runs/:runId/steps/:stepId
Returns one step with every attempt.integer
One attempt instead of the current one; a non-number is
400 VALIDATION_FAILED.200 with the step: its summary fields (stepId, kind, label, status, attempt, maxAttempts, retriesRemaining, dependsOn, timings, error, suspend, park, artefactIds, the job block on Job-tier rows) plus input, output, outputRef, and attempts[]. Payloads are { "__hidden": true } without workflows:read-outputs. 404 STEP_NOT_FOUND or RUN_NOT_FOUND.
Equivalent: lua workflows status <runId> --steps.
Events
GET /workflows/:agentId/runs/:runId/events
Streams the run’s event ledger, or returns one page of it.string
text/event-stream opens the stream; anything else answers one JSON page.string
Resume after this sequence number on reconnect; wins over
afterSeq.integer
Start after this sequence number.
string
Comma-separated event types to keep, in the replay and the tail.
integer
default:"200"
Polling form only; at most 500 events per page.
id: <seq>, event: <type>, data: <event>, where the event is { runId, seq, ts, type, stepId?, attempt?, actor: { kind, id? }, data?, orgId, agentId, userId? } with seq strictly increasing per run, ts an ISO 8601 timestamp, actor.kind one of engine, worker, sweeper, runner, user, agent, webhook, api, schedule, or system, and data at most 8 KB. Control frames carry no id: heartbeat { ts, head } every 15 seconds, end { seq, status } after the terminal event (the server then closes), reconnect { reason }, error { code } with run_gone, forbidden, or internal, and workflow.output.acl_changed { restricted, ts } when your output visibility flipped mid-stream. Without workflows:read-outputs each frame arrives without data and with restricted: true; the read scope and the output grant are re-checked every 60 seconds, and a lost read scope ends the stream with error { code: "forbidden" }. The event vocabulary is run.*, step.*, subrun.*, approval.*, signal.*, timer.*, billing.*, journal.*, goal.*, schedule.*, and connection.*; the terminal members are run.completed, run.failed, run.cancelled, run.abandoned, and run.timed_out, and a version migration appends run.migration_scheduled, run.migration_applied, run.migration_refused, or run.migrated.
The polling form answers 200 with { "events": [...] } under the same cursor rules.
Errors
404 RUN_NOT_FOUND; 429 SSE_SUBSCRIBER_CAP when too many streams are open on the run.
Equivalent: lua workflows watch <runId> (stream) and lua workflows logs <runId> (polling).
GET /workflows/:agentId/runs/:runId/journal
Reads a script-form run’s journal.integer
Start after this entry; a negative or non-numeric value is
400 VALIDATION_FAILED.integer
Page size.
boolean
true or 1 includes entry payloads; needs workflows:read-outputs.200 with { "items", "nextAfterSeq", "outputsHidden"? }, where outputsHidden: "read-outputs" marks a page served without the grant. 403 READ_OUTPUTS_REQUIRED; 404 RUN_NOT_FOUND or NOT_SCRIPT_RUN.
GET /workflows/:agentId/runs/:runId/attach
Streams the run as chat parts (event: part frames whose data is { type: "data-lua-workflow-run" | "data-lua-workflow-step", id, data }) for a chat card that re-attaches to a live run, with the same heartbeat, end, reconnect, and error control frames and Last-Event-ID rule as the events stream; a terminal run answers one part and end.
integer
Start after this sequence number.
string
Echoed into the audit record; never used for access checks.
404 RUN_NOT_FOUND, also when the stream is disabled on the server; 409 NOT_A_WORKFLOW_RUN for a cloud-task id; 429 SSE_SUBSCRIBER_CAP.
Artifacts and exports
GET /workflows/:agentId/runs/:runId/artefacts/:artefactId
Presigns a download of a run artifact, or of an offloaded step or run output ({ "__cdnRef" } or { "__datasetRef" }).
integer
Dataset refs only: first row of the page, from 0.
integer
default:"100"
Dataset refs only: rows per page, at most 1,000.
200 with { "artefactId", "kind", "url", "expiresAt", "contentType", "bytes" }; a file adds sha256, and a dataset ref adds rowCount, schema, and page { rows, offset, limit, nextOffset }. The link expires after 300 seconds. 400 VALIDATION_FAILED for an offset past the last row; 403 READ_OUTPUTS_REQUIRED; 404 ARTEFACT_NOT_FOUND or RUN_NOT_FOUND; 409 ARTEFACT_PENDING; 503 ARTEFACT_READ_FAILED.
POST /workflows/:agentId/artefacts
Declares an input artifact to upload, for example evidence to attach to an approval. Needsworkflows:execute.
string
required
At most 255 characters.
string
required
At most 255 characters.
integer
required
Declared size, at most 256 MB.
string
file, dataset, image, or document.string
At most 200 characters.
object
kind: "dataset" only.201 with { "artefactId", "uploadUrl", "expiresAt" }; the row stays pending until the bytes land, and an unclaimed declaration expires after 24 hours. 400 ARTEFACT_SCHEMA_INVALID (a datasetSchema on another kind, or not an object) or VALIDATION_FAILED; 413 ARTEFACT_TOO_LARGE.
PUT /workflows/:agentId/artefacts/:artefactId/content
Uploads the bytes of a declared artifact. Send the raw body with itsContent-Type; it is never parsed as JSON. Needs workflows:execute.
Response
200 with { "artefactId", "status": "ready", "bytes", "sha256" }. 404 ARTEFACT_NOT_FOUND for an unknown, foreign, or expired declaration; 413 ARTEFACT_TOO_LARGE past the declared size or 256 MB.
POST /workflows/:agentId/runs/:runId/export
Requests an evidence bundle of a terminal run. Needsworkflows:read-outputs.
boolean
Run and step inputs; on a customer’s run this also needs
workflows:write.boolean
default:"true"
The artifact manifest.
boolean
default:"true"
The script-run journal.
202 with { "exportId", "status": "pending" }. 403 READ_OUTPUTS_REQUIRED; 404 RUN_NOT_FOUND; 409 RUN_NOT_TERMINAL or EXPORT_IN_PROGRESS (exportId); 429 EXPORT_RATE_LIMITED past 5 exports of one run per day.
Equivalent: lua workflows archive-runs --since 8d --out ./archive.
GET /workflows/:agentId/runs/:runId/export/:exportId
Reads an export’s status and download link. Needsworkflows:read-outputs.
Response
200 with { "exportId", "runId", "status": "pending" | "ready" | "failed" | "expired" | "pushed", "url"?, "expiresAt"?, "bytes"?, "manifest": { files, sha256 }?, "error"?, "createdAt" }; a bundle can be downloaded for 7 days, after which it reads expired. 403 READ_OUTPUTS_REQUIRED; 404 RUN_NOT_FOUND or EXPORT_NOT_FOUND.
See also
- Workflows — the run model, run routes, and every error code
- Workflow approvals — the routes that answer the events a run waits on
lua workflows—watch,logs, andarchive-runsover the same routesWorkflows— reading a run from agent code- Operate runs — watching, exporting, and erasing runs in practice

