Checking a Run's Status¶
If your code started a run of a FlowRunner™ flow with
Call Flow (Non-Blocking), it holds an executionId and nothing else. This
endpoint is how it finds out what became of that run: one GET request answers whether the run is
still going, whether it finished, how long it took, and whether anything failed along the way. It
works for any run whose id you hold, however the run started. To read what an individual block
produced, use Retrieving Block Results. The examples on this page use a flow named
Order Lookup, started with an orderId.
Endpoint (GET)¶
https://api.flowrunner.ai/
{workspace-id}/
{api-key}/
automation/flow/
{flow-id}/
execution/
{execution-id}
| Placeholder | Value | Where it comes from |
|---|---|---|
{workspace-id} |
The workspace the flow belongs to | Workspace Settings ▸ General ▸ Credentials - see Workspace Settings |
{api-key} |
The workspace's API key | Same place |
{flow-id} |
The flow's id, case-sensitive | The browser's address bar while the flow is open in FlowRunner - the segment after /flow/. Renaming the flow does not change this URL |
{execution-id} |
The run you are asking about | The executionId that Call Flow (Non-Blocking) returned when it started the run. Every block can also read it under Flow Context |
Both ids are required, and they have to belong together: a real execution id under a different flow's
id is refused with 28068, the same answer as an id that does not exist at all.
Only GET. POST, PUT, and DELETE are refused with HTTP 405. HEAD is answered like
GET with an empty body - unlike the Call Flow endpoints, nothing here starts a run.
The flow does not have to be LIVE. This endpoint reads run history, so it keeps answering for runs of a flow you have since stopped or replaced. It answers for as long as run history keeps the run - see Inspecting a Run.
The URL is a credential. It embeds your workspace's id and API key, and it returns what your runs did, so keep it server-side, out of client code and untrusted configs. The key belongs to the whole workspace, not to this flow: see Workspace Settings.
Making the call¶
Copy, fill in the two credential placeholders, and run - put your flow's id in place of the flow id and the run's id in place of the execution id:
curl "https://api.flowrunner.ai/{workspace-id}/{api-key}/automation/flow/07E7DA91-CE96-4621-846C-747A26446263/execution/DC991247-7616-4EDD-9C6B-A25EF9BC5097"
The answer is a single JSON object, application/json, describing that one run:
{
"executionId": "DC991247-7616-4EDD-9C6B-A25EF9BC5097",
"flowName": "Order Lookup",
"flowVersion": 1,
"status": "COMPLETED",
"startedAt": "2026-08-25T23:42:41.199Z",
"finishedAt": "2026-08-25T23:42:41.712Z",
"durationMs": 513,
"completion": "NORMAL",
"hasErrors": false,
"handledErrorCount": 0,
"handledErrors": [],
"handledErrorsTruncated": false,
"error": null
}
flowName and flowVersion identify what actually ran: the Call Flow URL carries no version segment,
so a run executes whichever version was LIVE when it started.
Notice what is not here: the flow's own answer. This call reports how a run went, never what it returned - and neither does Retrieving Block Results, because a Return Result block has no result alias to fetch. When you need the value the flow produced, Call Flow (Blocking) waits for it and returns it as the response body.
The four statuses¶
status is the field to branch on. Two of its values mean the run is still open, two mean it is over:
status |
What it means |
|---|---|
RUNNING |
The run is working through its blocks. |
PENDING |
The run has paused and is waiting to be continued from outside - at an External Callback, for example. It stays here until something calls the callback address; see Activating an External Callback. Waiting that the run does on its own, such as a retry backing off, stays RUNNING. |
COMPLETED |
Over. The run reached the end of its path. |
TERMINATED |
Over. A failure no Handle Error caught stopped the run where it broke, or somebody stopped the run by hand from the flow's Instances tab. |
That run is the one whose JSON appears below: it reports COMPLETED and hasErrors at the same time,
and the Instances tab shows the same pair.
While the run is still open - RUNNING or PENDING - completion, finishedAt, and durationMs
are all null, because none of them are known yet. They fill in together the moment the run reaches
a terminal status, and completion reads NORMAL for a run that got to the end and ERROR for one
that did not.
Reading hasErrors¶
hasErrors is not the same question as status, and mistaking one for the other is the easy way to
get this wrong. A failure a Handle Error block catches
sends the run down a recovery path and the run carries on to a normal finish - so a run can be
COMPLETED and still report errors. Only an uncaught failure ends the run. The two fields together
give you three outcomes:
status |
hasErrors |
What happened |
|---|---|---|
COMPLETED |
false |
Clean run. Nothing failed. |
COMPLETED |
true |
The run finished normally, but at least one block failed and was caught. handledErrors lists what was caught. |
TERMINATED |
true |
A failure nobody caught stopped the run. error carries it. |
So do not retry a run because hasErrors is true - that flag on a COMPLETED run is recovery
working as designed. Branch on status to decide whether to retry, and read hasErrors to decide
whether to tell somebody.
A caught failure. handledErrorCount is how many were caught and handledErrors carries them.
Each one names the block that failed in source, carries FlowRunner's numeric code,
and repeats the underlying failure in message:
{
"status": "COMPLETED",
"completion": "NORMAL",
"hasErrors": true,
"handledErrorCount": 1,
"handledErrors": [
{
"source": "AI Agent",
"code": 28063,
"message": "401 {\"type\":\"error\",\"error\":{\"type\":\"authentication_error\"...",
"details": { "body": { "code": 28063, "message": "401 ..." } }
}
],
"handledErrorsTruncated": false,
"error": null
}
handledErrorsTruncated tells you whether that list is the whole story. It read false on every run
behind this page, each of which caught at most one failure; treat true as a signal that
handledErrors is a partial list and that handledErrorCount is the number to trust. The complete
record of a run is on its Instances tab either way - see
Inspecting a Run.
An uncaught failure. The run is TERMINATED, completion is ERROR, and the single error
object carries the same three fields - the block that broke, the code, and the message:
{
"status": "TERMINATED",
"completion": "ERROR",
"hasErrors": true,
"error": {
"source": "HTTP Request",
"code": 28105,
"message": "Error occurred while executing 'HTTP Request' block. Block execution failed with an error:\n\n{\n \"status\" : 503\n}"
}
}
A run that recovered from one failure before hitting a fatal one reports both: error for the
failure that stopped it, and handledErrors for whatever was caught earlier.
Polling a run to completion¶
The usual shape is to start the run, then ask this endpoint until status is no longer RUNNING or
PENDING:
FLOW=07E7DA91-CE96-4621-846C-747A26446263
EXEC=$(curl -s "https://api.flowrunner.ai/{workspace-id}/{api-key}/automation/flow/$FLOW/activate?orderId=1042" \
| sed 's/.*"executionId":"\([^"]*\)".*/\1/')
while :; do
STATUS=$(curl -s "https://api.flowrunner.ai/{workspace-id}/{api-key}/automation/flow/$FLOW/execution/$EXEC" \
| sed 's/.*"status":"\([^"]*\)".*/\1/')
case "$STATUS" in RUNNING|PENDING) sleep 2;; *) echo "$STATUS"; break;; esac
done
A run sitting at PENDING is not stuck: it is waiting for something outside FlowRunner to continue
it. A flow that can pause may sit there for a long time, so put a limit on how long your loop keeps
asking. When your code needs the flow's answer in the same request and the run
is short, Call Flow (Blocking) waits for you instead of you polling.
Errors¶
A refusal comes back as HTTP 400 or 404 with a JSON code, message, and details. A wrong method
is refused at the HTTP level with no code.
{
"code": 28068,
"details": {},
"message": "Execution context with id '4CE29875-0BD4-4AE4-B75C-60FCC55F4436' not found."
}
| Code | HTTP | What it means | What to do |
|---|---|---|---|
28068 |
404 | No such run under that flow | Check both ids. This is also the answer when the execution id is real but belongs to a different flow, and when the run has aged out of run history |
9000 |
400 | No workspace with that id | Re-copy the Workspace ID from Workspace Settings ▸ General ▸ Credentials |
| - | 405 | POST, PUT, and DELETE are refused |
Use GET. The body is a problem report with title and detail, not a FlowRunner code |
Related¶
- Call Flow (Non-Blocking) - starting the run, and the
executionIdthis call needs - Call Flow (Blocking) - waiting for the flow's answer instead of polling for it
- Retrieving Block Results - reading what an individual block of the run produced
- Activating an External Callback - continuing a run that is sitting at
PENDING - Inspecting a Run - the same facts on screen, block by block
- Handling Errors - the recovery paths behind the
hasErrorsdistinction
