List a workflow’s runs
GET/
Returns one workflow’s runs, newest first: production and test together.
Needs an API token with the Read permission for runs (run:read).
Parameters
Path
idRequired · UUIDThe workflow’s ID.
Query
pageOptional · integerThe page, from 1.
limitOptional · integerRuns a page, 1 to 100. The default is 20; anything else reads as 20, even 500.
Read it
There is no environment filter: tell runs apart by environment. Each row has its triggerData. A run waiting in line reads pending here.
versionId is the live version when the run started. For what actually ran (executedVersionId) and the steps, use Get a run.
Responses
- 200OK
A page of runs. None answers
[]. idisn’t a UUID.- 403
FORBIDDENThe token lacks the Read permission for runs.
- 404
NOT_FOUNDNo workflow with this ID in the token’s workspace.
The list couldn’t be read. Try again.
Any call can also answer: 401 UNAUTHORIZED · 401 TOKEN_ENDED · 403 TOKEN_SURFACE_OFF · 403 FEATURE_OFF · 403 EMBED_NOT_ALLOWED · 403 EMBED_NOT_AVAILABLE · 403 ACCOUNT_SUSPENDED · 429 RATE_LIMITED · 503 BUSY · 503 PAUSED · 503 SERVICE_UNAVAILABLE. See Errors.
Request
GORUNNER="https://api.gorunner.io"
GORUNNER_TOKEN="grt_…" # needs Read for runs
WORKFLOW_ID="your-workflow-id"
curl "$GORUNNER/api/workflows/$WORKFLOW_ID/runs" \
-H "Authorization: Bearer $GORUNNER_TOKEN"const GORUNNER = "https://api.gorunner.io";
const TOKEN = process.env.GORUNNER_TOKEN;
const WORKFLOW_ID = process.env.WORKFLOW_ID;
const res = await fetch(
`${GORUNNER}/api/workflows/${WORKFLOW_ID}/` +
`runs`,
{
headers: { Authorization: `Bearer ${TOKEN}` },
},
);
const { data } = await res.json();import os, requests
GORUNNER = "https://api.gorunner.io"
TOKEN = os.environ["GORUNNER_TOKEN"]
WORKFLOW_ID = os.environ["WORKFLOW_ID"]
res = requests.get(
f"{GORUNNER}/api/workflows/{WORKFLOW_ID}/runs",
headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]Response · 200 OK, shortened
{
"data": [
{
"id": "8f0c41d2-6b7e-4c1a-9d3f-2a5b7c9e1f04",
"workflowId": "3b1e5c2a-7d4f-4e8b-a1c6-5f2d9b0e7a31",
"status": "completed",
"triggerType": "api",
"startedAt": "2026-10-25T09:00:00Z",
"finishedAt": "2026-10-25T09:00:01Z",
"durationMs": 640,
"environment": "production",
"versionId": "f20f671a-2c8e-4b9d-a7f1-6e3b0d5c9a12",
"versionNumber": 3
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
}
}Every field
It takes no body.
Response · 200 OK
dataarray of objectsThe page’s runs;
[]when it has none.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.paginationobjectPaging envelope returned alongside
dataon list endpoints.pageintegerThe 1-based page that was returned.
limitintegerRows per page actually applied (not necessarily what you asked for — out-of-range values fall back to 20).
totalintegerTotal matching rows across all pages.
totalPagesinteger