> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flowmaticai.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflow Runs: Async Execution and Status Lifecycle

> Flowmatic workflow runs are asynchronous. Runs queue as PENDING and progress to RUNNING, then SUCCESS or FAILED. Learn how to enqueue and monitor runs.

A **run** is a single execution of a workflow. When you enqueue a run, Flowmatic acknowledges the request immediately and queues the work to be processed asynchronously — your API call returns in milliseconds regardless of how long the actual pipeline takes to execute. You then poll a status endpoint to track progress, inspect per-node results, and detect any failures. This page explains everything you need to know about run lifecycle, queuing behavior, and monitoring patterns.

***

## What Is a Run?

Each time you trigger a workflow, Flowmatic creates a run record that tracks:

* The overall execution status (`PENDING`, `RUNNING`, `SUCCESS`, or `FAILED`)
* A per-node status breakdown showing how each node in the graph fared
* Timestamps for when the run was enqueued and when it completed
* Error details if any node failed

A single workflow can have any number of runs over its lifetime. Runs are independent of each other — re-running a workflow creates a brand new run record and does not affect previous run history.

***

## Enqueueing a Run

To start a run, send a `POST` request to the runs endpoint for your workflow:

```bash title="Enqueue a run" theme={null}
curl -X POST https://api.flowmatic.io/api/workflows/wf_xyz789/run \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json"
```

Flowmatic responds immediately with `202 Accepted`:

```json title="202 Accepted — run enqueued" theme={null}
{
  "runId":  "run_def456",
  "status": "PENDING"
}
```

The `202` status code is intentional — it signals that the request was accepted but execution has not yet begun. Save the `runId`; you'll use it to poll for status updates.

<Note>
  Flowmatic never blocks on run completion. Even if a workflow has dozens of nodes and processes thousands of CSV rows, the enqueue call always returns within milliseconds.
</Note>

***

## Run Status Lifecycle

Every run moves through a defined set of statuses as it progresses from enqueue to completion.

```
PENDING → RUNNING → SUCCESS
                  ↘ FAILED
```

<CardGroup cols={2}>
  <Card title="PENDING" icon="clock">
    The run has been accepted and is waiting in the queue. No nodes have started executing yet. This status persists until the workflow runner picks up the job.
  </Card>

  <Card title="RUNNING" icon="circle-notch">
    The workflow runner has picked up the run and is actively executing nodes. The run will remain in this state until all nodes complete (or one fails fatally).
  </Card>

  <Card title="SUCCESS" icon="circle-check">
    All nodes executed successfully and the pipeline completed without errors. All emails have been sent (if an OUTPUT node was present).
  </Card>

  <Card title="FAILED" icon="circle-xmark">
    One or more nodes encountered an error that halted the pipeline. The per-node status breakdown will identify which node failed and provide error details.
  </Card>
</CardGroup>

### The SKIPPED Status

In addition to the four run-level statuses above, individual nodes within a run can have a `SKIPPED` status. This occurs when a workflow contains conditional branch nodes and the execution path did not pass through a particular branch. Skipped nodes are not errors — they simply were not needed for this execution path.

<Info>
  `SKIPPED` only applies to per-node statuses, not to the overall run status. A run that completes with some nodes `SKIPPED` can still end in `SUCCESS`.
</Info>

***

## Per-Node Status

When you fetch a run's details, the response includes a `nodes` array with the execution status of every node in the workflow. This granular breakdown is invaluable for debugging failures and understanding pipeline performance.

Each node status entry includes:

<ResponseField name="nodeId" type="string">
  The `id` of the node as defined in the workflow graph.
</ResponseField>

<ResponseField name="type" type="string">
  The node type (`TRIGGER`, `DATA_SOURCE`, `AI`, `FILTER`, or `OUTPUT`).
</ResponseField>

<ResponseField name="status" type="string">
  One of `PENDING`, `RUNNING`, `SUCCESS`, `FAILED`, or `SKIPPED`.
</ResponseField>

<ResponseField name="error" type="string | null">
  If the node's `status` is `FAILED`, this field contains a human-readable error message describing what went wrong. `null` for all other statuses.
</ResponseField>

<ResponseField name="startedAt" type="string | null">
  ISO 8601 timestamp of when this node began executing. `null` if the node hasn't started yet.
</ResponseField>

<ResponseField name="completedAt" type="string | null">
  ISO 8601 timestamp of when this node finished executing. `null` if the node hasn't completed yet.
</ResponseField>

### Example Run Detail Response

