Skip to main content
The workflow runtime routes start runs of a deployed workflow, read them, and recover them when they park. Definitions are authored with lua push workflow and activated with lua workflows deploy; these routes only run them. This page covers the run model, the definition and run routes, recovery, and the error codes. Steps, events, artifacts, and exports are on Workflow events; approvals, signals, and gates on Workflow approvals; compose, schedules, goals, workspaces, and organization-wide routes on Workflow schedules and goals. Every route is the REST twin of a Workflows method or a lua workflows action, named under each endpoint. Verified against lua-cli 3.33.0.

Base URL and authentication

Requests go to https://api.heylua.ai with Authorization: Bearer <<YOUR_API_KEY>>; the REST API overview covers keys, the typed error envelope, cursor pagination, and the per-minute rate buckets. Per-agent routes live under /workflows/:agentId and bind their scope to that agent: workflows:read for every GET, workflows:execute to start runs and act on them, workflows:write to compose, schedule, create or edit goals, and start a run from an inline graph or script. Reading step inputs and outputs needs workflows:read-outputs; without it payload fields come back as { "__hidden": true } and event data is redacted. Organization-wide routes bind to the orgId you pass. Approving, denying, editing an approval payload, resolving a parked step, erasing a run, and reassigning assets are human-only: a typed API credential is refused with 403 APPROVAL_REQUIRES_HUMAN, so drive those from a user session or the CLI. Where an endpoint names a Workflows method, deployed agents serve only start, get, list, and cancel; the other members work under lua test and throw not_implemented when deployed (see Deployed runtime differences).

Run model

A run is identified by runId (wfr_...), pins the workflowVersionId it started on, and finishes on that version: deploying a new version never moves a live run. It moves through these statuses: abandoned is a run a person or an agent force-canceled once forceAvailableAt had passed; timed_out is a run that reached budget.maxDurationSeconds, idle or in flight. A gated or suspended run carries gate { kind, since, expiresAt?, stepId?, attempt?, code? } with kind one of start-consent (approvalLinkId), quota (code concurrency_cap or suspended_cap), billing (code, operationId), budget (dimension, spent, cap), exception, org_archived, or disabled; Gates says which verb clears each. trigger records who started the run: chat, sdk, api (CLI and HTTP), schedule, webhook (a trigger primitive), template, workflow (a parent run), or device. origin records what it ran: inline, compose, definition, schedule, or goal. A run summary, the row every list returns, carries runId, workflowId, workflowName, workflowVersionId, form (graph or script), agentId, orgId, status, gate, trigger, origin, createdBy { subjectType, subjectId }, counts { total, completed, failed, skipped, running, suspended, pending, cancelled? }, createdAt, startedAt, updatedAt, completedAt, durationMs (terminal only), correlationKey, tags, principalKind (user, service, or customer), batchId, goalId, cancel, usage, error { code, message, stepId?, issues? }, hasOutput, restricted, stepCount, currentLabel, phase, and eventSeq. Timestamps are Unix time in milliseconds. The detail adds input, output (inline up to 256 KB, else { "__cdnRef" } beside an outputPreview and an outputRef), steps[], edges[], suspensions[], budget { maxCredits?, maxSteps, maxDurationSeconds, deadlineAt, unit, spent { credits, actionsEstimate, steps }, reserved, remaining? }, and budgetRaises[].

Definitions

GET /workflows/:agentId/definitions

Lists the workflow definitions on an agent.
string
required
The agent.
string
graph or script.
boolean
default:"false"
Include chat-composed workflows.
string
nextCursor from the previous page.
integer
default:"50"
From 1 to 100.
Response 200 with { "items": [...], "nextCursor": string | null }; each item is { workflowId, name, description?, form, dynamic, active, activeVersionId?, inputSchema?, updatedAt, scheduleJob?, idleExpiresAt? }. Equivalent: lua workflows list.

GET /workflows/:agentId/definitions/:workflowId/rollup

Returns a per-day rollup of one workflow’s runs, bucketed by the UTC day the run was created.
string
Start of the range, as Unix milliseconds or an ISO 8601 timestamp; defaults to 30 days before to.
string
End of the range, same formats; defaults to now.
Response 200 with { "workflowId", "bucket": "day", "from", "to", "rows": [...] }; each row is { date, runs, completed, failed, cancelled, suspended, p50DurationMs, p95DurationMs, creditsTotal, stepFailures }, where date is YYYY-MM-DD, failed counts failed, timed_out, and abandoned runs, suspended counts suspended, gated, and waiting runs, and the percentiles are null on a day with no terminal run. The answer is cached for 60 seconds. 400 ROLLUP_RANGE_TOO_WIDE (maxDays, days) past 90 days, or VALIDATION_FAILED for a timestamp that does not parse or a from after to; 404 WORKFLOW_NOT_FOUND.

Run routes

