Executions
Execute AI workflows, poll for results, and view execution history via the Wireflow API.
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 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
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
{
"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:
{
"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
curl https://www.wireflow.ai/api/v1/workflows/executions/exec_456/poll \
-H "Authorization: Bearer sk-your-api-key"
Response — Running 200 OK
{
"status": "RUNNING",
"nodeResults": {
"nodeRuns": [
{
"nodeId": "node-1",
"status": "RUNNING",
"output": null
}
]
}
}
Response — Completed 200 OK
{
"status": "COMPLETED",
"nodeResults": {
"nodeRuns": [
{
"nodeId": "node-1",
"status": "COMPLETED",
"output": {
"images": [{ "url": "https://cdn.atomu.ai/generated-image.png" }]
}
}
]
}
}
Response — Failed 200 OK
{
"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:
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
curl https://www.wireflow.ai/api/v1/workflows/executions/exec_456 \
-H "Authorization: Bearer sk-your-api-key"
Response 200 OK
{
"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.
{
"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:invokenode answers one entry per output port, with the port id inport. If the blueprint failed after earlier steps had already run and charged you, those results come back as entries withstatus: "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,CANCELLEDorTIMEOUT), the image, video, audio and 3D files that finished generation, processing (upscalers, background removal, matte) and vector nodes produced come back too, withstatus: "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 inoutputsWithheld. Thestatusof an output the app itself designated stayssuccess. - 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
curl "https://www.wireflow.ai/api/v1/workflows/cm1abc123/executions?limit=5" \
-H "Authorization: Bearer sk-your-api-key"
Response 200 OK
[
{
"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:
- POST to
/workflows/{id}/execute— returns201withexecutionId - GET to
/workflows/executions/{id}/poll— returns current status - Repeat step 2 until
statusisCOMPLETEDorFAILED
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.