```json title="GET /api/workflows/runs/run_def456 — SUCCESS" theme={null}
{
  "runId":       "run_def456",
  "workflowId":  "wf_xyz789",
  "status":      "SUCCESS",
  "enqueuedAt":  "2024-06-01T10:00:00.000Z",
  "startedAt":   "2024-06-01T10:00:02.341Z",
  "completedAt": "2024-06-01T10:00:18.892Z",
  "nodes": [
    {
      "nodeId":      "trigger",
      "type":        "TRIGGER",
      "status":      "SUCCESS",
      "error":       null,
      "startedAt":   "2024-06-01T10:00:02.341Z",
      "completedAt": "2024-06-01T10:00:02.355Z"
    },
    {
      "nodeId":      "ds",
      "type":        "DATA_SOURCE",
      "status":      "SUCCESS",
      "error":       null,
      "startedAt":   "2024-06-01T10:00:02.356Z",
      "completedAt": "2024-06-01T10:00:03.120Z"
    },
    {
      "nodeId":      "ai",
      "type":        "AI",
      "status":      "SUCCESS",
      "error":       null,
      "startedAt":   "2024-06-01T10:00:03.121Z",
      "completedAt": "2024-06-01T10:00:14.775Z"
    },
    {
      "nodeId":      "f",
      "type":        "FILTER",
      "status":      "SUCCESS",
      "error":       null,
      "startedAt":   "2024-06-01T10:00:14.776Z",
      "completedAt": "2024-06-01T10:00:14.801Z"
    },
    {
      "nodeId":      "out",
      "type":        "OUTPUT",
      "status":      "SUCCESS",
      "error":       null,
      "startedAt":   "2024-06-01T10:00:14.802Z",
      "completedAt": "2024-06-01T10:00:18.892Z"
    }
  ]
}
```

And a run that encountered a failure at the `AI` node:

```json title="GET /api/workflows/runs/run_ghi012 — FAILED" theme={null}
{
  "runId":       "run_ghi012",
  "workflowId":  "wf_xyz789",
  "status":      "FAILED",
  "enqueuedAt":  "2024-06-01T11:00:00.000Z",
  "startedAt":   "2024-06-01T11:00:01.883Z",
  "completedAt": "2024-06-01T11:00:09.441Z",
  "nodes": [
    {
      "nodeId":      "trigger",
      "type":        "TRIGGER",
      "status":      "SUCCESS",
      "error":       null,
      "startedAt":   "2024-06-01T11:00:01.883Z",
      "completedAt": "2024-06-01T11:00:01.894Z"
    },
    {
      "nodeId":      "ds",
      "type":        "DATA_SOURCE",
      "status":      "SUCCESS",
      "error":       null,
      "startedAt":   "2024-06-01T11:00:01.895Z",
      "completedAt": "2024-06-01T11:00:02.670Z"
    },
    {
      "nodeId":      "ai",
      "type":        "AI",
      "status":      "FAILED",
      "error":       "LLM response did not conform to the declared output schema. Expected field 'customers' to be an array, got string.",
      "startedAt":   "2024-06-01T11:00:02.671Z",
      "completedAt": "2024-06-01T11:00:09.441Z"
    },
    {
      "nodeId":      "f",
      "type":        "FILTER",
      "status":      "SKIPPED",
      "error":       null,
      "startedAt":   null,
      "completedAt": null
    },
    {
      "nodeId":      "out",
      "type":        "OUTPUT",
      "status":      "SKIPPED",
      "error":       null,
      "startedAt":   null,
      "completedAt": null
    }
  ]
}
```

Notice that when `ai` fails, all downstream nodes (`f` and `out`) are set to `SKIPPED` — the execution path through those nodes was never taken.

***

## Queue Behavior

Runs for the same workflow are **serialized** — only one run executes at a time per workflow. If you enqueue a second run while the first is still `RUNNING`, the second run enters the queue with a status of `PENDING` and waits until the first run reaches a terminal state (`SUCCESS` or `FAILED`).

<Info>
  This serialization is per-workflow. You can run multiple different workflows concurrently without any queuing constraints between them.
</Info>

This behavior has some important implications:

* **Long-running AI nodes** can cause queue backup if you enqueue many runs in quick succession. Monitor queue depth if latency is a concern.
* **Batch testing** (enqueueing many runs at once) works fine — runs will execute sequentially in the order they were enqueued.
* A `FAILED` run does not block the queue. The next `PENDING` run will be picked up immediately after the failure is recorded.

***

## Polling for Run Completion

Since execution is asynchronous, you need to poll the run status endpoint until the run reaches a terminal state (`SUCCESS` or `FAILED`). The recommended approach is **exponential backoff with a maximum interval**.

### Polling Endpoint

```
GET /api/workflows/runs/:runId
```

### Bash Polling Example