POST /workflows/:agentId/runs

Starts one run.
string
required
The agent.
string
Replay key; wins over the body field.
string
The saved definition, by name (at most 120 characters). Send exactly one of workflowName, workflowId, composeId, graph, or script.
string
The saved definition, by id.
string
A draft from POST .../compose.
object
An inline dynamic graph, at most 256 KB; needs workflows:write.
string
An inline script, at most 256 KB; needs workflows:write.
any
The run input, at most 256 KB; validated against the definition’s inputSchema.
string
At most 128 characters. The same key with the same definition and input replays the existing run; anything else answers 409 IDEMPOTENCY_KEY_REUSED.
object
{ maxCredits?, maxSteps?, maxDurationSeconds?, maxJobSeconds? }, each an integer of at least 1, capped by the organization’s policy. Omit a key rather than sending 0.
integer
From 0 to 55. Long-polls: when the run settles within the window the answer is 200 with the run detail instead of 202.
string
Pin a version; defaults to the active one.
string
A caller-chosen, non-unique key (at most 128 characters of letters, digits, :, _, -, ., /) for signals/by-key and list filters.
string[]
At most 10 tags of at most 40 characters.
string
default:"app"
Where the terminal summary goes: emailApp, email, app, or off.
object
{ channel, threadId } with channel one of whatsapp, sms, email, webchat, slack: the terminal summary is delivered on that channel.
string
For chat-started runs, the caller’s own thread on this agent where the run card lands (at most 256 characters); any other thread answers 400 ORIGIN_THREAD_INVALID.
boolean
Take one background agent turn on originThreadId when the run ends; without an origin thread the start is refused with 400 CALLBACK_THREAD_REQUIRED.
object
{ kind: "customer", externalId }: the run belongs to a customer, who can never be an approver.
object
{ repo?, ref?, credentialsRef?, sizeGb? } overriding a declared Job-tier workspace; sizeGb from 1 to 50.
object
Seeds the run state; at most 64 KB.
Response 202 with { "runId", "status": "queued" | "gated", "watchHint": { "events", "poll" } }. An idempotent replay answers 200 with the existing run’s runId, status, workflowId, workflowName, startedAt (ISO 8601), watchHint, and idempotentReplay: true, deduplicated: true; nothing was created. When waitSeconds elapsed on a settled run the answer is 200 with the run detail. Errors Equivalent: lua workflows start <workflow> --input @input.json --follow, Workflows.start().

POST /workflows/:agentId/runs/batch

Starts many runs of one workflow at once. The body may be at most 2 MB.
string
required
The definition.
array
required
From 1 to 200 items, subject to the organization’s batch cap: { idempotencyKey (required, at most 128 characters, unique within the batch), input?, budget?, tags?, correlationKey? }.
string
default:"gate"
gate parks items over the concurrency cap; reject refuses them.
object
Batch-wide default; an item’s own budget wins.
string
Pin a version.
Response 202 with { "batchId", "accepted", "items": [{ idempotencyKey, runId?, status, code?, gate?, issues? }] }, items in the order sent; status is queued, gated, replayed, or rejected, and accepted counts the first two. Errors 400 BATCH_TOO_LARGE (max, count) or BATCH_ITEM_INVALID (index, issues; also a duplicate idempotencyKey); 404 WORKFLOW_NOT_FOUND; 409 RUNS_IN_FLIGHT under forbid, or ORG_PURGING, either of which refuses the whole batch; 413 PAYLOAD_TOO_LARGE; 503 CONTROL_UNAVAILABLE, retry with the same keys. Equivalent: Workflows.startBatch().

GET /workflows/:agentId/runs

Lists runs on an agent, newest first by default. Rows carry metadata and counts, never outputs.
string
Comma-separated statuses.
string
Only runs of this definition.
string
One trigger value.
integer
Unix milliseconds; runs updated at or after it.
string
Runs of one batch start.
string
Exact match.
string
Repeatable, at most 10.
integer
Terminal runs only; maxDurationMs likewise.
string
default:"-createdAt"
-createdAt, createdAt, -durationMs, or durationMs.
string
nextCursor from the previous page.
integer
default:"50"
From 1 to 100.
Response 200 with { "items": [...run summaries], "nextCursor": string | null }. 400 BAD_CURSOR, CORRELATION_KEY_INVALID, or VALIDATION_FAILED with the offending parameter in issues[].path. Equivalent: lua workflows runs --workflow <name> --status failed, Workflows.list().

GET /workflows/:agentId/runs/:runId

Returns one run.
string
default:"summary"
full adds inputs and outputs for callers who may read them, plus receipt and notify for the creator or an org:manage holder.
Response 200 with the run detail. 404 RUN_NOT_FOUND; any other value of fields is 400 VALIDATION_FAILED. Equivalent: lua workflows status <runId> --steps, Workflows.get().

