---
title: Run Workflows
description: Execute any workflow as a headless API by passing just the input values — no need to send the full node graph.
updated: 2026-09-28
---

The Run endpoint lets you execute any workflow you own by passing only the input values. The server loads the saved workflow, injects your inputs, and executes the full pipeline. This is how you turn visual workflows into programmable APIs.

## Describe Inputs

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

Returns the input schema for a workflow so you know what to pass.

**Request**

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

**Response** `200 OK`

```json
{
  "data": {
    "workflowId": "cm1abc123",
    "workflowName": "Video Analyzer",
    "inputs": [
      {
        "nodeId": "node-tiktok",
        "label": "TikTok Import",
        "type": "url",
        "required": true,
        "description": "TikTok video URL"
      },
      {
        "nodeId": "node-theme",
        "label": "Analysis Theme",
        "type": "text",
        "required": false,
        "default": "Break down the visual storytelling techniques"
      }
    ],
    "endpoint": "/api/v1/workflows/cm1abc123/run",
    "method": "POST",
    "example": {
      "inputs": {
        "node-tiktok": "<url>",
        "node-theme": "Break down the visual storytelling techniques"
      }
    }
  }
}
```

### Input Types

**`type` tells you what the input FEEDS, not which node holds it.** It comes
from the declared type of the port the input is wired into. Wireflow's Image
Input node is the universal importer — it carries sound and video just as
happily — so the node it lives on is not evidence of what the value is for. When
an input is wired to nothing, `type` falls back to the node's own kind.

| Type      | Description          | Wired to                                                   |
| --------- | -------------------- | ---------------------------------------------------------- |
| `text`    | Free-form text input | `TEXT` ports; unwired Text Input / Prompt nodes            |
| `image`   | HTTPS image URL      | `IMAGE` ports; unwired Image Input / Media Upload          |
| `audio`   | HTTPS audio URL      | `AUDIO` ports, e.g. a render node's `audio_2`              |
| `video`   | HTTPS video URL      | `VIDEO` ports                                              |
| `url`     | Any media URL        | ports of **conflicting** media kinds; unwired import nodes |
| `number`  | Numeric value        | Number, Seed nodes                                         |
| `boolean` | True/false toggle    | Toggle nodes                                               |
| `select`  | Choice from options  | List Selector nodes                                        |

So four sound effects imported through Image Input nodes and wired into a render
node's `audio_2` / `audio_3` / `audio_4` ports report `audio`. When consumers
disagree on the media kind, `type` is `url` and `description` names the conflict.

`required: false` means the input already resolves without you — it holds a
value (see `default`) or every port it feeds declares itself optional. A port
that declares no `required` at all counts as unknown, never optional, so
`required: true` can be conservative.

> **Back-compat.** `audio` and `video` are recent additions to this union. If
> you switch on `type`, add cases for them: an input that used to report `image`
> or `url` may now report `audio` or `video`. This is advertisement only — it
> does not change how a run injects your value, what it costs, or which values
> are refused.

### How Inputs Are Detected

1. If the workflow has **published input settings** (`exposedInputs`), only those nodes are exposed
2. Otherwise, all input-category nodes and utility import nodes **without incoming connections** are automatically exposed

## Run a Workflow

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

Executes the workflow with your input values. Returns `202` with an execution ID for polling.

**Request Body**

| Field      | Type     | Required | Description                                                                                                   |
| ---------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `inputs`   | `object` | No       | Map of `nodeId` → value. Omit to run with saved defaults.                                                     |
| `webhook`  | `object` | No       | `{ url, secret? }`. Wireflow POSTs the result here when the run finishes. See [Webhooks](/docs/api/webhooks). |
| `metadata` | `object` | No       | Any JSON object, echoed back in the completion webhook so you can match it to your own job.                   |

**Request**

```bash
curl -X POST https://www.wireflow.ai/api/v1/workflows/cm1abc123/run \
  -H "Authorization: Bearer sk-your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "inputs": {
      "node-tiktok": "https://www.tiktok.com/t/ZP8bar3Dk/"
    }
  }'
```

**Response** `202 Accepted`

```json
{
  "data": {
    "executionId": "exec_789",
    "status": "running",
    "poll": "/api/v1/workflows/executions/exec_789/poll"
  }
}
```

The `Location` header also points to the poll URL.

**Get the result** by polling, or skip polling by passing a `webhook` block (above) and letting Wireflow POST the result to you. See [Webhooks](/docs/api/webhooks).

**Poll for results** using the [Poll endpoint](/docs/api/executions#poll-execution-status):

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

## Complete Example

Build a workflow in the editor, then call it from code:

```javascript
const WIREFLOW_API = 'https://www.wireflow.ai/api/v1';
const API_KEY = process.env.WIREFLOW_API_KEY;

// 1. Discover the inputs
const schema = await fetch(`${WIREFLOW_API}/workflows/${workflowId}/run`, {
  headers: { Authorization: `Bearer ${API_KEY}` },
}).then((r) => r.json());

console.log('Available inputs:', schema.data.inputs);

// 2. Run the workflow
const { data } = await fetch(`${WIREFLOW_API}/workflows/${workflowId}/run`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    inputs: {
      'node-tiktok': 'https://www.tiktok.com/t/ZP8bar3Dk/',
    },
  }),
}).then((r) => r.json());

// 3. Poll until complete
let result;
while (true) {
  const poll = await fetch(`${WIREFLOW_API}${data.poll}`, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  }).then((r) => r.json());

  if (poll.status === 'COMPLETED') {
    result = poll;
    break;
  }
  if (poll.status === 'FAILED') throw new Error(poll.error);

  await new Promise((r) => setTimeout(r, 2000));
}

console.log('Results:', result.nodeResults);
```

## Credits

The API key owner's credits are charged for each AI model node that runs. Text input and utility nodes are free. Use the [credit pre-check error](/docs/api/executions#insufficient-credits) to handle insufficient balance gracefully.

## Comparison with Execute

|                     | `/run`                                     | `/execute`                                |
| ------------------- | ------------------------------------------ | ----------------------------------------- |
| **Input**           | Just the values you want to override       | Full nodes + edges arrays                 |
| **Workflow source** | Loaded from saved state                    | Sent in request body                      |
| **Best for**        | Programmatic use, integrations, automation | Editor (frontend sends live canvas state) |
| **Auth**            | API key or session                         | API key, session, or internal             |

---

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