Run a workflow
POST/
Starts a run of a workflow. The JSON you send becomes the trigger’s data.
Needs an API token with the Run permission for workflows (workflow:run), from Settings › Workspace › API tokens.
Parameters
Path
idRequired · UUIDThe workflow’s ID.
Query
environmentOptional · stringproductionruns the published version,testthe draft. Left out:productionwhen the workflow is live with no unpublished changes, elsetest. A production run recordstriggerType: "api", a test runmanual. List runs leaves test runs out unless you sendenvironment=testorall.waitOptional · integerSeconds to wait for the run to finish, a whole number from 0 to 30. The default, 0, answers at once. Anything else is a
400, and no run is made.
Headers
Idempotency-KeyOptional · stringUp to 255 characters. Send the same request with the same key within 24 hours of a successful answer, and you get that answer again, not a second run. See Idempotency.
Body
Any JSON object, or nothing. A webhook workflow reads {"orderId": …} as {{trigger.body.orderId}}.
Request
GORUNNER="https://api.gorunner.io"
# An API token, from Settings › Workspace › API tokens
GORUNNER_TOKEN="grt_…"
WORKFLOW_ID="your-workflow-id" # replace: from the workflow's address
URL="$GORUNNER/api/workflows/$WORKFLOW_ID/run"
curl -X POST "$URL?environment=production" \
-H "Authorization: Bearer $GORUNNER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"orderId": "A-1001"}'const GORUNNER = "https://api.gorunner.io";
const TOKEN = process.env.GORUNNER_TOKEN;
const ID = process.env.WORKFLOW_ID; // the workflow's ID
const headers = {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
};
const body = JSON.stringify({ orderId: "A-1001" });
const res = await fetch(
`${GORUNNER}/api/workflows/${ID}/run` +
"?environment=production",
{ method: "POST", headers, body },
);
const { data: run } = await res.json(); // 202import os, requests
GORUNNER = "https://api.gorunner.io"
TOKEN = os.environ["GORUNNER_TOKEN"]
ID = os.environ["WORKFLOW_ID"] # the workflow's ID
headers = {"Authorization": f"Bearer {TOKEN}"}
body = {"orderId": "A-1001"}
res = requests.post(
f"{GORUNNER}/api/workflows/{ID}/run",
params={"environment": "production"},
headers=headers, json=body,
)
run = res.json()["data"] # 202Response · 202 Accepted
{
"data": {
"id": "8f0c41d2-6b7e-4c1a-9d3f-2a5b7c9e1f04",
"workflowId": "3b1e5c2a-7d4f-4e8b-a1c6-5f2d9b0e7a31",
"workspaceId": "c41a7e02-9b3d-4f6a-8e15-7d2c0b9a4f68",
"orgId": "e9d2b6f1-3a7c-4d05-b8e4-1c6f2a9d7b30",
"status": "pending",
"triggerType": "api",
"triggerData": {
"body": { "orderId": "A-1001" },
"headers": {
"content-type": "application/json"
},
"method": "POST",
"query": {}
},
"environment": "production",
"versionId": "f20f671a-2c8e-4b9d-a7f1-6e3b0d5c9a12",
"startedAt": "2026-10-25T09:00:00.317Z",
"firstStartedAt": null,
"tags": null
}
}Get the result
A 202 means the run has started, not finished. data.id is the run’s ID.
Wait in the same call. Add wait=30. A run that finishes in time answers 200, with each step’s output. Otherwise you get 202, also for a run that pauses on a Delay or an approval. When the server already holds as many waiting callers as it can, the 202 comes at once, with X-Run-Wait: overloaded and Location: /api/runs/<id>. The run is never cut short by wait.
Ask later. Call Get a run with the run’s ID.
Request
curl -X POST "$URL?wait=30" \
-H "Authorization: Bearer $GORUNNER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"orderId": "A-1001"}'// GORUNNER, ID, headers and body as in the first sample
const res = await fetch(
`${GORUNNER}/api/workflows/${ID}/run?wait=30`,
{ method: "POST", headers, body },
);
// 200: finished · 202: still going, ask later# GORUNNER, ID, headers and body as in the first sample
res = requests.post(
f"{GORUNNER}/api/workflows/{ID}/run",
params={"wait": 30},
headers=headers, json=body,
)
# 200: finished · 202: still going, ask laterResponse · 200 OK, shortened
{
"data": {
"id": "8f0c41d2-6b7e-4c1a-9d3f-2a5b7c9e1f04",
"status": "completed",
"finishedAt": "2026-10-25T09:00:00.338Z",
"durationMs": 15,
"steps": [
{
"nodeId": "check_stock",
"status": "completed",
"outputData": { "inStock": true }
}
]
}
}Responses
- 200Finished
The run finished within
wait. The body is the finished run. - 202Accepted
The run is queued or still going.
A parameter is not valid, such as
environment=liveorwait=31. No run is made.An
Idempotency-Keylonger than 255 characters.- 403
FORBIDDENThe token lacks the Run permission.
The workspace’s account is suspended. Nothing starts until it is resumed.
- 404
NOT_FOUNDNo workflow with this ID in the token’s workspace, or an unknown
versionIdorretryOf. - 409
CONFLICTYou asked for
production, but the workflow isn’t live (a draft or paused), or its live version can’t be read. Also: a request with thisIdempotency-Keyis still in flight. The workspace’s account was deleted.
The workflow can’t run here, such as a step on an app this installation doesn’t have. No run is made.
This
Idempotency-Keywas already used with a different request.fromNodeisn’t a step on the main line.A step uses an app this workspace may not use. The message names the step.
The workflow is at its concurrency limit and is set to Refuse.
Too many runs are already waiting. Try again later.
GoRunner couldn’t check which apps may run. Nothing started. Try again.
The account’s state couldn’t be read. Nothing started. Try again.
With
wait: the run finished, but couldn’t be read back in time. It is intact: read it with Get a run.
Any call can also answer: 401 UNAUTHORIZED · 401 TOKEN_ENDED · 403 TOKEN_SURFACE_OFF · 413 PAYLOAD_TOO_LARGE · 415 INVALID_CONTENT_TYPE · 429 RATE_LIMITED · 503 PAUSED · 503 SERVICE_UNAVAILABLE. See Errors.
Response · 429 Too Many Requests
{
"error": {
"code": "CONCURRENCY_LIMIT",
"message": "This workflow is already running its limit of 2 at once. Try again when a run finishes."
}
}Every field
The trigger payload. Any JSON object; it is reshaped into the delivery shape of this workflow’s trigger kind unless __shaped is true.
Body
__shapedOptional · booleanSet true when the object is ALREADY in delivery shape (a replayed run’s
triggerData, or a saved test payload): the key is stripped and the rest passes through verbatim, somethod/headers/querysurvive instead of being re-wrapped underbody.
Response · 200 OK
dataobjectOne execution of a workflow. The same schema serves the list and the detail read, but the two carry different subsets: the LIST adds
workflowName,workflowTriggerType,stepTotal,stepFailedand the fields marked LIST only, and never includessteps; the DETAIL includessteps. Both carry the version fields and the retry and parent links. An IN-FLIGHT run read from the detail endpoint is served from a live cache and is completed from its run row (its start, environment, versions, retry and parent links, workspace and organization), so it reads as a settled run does, except thattagsisnulluntil the worker saves it.idUUIDThe run id.
workflowIdUUIDThe workflow that ran.
workspaceIdUUIDTenant workspace, denormalized onto the run.
orgIdUUIDTenant organization.
statusstringA run’s lifecycle status. TERMINAL values — stop polling when you see one — are
completed,failedandcancelled. NON-TERMINAL values arewaiting(accepted and waiting in line for a free slot: its workflow’s or its organization’s runs-at-once limit, or a free worker —waitingFor,waitingLimitandlinePositionsay which and where; it starts in order, and cancelling it takes it out of the line),pending(a worker has it or is about to:claimedAtsays when a worker took it),running(a resumed run executing after its wait) andpaused(suspended on a durable wait: an approval/webhook resume, or a scheduled resume time). A paused run can remain paused indefinitely; it resumes only when its wait is satisfied, or ends if you cancel it.waitingis new in v2026.10.4: before it, a run over a limit was refused instead.One of
waiting,pending,running,completed,failed,cancelled,paused.triggerTypestringHow THIS run began — an open string, not the workflow’s trigger vocabulary. Known values:
manual(editor test),webhook,cron,poll,event,error(an error-handler workflow),run_finished,api(started throughPOST /api/workflows/{id}/run),mcp(the same door, by an AI app’s tool),subflow(started by a parent flow’s Sub-flow step).triggerDataobject or array or string or number or boolean or nullThe payload the trigger delivered — for a webhook run an object with
body,headers,query,method. This is what{{trigger...}}expressions resolved against.startedAttimeWhen the run was accepted. It may then wait in line for a free slot before a worker starts it (
firstStartedAt).firstStartedAttime or nullWhen a worker first started the run.
durationMscounts from here, so the time fromstartedAtto this is the time the run waited in line. Null until a worker starts the run, and for a run started on v2026.10.5 or earlier or one that ran inline (a Sub-flow’s, an error handler’s), whosedurationMscounts fromstartedAt.finishedAttimeWhen the run settled: the moment its result was decided (not when its record was saved, which can be a moment later). Present as soon as the run is finished, also while its record is still being saved. Absent while non-terminal.
durationMsintegerHow long the run took, in milliseconds: from the first time a worker started the run (
firstStartedAt, elsestartedAt) to its finish. Waits inside the run (a Delay, an Approval) count; the time it waited in the line for a free slot does not. A run cancelled while it waited has none. Rounded to the millisecond. Present as soon as the run is finished, also while its record is still being saved. Absent while non-terminal.errorMessagestringWhy the run failed (or the cancellation reason). Absent/empty otherwise.
tagsarray of strings or nullTags attached during execution by the Tags app. Filterable via
GET /api/runs?tag=.[]when it has none.nullwhile the run is read from its live cache: in flight until the worker saves it, and for a moment after it is cancelled. A run still waiting in line, read from its record, says[].environmentstringWhere the run came from.
testfor anything the editor started;productionfor real trigger traffic and forPOST /api/workflows/{id}/run?environment=production. Deliberately independent oftriggerType— a listen-captured run is both a webhook run and a test run.GET /api/runshidestestruns unless asked.One of
production,test.stepsarray of objectsThe executed steps, oldest first. Returned by
GET /api/runs/{id}only — never in the list. Step output lives here, keyed by each element’snodeId.idUUIDId of this step-run row. A step that suspended the run gets a new id when the run resumes: address a step by
nodeId.runIdUUIDThe run this step belongs to.
nodeIdstringThe definition step NAME (stable identifier, e.g.
parse_csv). Use this to find a step, notdisplayName.pieceIdstringThe app that ran, e.g.
http,code,csv.actionIdstringThe action within that app, e.g.
send_request,convert.displayNamestringThe human label shown on the canvas. Not an identifier — it can be duplicated and renamed.
statusstringA single step’s status inside a run.
skippedmeans the step was not executed (a branch arm not taken, or a step after a failure).pausedmarks the wait step a suspended run is currently sitting on. A step executing when its run is cancelled endsfailed("Cancelled while it ran."), and so does the step a cancelled run was held on. A step can befailedwhile the run iscompleted— that happens when the step’s error handling routed to an error path or continued.One of
pending,running,completed,failed,skipped,paused.inputDataobject or array or string or number or boolean or nullThe resolved input the step ran with, after expressions were evaluated. Normally an object of the form
{"config": { ...the step's resolved config... }}. Absent when the step never executed, and on a step that suspended the run (a top-level Approval, Delay or Wait for callback) once the run has resumed: itsoutputDatais kept, its input is not.outputDataobject or array or string or number or boolean or nullTHE STEP’S OUTPUT — whatever the app returned, arbitrary JSON. Absent when the step failed before producing output or never executed. Large payloads are offloaded to object storage during the run and rehydrated here transparently, so this is always inline JSON when present; when it cannot be read back, it is absent and
dataUnavailableis true.errorMessagestringWhy the step failed. Absent/empty when it did not.
retryCountintegerWhich attempt this record reflects (0 = first attempt).
startedAttimeWhen the step began. Absent if it never started.
finishedAttimeWhen the step settled. Absent while running or if it never finished.
durationMsintegerHow long this record’s attempt took, in milliseconds, rounded (0 = under half a millisecond; the dashboard says "<1 ms"). For a retried step it is the last attempt; for a step that ran more than once (a loop body) its last pass.
totalDurationMshas the step’s time in all.attemptsintegerHow many attempts the step’s last pass took, when it was retried (absent: one attempt; every field of this group is absent on runs recorded before it existed). This record is the last of them.
firstAttemptStartedAttimeWhen the first of
attemptsbegan. Present withattempts.passesintegerHow many times the step ran in the run, when more than once — a step inside a loop runs once per item. Absent: once. This record is the last pass.
totalDurationMsintegerThe step’s time in all, in milliseconds: each pass from its first attempt’s start to its finish (the waits between retries included), summed over its passes. Present with
attemptsorpasses.attemptErrorsarray of objectsThe earlier attempts of the last pass and why each failed, oldest first: at most the last 20, each error at most 1,000 characters. Present with
attempts.attemptintegerWhich attempt (1 = the first).
errorstringWhat it failed with.
attimeWhen it ended.
recoverystringIn a restarted run (
restarts), what the run did with this step:replayed, an earlier attempt finished it, its recorded result was used and it did not run again;rerun, it had started when the worker stopped and ran again (it only reads or computes);unknown, it changes something outside the run and was in flight when the worker stopped, so it was NOT sent again: it failed with "Outcome unknown" and its error handling decided what followed. Absent: an ordinary step.One of
replayed,rerun,unknown.runAttemptintegerWhich execution of a restarted run produced this record, when it was not the first (2 = the first restart). Absent: the first.
dataUnavailablebooleanPresent (true) when the step’s payload was offloaded to object storage and could not be read back: the step did produce
inputData/outputData, but it cannot be shown now (they are absent). Tells "produced nothing" apart from "cannot be read". Absent otherwise.deferredobjectOnly on a read with
large=defer: the step’sinputDataand/oroutputDatathis read left out because each is over 64 KB of JSON (the step a paused run waits on is never left out), each with its size. The value exists; read it withGET /api/runs/{id}?step=<nodeId>. Absent otherwise.inputobjectA value this read left out because it is large: its size in bytes of JSON, as stored, and for a step’s output its
head.bytesintegerThe value’s size in bytes of JSON.
headobjectA step OUTPUT’s facts, so a client can say what the step did without reading it (an HTTP status, an approval’s answer, a loop’s count): the output’s top-level keys — a scalar (up to 512 bytes) with its value, an object as its own scalar members, every other value null — at most 8 KB, scalars first. Absent for an input, the trigger data and an output that is not a JSON object. Never the output itself.
outputobjectA value this read left out because it is large: its size in bytes of JSON, as stored, and for a step’s output its
head.The same fields as
data.steps.deferred.inputabove.versionIdUUIDThe workflow’s latest published version at the moment the run started. Absent for a workflow that has never been published.
versionNumberintegerDisplay number of
versionId.executedVersionIdUUIDThe version whose definition ACTUALLY executed. ABSENT means the DRAFT ran (an editor test), so rendering
versionId's snapshot would show a graph that never ran. The list and the detail read carry it;GET /api/workflows/{id}/runsdoes not.executedVersionNumberintegerDisplay number of
executedVersionId.startedFromStepstringFor a partial ("run from here") run, the definition step NAME the run began at. Absent means the run started at its trigger, which is every ordinary run. Everything above that step was seeded, not executed — do not read its steps as having run. Detail read only: a row of
GET /api/runsleaves it out.retryOfRunIdUUIDThe run this one is a retry of. Set at creation and immutable.
retriedByRunIdUUIDThe most recent run that retried THIS one. The list and the detail read carry it.
parentRunIdUUIDFor a run started by another run’s Sub-flow step (
triggerTypesubflow), the run that called it. The list and the detail read carry it; absent for every other run and for sub-flow runs from before 2026-10.restartsintegerHow many times the run was offered to another worker because the one running it stopped (killed, out of memory, its host lost, or it lost the run’s lease). A restarted run continues from what the stopped attempt finished: a step it finished does not run again (its step record says
recoveryreplayed), and a step that changes something outside the run and was in flight is not sent again (recoveryunknown, failed with "Outcome unknown"). Absent: never restarted, and every run from before 2026-11.answerstringHow the run’s last wait on a person was settled: approved, rejected, or
timed_out(nobody answered before the step’s timeout and the run continued without an answer). List read only; absent when the run waited on no one, or its answer was given before 2026-10.One of
approved,rejected,timed_out.workflowNamestringThe workflow’s name. List read only.
workflowTriggerTypestringThe WORKFLOW’s configured trigger type (as opposed to this run’s
triggerType). List read only.workflowTriggerPiecestringThe PIECE the workflow starts with (
form,schedule,gmailtrigger), as opposed toworkflowTriggerType, which is how the platform DELIVERS it. Several apps share one type — a hosted form is delivered over a webhook — so the type alone cannot say what a person chose. Absent when the trigger has no app (manual). List read only.stepTotalintegerNumber of step rows recorded for this run. List read only — use it to avoid fetching the detail just to count steps.
stepFailedintegerNumber of those steps with status
failed. List read only.waitStepstringOnly on a
pausedrun: the definition step NAME that suspended the run.resumeModestringOnly on a
pausedrun:time= it resumes automatically atresumeAt;webhook= it waits for an external call (an approval) and will not resume on its own. An approval with a timeout is atimewait that is also answerable: readwaitingOnfor whether a person is waited on.One of
time,webhook.resumeAttimeOnly on a
pausedrun withresumeMode: time: when it will resume.waitingOnstringOnly on a
pausedrun:person= a resume link is outstanding (an approval, a webhook resume), so someone has to answer it;time= it carries on by itself (a Delay, or a link already answered and resuming). The list’swaitingfilter selects by this.One of
person,time.claimedAttimeOnly on an unfinished run a worker has taken: when it took it. A
pendingrun withclaimedAtis executing; one without it is about to be taken.waitingForstringOnly on a
waitingrun held by a runs-at-once limit:workflow= its workflow is at its limit (settings.concurrencyLimit),organization= its organization is (the limit set on Platform › Organizations or by the embed API). Absent when it only waits for a free worker.One of
workflow,organization.waitingLimitintegerOnly beside
waitingFor: the limit it waits on.linePositionintegerOnly on a
waitingrun: its place in its workflow’s line, 1 = next.statestringLIST only. The run’s state word:
runningcovers a run in a Delay; a rejected approval isworked(seedecision).One of
failed,held,running,queued,worked,cancelled.retryOfStartedAttimeLIST only. When the run this one retried started. Absent when it is gone.
retryOutcomestringLIST only. What retrying this run came to, over every retry descending from it:
workedif any worked, else an open one’s state, else the newest one’s. Absent when never retried.One of
failed,held,running,queued,worked,cancelled.retryAttimeLIST only. When the retry
retryOutcomeis about started.decisionstringLIST only. How the run’s last person-wait was settled (the same value as
answer).One of
approved,rejected,timed_out.decidedBystringLIST only. Who answered from the product, never an address: a member of the workspace by their name (
a memberwhen they gave none),a former memberfor anyone the workspace no longer has, ortoken {name}. Absent for an answer through the link itself, a timeout, one from before 2026-10-19, and to an embedded person (who reads onlydecision).decidedAttimeLIST only. When the last person-wait was settled.
heldSincetimeLIST only. A held run: when its current hold began. A run that held: when its last answered hold began.
heldMsintegerLIST only. Time held on a person over every answered hold.
durationMsincludes it, so the run’s own time isdurationMs - heldMs.publishedVersionNumberintegerLIST only. The workflow’s published version now. Absent for a draft.
publishedAttimeLIST only. When that version was published.
workflowStatusstringLIST only. The workflow’s status now; absent when it was deleted.
One of
active,paused,draft.failureobjectLIST only, failed runs. The step the error names and the first clause of the reason.
stepKeystringThe step’s key; absent when the message names no step.
stepstringIts display name in the version that ran (the draft for a test), else the key.
reasonstringThe first clause of the reason, without the step prefix, at most 160 characters.
parentWorkflowNamestringLIST only. A sub-flow run’s caller: its workflow’s name.
parentStartedAttimeLIST only. When the calling run started.
deferredobjectOnly on a read with
large=defer: the run’striggerData, when it was left out because it is over 64 KB of JSON, with its size. Read it withGET /api/runs/{id}?step=trigger. Absent otherwise.triggerDataobjectA value this read left out because it is large: its size in bytes of JSON, as stored, and for a step’s output its
head.The same fields as
data.steps.deferred.inputabove.
Response · 202 Accepted, A run
dataobjectA run. From
POST /api/workflows/{id}/runit arrivespendingwith nothing executed — pollGET /api/runs/{id}for the outcome and per-step detail.idUUIDThe run id. This is what a 202 gives you to poll or stream.
workflowIdUUIDworkspaceIdUUIDorgIdUUIDstatusstringpending= queued, nothing has executed.waiting(with?wait=) = accepted and still waiting in line for a free slot after the wait: a runs-at-once limit holds it, and it starts in order.paused= suspended on a durable wait (delay/approval) and will continue — it is in flight, not finished.One of
pending,running,completed,failed,cancelled,paused,waiting.triggerTypestringHow the run started.
api= a production run throughPOST /api/workflows/{id}/run;mcp= the same door, by an AI app’s tool.manual= a TEST run through that same endpoint (kept asmanualdeliberately — the builder’s step-data picker matches it literally). Alsowebhook(a hosted form’s submission too),cron,poll,error,run_finished,subflow.triggerDataThe trigger payload this run received, in delivery shape (for a webhook:
method/headers/query/body). This is exactly what{{trigger.…}}resolves against, and what you would replay as a__shapedbody.startedAttimeWhen the run was accepted. It may then wait in line for a free slot before a worker starts it (
firstStartedAt).firstStartedAttime or nullWhen a worker first started the run.
durationMscounts from here, so the time fromstartedAtto this is the time the run waited in line. Null until a worker starts the run, and for a run started on v2026.10.5 or earlier or one that ran inline (a Sub-flow’s, an error handler’s), whosedurationMscounts fromstartedAt.finishedAttime or nullAbsent while the run is in flight.
durationMsinteger or nullHow long the run took, in milliseconds: from the first time a worker started the run (
firstStartedAt, elsestartedAt) to its finish. Absent while the run is in flight.errorMessagestringtagsarray of strings or nullnullon the 202 of a run just accepted,[]when a finished run has none.environmentstringDeliberately independent of
triggerType— a listen-captured delivery is honestly a webhook run AND honestly a test.GET /api/runsdefaults to production only; this workflow-scoped list returns both.One of
test,production.versionIdUUID or nullThe latest published version at the moment the run started — NOT necessarily what executed.
versionNumberinteger or nullexecutedVersionIdUUID or nullWhat ACTUALLY executed. Neither answer of this shape carries it, whatever ran (the 202 without
?wait=,GET /api/workflows/{id}/runs): readGET /api/runs/{id}, where absent means the DRAFT ran, so renderingversionId's snapshot would show a graph that never ran.executedVersionNumberinteger or nullDisplay number of
executedVersionId; carried only where it is.startedFromStepstring or nullThe step a PARTIAL (
fromNode) run began at. Empty/absent means the run started at its trigger, which is every ordinary run — without it the trigger would be marked complete on a run that never executed it.retryOfRunIdUUID or nullLineage only, set from
?retryOf=.stepsarray of objectsPer-step results. NOT populated by the run list or by the 202 from the run endpoint — fetch
GET /api/runs/{id}. Each entry’soutputDatais that step’s output, keyed to the step bynodeId.
Response · 202 Accepted, One execution of a workflow
dataobjectOne execution of a workflow. The same schema serves the list and the detail read, but the two carry different subsets: the LIST adds
workflowName,workflowTriggerType,stepTotal,stepFailedand the fields marked LIST only, and never includessteps; the DETAIL includessteps. Both carry the version fields and the retry and parent links. An IN-FLIGHT run read from the detail endpoint is served from a live cache and is completed from its run row (its start, environment, versions, retry and parent links, workspace and organization), so it reads as a settled run does, except thattagsisnulluntil the worker saves it.idUUIDThe run id.
workflowIdUUIDThe workflow that ran.
workspaceIdUUIDTenant workspace, denormalized onto the run.
orgIdUUIDTenant organization.
statusstringA run’s lifecycle status. TERMINAL values — stop polling when you see one — are
completed,failedandcancelled. NON-TERMINAL values arewaiting(accepted and waiting in line for a free slot: its workflow’s or its organization’s runs-at-once limit, or a free worker —waitingFor,waitingLimitandlinePositionsay which and where; it starts in order, and cancelling it takes it out of the line),pending(a worker has it or is about to:claimedAtsays when a worker took it),running(a resumed run executing after its wait) andpaused(suspended on a durable wait: an approval/webhook resume, or a scheduled resume time). A paused run can remain paused indefinitely; it resumes only when its wait is satisfied, or ends if you cancel it.waitingis new in v2026.10.4: before it, a run over a limit was refused instead.One of
waiting,pending,running,completed,failed,cancelled,paused.triggerTypestringHow THIS run began — an open string, not the workflow’s trigger vocabulary. Known values:
manual(editor test),webhook,cron,poll,event,error(an error-handler workflow),run_finished,api(started throughPOST /api/workflows/{id}/run),mcp(the same door, by an AI app’s tool),subflow(started by a parent flow’s Sub-flow step).triggerDataobject or array or string or number or boolean or nullThe payload the trigger delivered — for a webhook run an object with
body,headers,query,method. This is what{{trigger...}}expressions resolved against.startedAttimeWhen the run was accepted. It may then wait in line for a free slot before a worker starts it (
firstStartedAt).firstStartedAttime or nullWhen a worker first started the run.
durationMscounts from here, so the time fromstartedAtto this is the time the run waited in line. Null until a worker starts the run, and for a run started on v2026.10.5 or earlier or one that ran inline (a Sub-flow’s, an error handler’s), whosedurationMscounts fromstartedAt.finishedAttimeWhen the run settled: the moment its result was decided (not when its record was saved, which can be a moment later). Present as soon as the run is finished, also while its record is still being saved. Absent while non-terminal.
durationMsintegerHow long the run took, in milliseconds: from the first time a worker started the run (
firstStartedAt, elsestartedAt) to its finish. Waits inside the run (a Delay, an Approval) count; the time it waited in the line for a free slot does not. A run cancelled while it waited has none. Rounded to the millisecond. Present as soon as the run is finished, also while its record is still being saved. Absent while non-terminal.errorMessagestringWhy the run failed (or the cancellation reason). Absent/empty otherwise.
tagsarray of strings or nullTags attached during execution by the Tags app. Filterable via
GET /api/runs?tag=.[]when it has none.nullwhile the run is read from its live cache: in flight until the worker saves it, and for a moment after it is cancelled. A run still waiting in line, read from its record, says[].environmentstringWhere the run came from.
testfor anything the editor started;productionfor real trigger traffic and forPOST /api/workflows/{id}/run?environment=production. Deliberately independent oftriggerType— a listen-captured run is both a webhook run and a test run.GET /api/runshidestestruns unless asked.One of
production,test.stepsarray of objectsThe executed steps, oldest first. Returned by
GET /api/runs/{id}only — never in the list. Step output lives here, keyed by each element’snodeId.idUUIDId of this step-run row. A step that suspended the run gets a new id when the run resumes: address a step by
nodeId.runIdUUIDThe run this step belongs to.
nodeIdstringThe definition step NAME (stable identifier, e.g.
parse_csv). Use this to find a step, notdisplayName.pieceIdstringThe app that ran, e.g.
http,code,csv.actionIdstringThe action within that app, e.g.
send_request,convert.displayNamestringThe human label shown on the canvas. Not an identifier — it can be duplicated and renamed.
statusstringA single step’s status inside a run.
skippedmeans the step was not executed (a branch arm not taken, or a step after a failure).pausedmarks the wait step a suspended run is currently sitting on. A step executing when its run is cancelled endsfailed("Cancelled while it ran."), and so does the step a cancelled run was held on. A step can befailedwhile the run iscompleted— that happens when the step’s error handling routed to an error path or continued.One of
pending,running,completed,failed,skipped,paused.inputDataobject or array or string or number or boolean or nullThe resolved input the step ran with, after expressions were evaluated. Normally an object of the form
{"config": { ...the step's resolved config... }}. Absent when the step never executed, and on a step that suspended the run (a top-level Approval, Delay or Wait for callback) once the run has resumed: itsoutputDatais kept, its input is not.outputDataobject or array or string or number or boolean or nullTHE STEP’S OUTPUT — whatever the app returned, arbitrary JSON. Absent when the step failed before producing output or never executed. Large payloads are offloaded to object storage during the run and rehydrated here transparently, so this is always inline JSON when present; when it cannot be read back, it is absent and
dataUnavailableis true.errorMessagestringWhy the step failed. Absent/empty when it did not.
retryCountintegerWhich attempt this record reflects (0 = first attempt).
startedAttimeWhen the step began. Absent if it never started.
finishedAttimeWhen the step settled. Absent while running or if it never finished.
durationMsintegerHow long this record’s attempt took, in milliseconds, rounded (0 = under half a millisecond; the dashboard says "<1 ms"). For a retried step it is the last attempt; for a step that ran more than once (a loop body) its last pass.
totalDurationMshas the step’s time in all.attemptsintegerHow many attempts the step’s last pass took, when it was retried (absent: one attempt; every field of this group is absent on runs recorded before it existed). This record is the last of them.
firstAttemptStartedAttimeWhen the first of
attemptsbegan. Present withattempts.passesintegerHow many times the step ran in the run, when more than once — a step inside a loop runs once per item. Absent: once. This record is the last pass.
totalDurationMsintegerThe step’s time in all, in milliseconds: each pass from its first attempt’s start to its finish (the waits between retries included), summed over its passes. Present with
attemptsorpasses.attemptErrorsarray of objectsThe earlier attempts of the last pass and why each failed, oldest first: at most the last 20, each error at most 1,000 characters. Present with
attempts.attemptintegerWhich attempt (1 = the first).
errorstringWhat it failed with.
attimeWhen it ended.
recoverystringIn a restarted run (
restarts), what the run did with this step:replayed, an earlier attempt finished it, its recorded result was used and it did not run again;rerun, it had started when the worker stopped and ran again (it only reads or computes);unknown, it changes something outside the run and was in flight when the worker stopped, so it was NOT sent again: it failed with "Outcome unknown" and its error handling decided what followed. Absent: an ordinary step.One of
replayed,rerun,unknown.runAttemptintegerWhich execution of a restarted run produced this record, when it was not the first (2 = the first restart). Absent: the first.
dataUnavailablebooleanPresent (true) when the step’s payload was offloaded to object storage and could not be read back: the step did produce
inputData/outputData, but it cannot be shown now (they are absent). Tells "produced nothing" apart from "cannot be read". Absent otherwise.deferredobjectOnly on a read with
large=defer: the step’sinputDataand/oroutputDatathis read left out because each is over 64 KB of JSON (the step a paused run waits on is never left out), each with its size. The value exists; read it withGET /api/runs/{id}?step=<nodeId>. Absent otherwise.inputobjectA value this read left out because it is large: its size in bytes of JSON, as stored, and for a step’s output its
head.bytesintegerThe value’s size in bytes of JSON.
headobjectA step OUTPUT’s facts, so a client can say what the step did without reading it (an HTTP status, an approval’s answer, a loop’s count): the output’s top-level keys — a scalar (up to 512 bytes) with its value, an object as its own scalar members, every other value null — at most 8 KB, scalars first. Absent for an input, the trigger data and an output that is not a JSON object. Never the output itself.
outputobjectA value this read left out because it is large: its size in bytes of JSON, as stored, and for a step’s output its
head.The same fields as
data.steps.deferred.inputabove.versionIdUUIDThe workflow’s latest published version at the moment the run started. Absent for a workflow that has never been published.
versionNumberintegerDisplay number of
versionId.executedVersionIdUUIDThe version whose definition ACTUALLY executed. ABSENT means the DRAFT ran (an editor test), so rendering
versionId's snapshot would show a graph that never ran. The list and the detail read carry it;GET /api/workflows/{id}/runsdoes not.executedVersionNumberintegerDisplay number of
executedVersionId.startedFromStepstringFor a partial ("run from here") run, the definition step NAME the run began at. Absent means the run started at its trigger, which is every ordinary run. Everything above that step was seeded, not executed — do not read its steps as having run. Detail read only: a row of
GET /api/runsleaves it out.retryOfRunIdUUIDThe run this one is a retry of. Set at creation and immutable.
retriedByRunIdUUIDThe most recent run that retried THIS one. The list and the detail read carry it.
parentRunIdUUIDFor a run started by another run’s Sub-flow step (
triggerTypesubflow), the run that called it. The list and the detail read carry it; absent for every other run and for sub-flow runs from before 2026-10.restartsintegerHow many times the run was offered to another worker because the one running it stopped (killed, out of memory, its host lost, or it lost the run’s lease). A restarted run continues from what the stopped attempt finished: a step it finished does not run again (its step record says
recoveryreplayed), and a step that changes something outside the run and was in flight is not sent again (recoveryunknown, failed with "Outcome unknown"). Absent: never restarted, and every run from before 2026-11.answerstringHow the run’s last wait on a person was settled: approved, rejected, or
timed_out(nobody answered before the step’s timeout and the run continued without an answer). List read only; absent when the run waited on no one, or its answer was given before 2026-10.One of
approved,rejected,timed_out.workflowNamestringThe workflow’s name. List read only.
workflowTriggerTypestringThe WORKFLOW’s configured trigger type (as opposed to this run’s
triggerType). List read only.workflowTriggerPiecestringThe PIECE the workflow starts with (
form,schedule,gmailtrigger), as opposed toworkflowTriggerType, which is how the platform DELIVERS it. Several apps share one type — a hosted form is delivered over a webhook — so the type alone cannot say what a person chose. Absent when the trigger has no app (manual). List read only.stepTotalintegerNumber of step rows recorded for this run. List read only — use it to avoid fetching the detail just to count steps.
stepFailedintegerNumber of those steps with status
failed. List read only.waitStepstringOnly on a
pausedrun: the definition step NAME that suspended the run.resumeModestringOnly on a
pausedrun:time= it resumes automatically atresumeAt;webhook= it waits for an external call (an approval) and will not resume on its own. An approval with a timeout is atimewait that is also answerable: readwaitingOnfor whether a person is waited on.One of
time,webhook.resumeAttimeOnly on a
pausedrun withresumeMode: time: when it will resume.waitingOnstringOnly on a
pausedrun:person= a resume link is outstanding (an approval, a webhook resume), so someone has to answer it;time= it carries on by itself (a Delay, or a link already answered and resuming). The list’swaitingfilter selects by this.One of
person,time.claimedAttimeOnly on an unfinished run a worker has taken: when it took it. A
pendingrun withclaimedAtis executing; one without it is about to be taken.waitingForstringOnly on a
waitingrun held by a runs-at-once limit:workflow= its workflow is at its limit (settings.concurrencyLimit),organization= its organization is (the limit set on Platform › Organizations or by the embed API). Absent when it only waits for a free worker.One of
workflow,organization.waitingLimitintegerOnly beside
waitingFor: the limit it waits on.linePositionintegerOnly on a
waitingrun: its place in its workflow’s line, 1 = next.statestringLIST only. The run’s state word:
runningcovers a run in a Delay; a rejected approval isworked(seedecision).One of
failed,held,running,queued,worked,cancelled.retryOfStartedAttimeLIST only. When the run this one retried started. Absent when it is gone.
retryOutcomestringLIST only. What retrying this run came to, over every retry descending from it:
workedif any worked, else an open one’s state, else the newest one’s. Absent when never retried.One of
failed,held,running,queued,worked,cancelled.retryAttimeLIST only. When the retry
retryOutcomeis about started.decisionstringLIST only. How the run’s last person-wait was settled (the same value as
answer).One of
approved,rejected,timed_out.decidedBystringLIST only. Who answered from the product, never an address: a member of the workspace by their name (
a memberwhen they gave none),a former memberfor anyone the workspace no longer has, ortoken {name}. Absent for an answer through the link itself, a timeout, one from before 2026-10-19, and to an embedded person (who reads onlydecision).decidedAttimeLIST only. When the last person-wait was settled.
heldSincetimeLIST only. A held run: when its current hold began. A run that held: when its last answered hold began.
heldMsintegerLIST only. Time held on a person over every answered hold.
durationMsincludes it, so the run’s own time isdurationMs - heldMs.publishedVersionNumberintegerLIST only. The workflow’s published version now. Absent for a draft.
publishedAttimeLIST only. When that version was published.
workflowStatusstringLIST only. The workflow’s status now; absent when it was deleted.
One of
active,paused,draft.failureobjectLIST only, failed runs. The step the error names and the first clause of the reason.
stepKeystringThe step’s key; absent when the message names no step.
stepstringIts display name in the version that ran (the draft for a test), else the key.
reasonstringThe first clause of the reason, without the step prefix, at most 160 characters.
parentWorkflowNamestringLIST only. A sub-flow run’s caller: its workflow’s name.
parentStartedAttimeLIST only. When the calling run started.
deferredobjectOnly on a read with
large=defer: the run’striggerData, when it was left out because it is over 64 KB of JSON, with its size. Read it withGET /api/runs/{id}?step=trigger. Absent otherwise.triggerDataobjectA value this read left out because it is large: its size in bytes of JSON, as stored, and for a step’s output its
head.The same fields as
data.steps.deferred.inputabove.
How does the body look to each kind of trigger?
GoRunner reshapes the body to look like a real delivery from the workflow’s trigger. A webhook workflow gets it as trigger.body, with trigger.headers, trigger.query and trigger.method beside it. A schedule workflow gets a schedule-shaped payload.
To send trigger data that is already in that shape, such as a past run’s triggerData, add "__shaped": true at the top level. GoRunner removes that key and passes the rest through as it is.
Can I start from a step, run an older version, or mark a retry?
fromNodeOptional · stringStarts at this step, with earlier steps’ values taken from test data. It must be a step on the main line: one inside a branch or loop, or an unknown one, answers 422. The run records it as
startedFromStep. Test runs only.versionIdOptional · UUIDRuns this published version. It must belong to this workflow. With
production, it must be the live version.retryOfOptional · UUIDRecords this run as a retry of that run. It changes nothing about what runs: pass the original’s data,
versionIdandfromNodeyourself.
Passing fromNode or versionId makes test the default environment.
What happens when the workflow is already busy?
A production run counts toward the workflow’s concurrency limit. By default, a run over the limit waits in line and starts in order.
If the workflow is set to Refuse, the call fails with 429 CONCURRENCY_LIMIT. Try again when a run finishes.
Waiting runs are capped at 10,000 per organization and 1,000 per workflow. A self-hosted installation can change both (RUNS_WAITING_PER_ORG, RUNS_WAITING_PER_WORKFLOW). Test runs count toward the caps, but ignore the concurrency limit.