---
title: Executions
description: Execute AI workflows, poll for results, and view execution history via the Wireflow API.
updated: 2026-10-04
---

Executions are the result of running a workflow. The API uses an async pattern: you start an execution, then poll until it completes.

## Execute a Workflow

```
POST /api/v1/workflows/{id}/execute
```

Starts a workflow execution. Returns `201` immediately with an execution ID. Use the [poll endpoint](#poll-execution-status) to check for completion.

**Access**

You need edit-level access to the workflow: you own it, you are an editor on it (a shared editor, or anyone when its link is set to edit), you hold an edit or owner grant on its media, or you are an owner or admin of its team. View-only grants (a view share, a view link, a view-only media grant) are refused with `403` and `{ "error": "Forbidden" }` before any credit is read and before any node in the request is used. To run a board you can only view, duplicate it and run your copy.

One known exception: a board filed in a team folder can be run by every member of that team whatever their team role, a team viewer included, and that run bills the team's credit pool.

**Request Body**

| Field             | Type               | Required | Description                                 |
| ----------------- | ------------------ | -------- | ------------------------------------------- |
| `nodes`           | `Node[]`           | Yes      | Workflow nodes with current input values    |
| `edges`           | `Edge[]`           | Yes      | Connections between nodes                   |
| `targetNodeId`    | `string`           | No       | Execute only this node and its dependencies |
| `triggerData`     | `object`           | No       | Data passed from an external trigger        |
| `iteratorConfigs` | `IteratorConfig[]` | No       | Iterator execution settings                 |

**Request**

```bash
curl -X POST https://www.wireflow.ai/api/v1/workflows/cm1abc123/execute \
  -H "Authorization: Bearer sk-your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "nodes": [
      {
        "id": "node-1",
        "type": "model",
        "position": { "x": 0, "y": 0 },
        "data": {
          "label": "Text to Image",
          "nodeType": "model:fal:text-to-image",
          "category": "model",
          "prompt": "A sunset over mountains"
        }
      }
    ],
    "edges": []
  }'
```

**Response** `201 Created`

```json
{
  "workflowId": "cm1abc123",
  "batchId": "550e8400-e29b-41d4-a716-446655440000",
  "executionId": "exec_456",
  "executionRuns": [
    {
      "id": "exec_456",
      "startTime": "2025-03-20T12:01:00.000Z",
      "endTime": null,
      "status": "RUNNING",
      "error": null,
      "nodeRuns": [
        {
          "nodeId": "node-1",
          "status": "COMPLETED",
          "output": {
            "images": [{ "url": "https://cdn.atomu.ai/..." }]
          }
        }
      ],
      "batchId": "550e8400-e29b-41d4-a716-446655440000",
      "runIndexInBatch": 0,
      "inputIndex": 0
    }
  ]
}
```

`executionId` is the id to poll. It is the same value as `executionRuns[0].id`, which stays in the response for existing clients. Error responses carry no `executionId`.

The response includes a `Location` header pointing to the execution status endpoint.

**Insufficient Credits**

If you don't have enough credits, the API returns `402`:

```json
{
  "error": "Insufficient credits. Required: 10, Available: 3",
  "insufficientCredits": true,
  "requiredCredits": 10,
  "availableCredits": 3,
  "breakdown": [
    { "nodeId": "node-1", "nodeLabel": "Text to Image", "credits": 10 }
  ]
}
```

## Poll Execution Status

```
GET /api/v1/workflows/executions/{executionId}/poll
```

Checks the status of an async execution. For long-running AI models (video generation, etc.), poll this endpoint until `status` is `COMPLETED` or `FAILED`. This is the URL the execute response's `Location` header points to.

**Request**

```bash
curl https://www.wireflow.ai/api/v1/workflows/executions/exec_456/poll \
  -H "Authorization: Bearer sk-your-api-key"
```

**Response — Running** `200 OK`

```json
{
  "status": "RUNNING",
  "nodeResults": {
    "nodeRuns": [
      {
        "nodeId": "node-1",
        "status": "RUNNING",
        "output": null
      }
    ]
  }
}
```

**Response — Completed** `200 OK`

```json
{
  "status": "COMPLETED",
  "nodeResults": {
    "nodeRuns": [
      {
        "nodeId": "node-1",
        "status": "COMPLETED",
        "output": {
          "images": [{ "url": "https://cdn.atomu.ai/generated-image.png" }]
        }
      }
    ]
  }
}
```

**Response — Failed** `200 OK`

```json
{
  "status": "FAILED",
  "error": "Model inference failed",
  "nodeResults": {
    "nodeRuns": [
      {
        "nodeId": "node-1",
        "status": "FAILED",
        "error": "Provider returned 500"
      }
    ]
  }
}
```

### Polling Strategy

We recommend polling with exponential backoff:

```javascript
async function pollExecution(executionId, apiKey) {
  const baseUrl = 'https://www.wireflow.ai/api/v1';
  let delay = 1000; // Start at 1 second

  while (true) {
    const res = await fetch(
      `${baseUrl}/workflows/executions/${executionId}/poll`,
      { headers: { Authorization: `Bearer ${apiKey}` } }
    );
    const data = await res.json();

    if (data.status === 'COMPLETED') return data;
    if (data.status === 'FAILED') throw new Error(data.error);

    await new Promise((r) => setTimeout(r, delay));
    delay = Math.min(delay * 1.5, 10000); // Cap at 10 seconds
  }
}
```

## Get Execution Details

```
GET /api/v1/workflows/executions/{executionId}
```

Returns the full execution record including status, node results, and metadata.

**Request**

```bash
curl https://www.wireflow.ai/api/v1/workflows/executions/exec_456 \
  -H "Authorization: Bearer sk-your-api-key"
```

**Response** `200 OK`

```json
{
  "id": "exec_456",
  "workflowId": "cm1abc123",
  "status": "COMPLETED",
  "triggeredBy": "manual",
  "triggerData": {},
  "nodeResults": { "nodeRuns": [...] },
  "currentNode": null,
  "output": null,
  "error": null,
  "errorNode": null,
  "executionTime": 15000,
  "creditsUsed": 10,
  "startedAt": "2025-03-20T12:01:00.000Z",
  "completedAt": "2025-03-20T12:01:15.000Z",
  "workflowSnapshot": { ... }
}
```

## Runs on a Workflow You Do Not Own

A run you start on somebody else's workflow (a published app you called with your own key, a template, or a run from an MCP client) is your run. You pay for it and you can read it. What you read back is the run and its outputs, not the author's workflow.

When you neither own the workflow nor have edit access to it, the execution reads below return an **outputs-only projection**. Fields are omitted or set to `null`, so a client should treat every one of them as optional. Owners and editors get the full responses documented above, unchanged.

| Route                                                          | What a cross-account caller gets                                                                                                                                                                                                                                                                           |
| -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /workflows/executions/{id}/poll`                          | `executionId`, `status`, `progress`, `creditsUsed`, `error`, `outputs`, `outputsWithheld`. No `nodes`, `result` or `nodeStates`.                                                                                                                                                                           |
| `GET /workflows/executions/{id}`                               | `id`, `workflowId`, `status`, `triggeredBy`, `currentNode`, `errorNode`, `executionTime`, `creditsUsed`, `startedAt`, `completedAt`, `error`, `outputs`, `outputsWithheld`.                                                                                                                                |
| `PATCH /workflows/executions/{id}` (cancel)                    | The same fields as the GET above.                                                                                                                                                                                                                                                                          |
| `GET /workflows/executions/{id}/node-runs/{nodeRunId}/details` | The run's status, timings, error, credit ledger and run list. The node's `output` is its media and text, only when the app designates that node (a blueprint answers `{ ports }`). `input` and `result` are `null`, and each provider submission loses `inputPayload`, `providerResponse` and `inputHash`. |
| `GET /workflows/executions/poll?ids=`                          | Per node: `nodeId`, `status`, `error`, `progress` and skip fields. Nothing a node produced; use the single poll for results.                                                                                                                                                                               |

Not returned to a cross-account caller: `nodes`, `edges`, `nodeResults`, `workflowSnapshot`, `triggerData`, the stored `output`, any node's resolved `input`, and anything produced by an Input node.

The MCP tool `get_execution` reads a run on a workflow you do not own through the same projection. It answers `id`, `status`, `error`, `creditsUsed`, `startedAt`, `completedAt`, `outputs` and `outputsWithheld` as described below, plus `nodeStates` that carry each node's `status` and `error` (with the author's saved values replaced by `[redacted]`) and nothing a node produced. If the workflow cannot be read, the tool answers a retryable error.

```json
{
  "executionId": "exec_456",
  "status": "COMPLETED",
  "creditsUsed": 10,
  "outputs": [
    {
      "nodeId": "node-video",
      "label": "Final reel",
      "nodeType": "video:seedance",
      "status": "success",
      "url": "https://cdn.wireflow.ai/renders/reel-final.mp4"
    }
  ],
  "outputsWithheld": 0
}
```

Each entry in `outputs` is one finished node run of this execution. An iterator fan-out yields one entry per take, with `iteration` set. A node that produced several files carries them in `urls`, and `url` is the first. Template executions (`template_api`) use an older projection of the same shape: every node that ran except Input and utility nodes, with no value check and no `outputsWithheld`.

### Which nodes are outputs

`outputs` holds what the Apps API would give you for this workflow: the nodes its author picked as the app's outputs (`publishSettings.exampleOutputs`), or, when none were picked, the final nodes of the graph (the ones nothing consumes), as the graph was when your run started. A node in the middle of the graph is not an output of a run that completed, and an Input node never is.

- A `blueprint:invoke` node answers one entry per output port, with the port id in `port`. If the blueprint failed after earlier steps had already run and charged you, those results come back as entries with `status: "partial"`, whether or not the invoke is a final node.
- A run you aimed at specific nodes (`targetNodeIds`) is read the same way: by what the app designates, not by the nodes you named. A run aimed at a node in the middle of the graph returns nothing for it, and neither does a run aimed at a final node the app did not pick.
- If the run stopped before it finished (`FAILED`, `CANCELLED` or `TIMEOUT`), the image, video, audio and 3D files that finished generation, processing (upscalers, background removal, matte) and vector nodes produced come back too, with `status: "partial"`, because you are still charged for them. Nothing else a node finished comes back on a stopped run: not its text, not fetched data, not a model's reply, not a utility's output. Those are counted in `outputsWithheld`. The `status` of an output the app itself designated stays `success`.
- A utility or logic node returns the media it produced. It never returns text.
- A text or media value equal to something the author saved in one of the workflow's inputs is held back, as is any text that contains a saved value with no spaces in it and 16 characters or more (a key, a cookie) or any saved value of 64 characters or more. A saved cookie header, JSON export or cookie file is also held back when only one pair or one value of it comes back, in its encoded or decoded form. A saved url is held back only when the value is exactly that url, so a caption that cites a plain image url is returned; a token in the url's query or path (a webhook url's secret) is held back wherever it appears. That is what keeps a router or a preview wired to a saved input from handing the input back to you. The check covers the values your run was started with, so it keeps holding after the author changes or deletes one. What you typed yourself comes back to you.

`outputsWithheld` counts the outputs the run owes you (the app's outputs, and on a run that stopped early every finished node outside them) that had something held back. `0` means none did. A positive number next to an empty `outputs` means something was produced and kept from you, not that nothing was produced. A node in the middle of a run that completed is not an output and is not counted. Error messages have the author's saved values replaced with `[redacted]`.

If the server cannot read the workflow to decide this, the read answers `503` with a retryable error. The run itself is unaffected, so retry in a moment.

## List Execution History

```
GET /api/v1/workflows/{id}/executions
```

Returns recent executions for a workflow, ordered by most recent first.

**Query Parameters**

| Parameter | Type      | Default | Description                    |
| --------- | --------- | ------- | ------------------------------ |
| `limit`   | `integer` | `10`    | Number of executions to return |
| `offset`  | `integer` | `0`     | Offset for pagination          |

**Request**

```bash
curl "https://www.wireflow.ai/api/v1/workflows/cm1abc123/executions?limit=5" \
  -H "Authorization: Bearer sk-your-api-key"
```

**Response** `200 OK`

```json
[
  {
    "id": "exec_456",
    "workflowId": "cm1abc123",
    "status": "COMPLETED",
    "triggeredBy": "manual",
    "executionTime": 15000,
    "creditsUsed": 10,
    "startedAt": "2025-03-20T12:01:00.000Z",
    "completedAt": "2025-03-20T12:01:15.000Z",
    "error": null
  }
]
```

## Async Execution Pattern

Wireflow uses an asynchronous execution model:

1. **POST** to `/workflows/{id}/execute` — returns `201` with `executionId`
2. **GET** to `/workflows/executions/{id}/poll` — returns current status
3. Repeat step 2 until `status` is `COMPLETED` or `FAILED`

This design handles long-running AI models (video generation can take 5+ minutes) without holding HTTP connections open.

### Retries and idempotency

`/execute` does **not** deduplicate on an `Idempotency-Key` header. It records the key in the audit log only, so a retry starts a second run and bills you again. After a timeout or a `5xx`, check `GET /workflows/{id}/executions` (or poll the `executionId` you already have) before retrying. If you need a key that replays the original run, use `POST /workflows/{id}/run`.

---

Documentation index: fetch https://www.wireflow.ai/llms.txt for the full list of Wireflow docs.
