Skip to content

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.

The Instances list for the Error Catch Demo flow: one run, 5932DC8F-3241-4E1A-B372-2A5A89B6DAFA, with its HAS ERROR column ticked and its STATUS column reading COMPLETED.

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