```bash title="Poll until completion (bash)" theme={null}
RUN_ID="run_def456"
API_KEY="YOUR_API_KEY"
INTERVAL=2

while true; do
  RESPONSE=$(curl -s \
    -H "Authorization: Bearer $API_KEY" \
    "https://api.flowmatic.io/api/workflows/runs/$RUN_ID")

  STATUS=$(echo "$RESPONSE" | jq -r '.status')
  echo "$(date -u +%H:%M:%S) — Run status: $STATUS"

  if [ "$STATUS" = "SUCCESS" ] || [ "$STATUS" = "FAILED" ]; then
    echo "Run reached terminal state: $STATUS"
    echo "$RESPONSE" | jq .
    break
  fi

  sleep $INTERVAL
  # Double the interval up to a maximum of 30 seconds
  INTERVAL=$(( INTERVAL < 30 ? INTERVAL * 2 : 30 ))
done
```

### Polling Example with Node.js

```javascript title="Poll until completion (Node.js)" theme={null}
async function pollRunUntilComplete(runId, apiKey) {
  const url = `https://api.flowmatic.io/api/workflows/runs/${runId}`;
  let intervalMs = 2000;
  const maxIntervalMs = 30000;

  while (true) {
    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${apiKey}` }
    });

    if (!response.ok) {
      throw new Error(`Unexpected response: ${response.status}`);
    }

    const run = await response.json();
    console.log(`[${new Date().toISOString()}] Run status: ${run.status}`);

    if (run.status === "SUCCESS" || run.status === "FAILED") {
      return run;
    }

    await new Promise(resolve => setTimeout(resolve, intervalMs));
    intervalMs = Math.min(intervalMs * 2, maxIntervalMs);
  }
}

// Usage
const run = await pollRunUntilComplete("run_def456", process.env.FLOWMATIC_API_KEY);
if (run.status === "FAILED") {
  const failedNode = run.nodes.find(n => n.status === "FAILED");
  console.error(`Pipeline failed at node '${failedNode.nodeId}': ${failedNode.error}`);
}
```

<Tip>
  Start polling 1–2 seconds after enqueueing rather than immediately. A brand new run will always be `PENDING` for at least a brief moment, so the first request is rarely useful if made instantly.
</Tip>

***

## Batch Testing: Enqueuing Multiple Runs

You can enqueue multiple runs against the same workflow in rapid succession — for example, to test a pipeline with several different CSV uploads. Each enqueue call returns immediately with a unique `runId`. Flowmatic queues them all and processes them one by one in order.

```bash title="Enqueue three runs in parallel" theme={null}
WORKFLOW_ID="wf_xyz789"
API_KEY="YOUR_API_KEY"

# Enqueue run 1
RUN1=$(curl -s -X POST \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.flowmatic.io/api/workflows/$WORKFLOW_ID/run")
echo "Enqueued: $(echo $RUN1 | jq -r '.runId')"

# Enqueue run 2
RUN2=$(curl -s -X POST \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.flowmatic.io/api/workflows/$WORKFLOW_ID/run")
echo "Enqueued: $(echo $RUN2 | jq -r '.runId')"

# Enqueue run 3
RUN3=$(curl -s -X POST \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.flowmatic.io/api/workflows/$WORKFLOW_ID/run")
echo "Enqueued: $(echo $RUN3 | jq -r '.runId')"
```

All three runs will be in `PENDING` state initially. You can poll each `runId` independently to track their individual progress.

<Warning>
  Be mindful of rate limits when enqueuing many runs. If you're batch-testing, introduce a small delay (e.g., 100ms) between enqueue calls to avoid hitting the API rate limit for the enqueue endpoint.
</Warning>

***

## End-to-End Run Workflow

<Steps>
  <Step title="Upload your CSV">
    Send a `POST /api/uploads` request with your CSV file. Save the returned `uploadId` — you'll reference it in your `DATA_SOURCE` node.
  </Step>

  <Step title="Create or update your workflow">
    Send a `POST /api/workflows` (or `PATCH /api/workflows/:workflowId`) with your workflow graph, including the `uploadId` in the `DATA_SOURCE` node's `data` object.
  </Step>

  <Step title="Enqueue a run">
    Send `POST /api/workflows/:workflowId/run`. Save the `runId` from the `202` response.
  </Step>

  <Step title="Poll for completion">
    Call `GET /api/workflows/runs/:runId` in a loop with exponential backoff until `status` is `SUCCESS` or `FAILED`.
  </Step>

  <Step title="Inspect results">
    On `SUCCESS`, your emails have been sent. On `FAILED`, check the `nodes` array for the failed node's `error` field to understand what went wrong, then fix your workflow or data and enqueue a new run.
  </Step>
</Steps>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Workflows" icon="diagram-project" href="/concepts/workflows">
    Understand how to structure workflow graphs, nodes, and edges.
  </Card>

  <Card title="Node Types" icon="circle-nodes" href="/concepts/nodes">
    Deep-dive into node configuration and the template variable system.
  </Card>
</CardGroup>