DELETE /workflows/:agentId/runs/:runId

Erases a terminal run with its events, steps, signals, and artifacts. Creator or organization admin (org:manage), person only. Response 202 with { "accepted": true, "purgeId" }. 403 APPROVAL_REQUIRES_HUMAN or NOT_RUN_CREATOR; 404 RUN_NOT_FOUND; 409 RUN_NOT_TERMINAL with nextAction: "cancel"; 501 ERASE_UNAVAILABLE; 503 CONTROL_UNAVAILABLE. Equivalent: lua workflows delete-run <runId> --yes.

Recovery

POST /workflows/:agentId/runs/:runId/cancel

Asks a run to stop at the next step boundary, and escalates to a force cancel when asked again after forceAvailableAt. Creator or a workflows:execute holder.
string
default:"request"
request or force.
string
At most 2,000 characters, kept for audit.
Response 200 with the verdict { "cancelRequested", "state", "status", "transitioned", "requestedAt"?, "nextAction": "cancel_again" | "force" | "none", "forceAvailableAt"?, "forced"? }. A force before forceAvailableAt is a 200 with transitioned: false and nextAction: "cancel_again", and a terminal run is a 200 with state: "terminal" and nextAction: "none"; neither is an error. 403 NOT_RUN_CREATOR; 404 RUN_NOT_FOUND; 503 CONTROL_UNAVAILABLE. Equivalent: lua workflows cancel <runId> --reason "wrong input", Workflows.cancel().

POST /workflows/:agentId/runs/:runId/steps/:stepId/retry

Re-arms a step that failed past its retries, parked on an exception gate, or is on a billing hold.
string
At most 2,000 characters.
boolean
Restart the step’s automatic retry budget; the attempt number keeps counting up. Ignored on a gate or billing park.
Response 200 with { "retried": true, "attempt", "runStatus", "remainingParks" }, or the no-op { "retried": false, "reason": "already_retried" | "already_resolved", "recorded": { at?, by?, outcome? }, "runStatus" } when a decision already closed the park. 404 RUN_NOT_FOUND or STEP_NOT_FOUND; 409 STEP_NOT_PARKED (stepId, status), STEP_RETRY_CAP (a retry that would run past attempt 20: stepId, attempt, toAttempt, maxAttempts), or RUN_TERMINAL. Equivalent: lua workflows retry-step <runId> --step <stepId> --note "vendor back up".

POST /workflows/:agentId/runs/:runId/steps/:stepId/resolve

Resolves a parked step by skipping it, completing it with an output you supply, or failing it. Person only.
string
default:"complete"
skip, complete, or fail.
any
Required for complete, ignored otherwise; at most 256 KB serialized; validated against the step’s outputSchema.
string
At most 2,000 characters.
Response 200 with { "resolved": true, "outcome", "runStatus", "remainingParks" }, or the no-op { "resolved": false, "reason": "already_resolved" | "already_retried", "recorded", "runStatus" }. 400 VALIDATION_FAILED (output-required) or RESOLVE_OUTPUT_INVALID (stepId, issues); 403 APPROVAL_REQUIRES_HUMAN or NOT_RUN_CREATOR; 404 RUN_NOT_FOUND or STEP_NOT_FOUND; 409 STEP_NOT_PARKED or RUN_TERMINAL; 413 OUTPUT_TOO_LARGE. Equivalent: lua workflows resolve-step <runId> --step <stepId> --outcome complete --output '{"sent":0}'.

POST /workflows/:agentId/runs/:runId/budget

Raises the budget of a run parked on a budget gate. Creator or organization admin.
number
New credit cap, above 0.
integer
New step cap, at least 1.
integer
New Job-tier seconds cap, at least 1.
integer
New wall-clock cap, at least 1.
string
At most 2,000 characters.
Response 200 with { "raised": true, "budget", "runStatus", "resumed" }, or { "raised": false, "reason": "already_raised", "runStatus" }; resumed: false means the raise still does not cover the next step’s reserve and the run parked again. 400 VALIDATION_FAILED (no cap sent, or a cap at or below the current one) or CAP_EXCEEDED (above the organization’s ceiling); 403 NOT_RUN_CREATOR; 409 BUDGET_NOT_RAISABLE when the run is not parked on a budget. Equivalent: lua workflows raise-budget <runId> --credits 20. Workflows.raiseBudget() is typed but never works: deployed code gets not_implemented (501) and lua test gets WORKFLOWS_API_UNAVAILABLE.

Error codes

Every code the four workflow pages can answer, by status.

See also

  • Workflows — the same operations from agent code, and what the deployed runtime serves
  • lua workflows — every action, with exit codes 4 to 8 for run outcomes
  • Workflow builder — the definitions these routes run
  • Operate runs — the day-two loop: watch, retry, resolve, export
  • Workflow approvals — answering the human input a run waits for