Skip to main content

Workflow runs

Executing a workflow runs the whole pipeline on Corte's servers and records a run — a status plus one result entry per node. API and MCP runs use the same runner as the app canvas, so behavior is identical wherever a run starts.

The loop is: fetch the workflow to learn its input node ids (GET /v1/workflows/:id), execute with overrides, then poll the run.

The run object

FieldTypeDescription
idstringRun id (UUID).
workflowIdstringThe workflow that produced it.
statusenumrunning · complete · error · stopped.
nodeResultsobjectMap of node id → node result. Fills in incrementally while running.
userIdstringThe member who started the run.
createdAtstringISO 8601.
completedAtstring | nullISO 8601, once terminal.

Node results

nodeResults[nodeId]

FieldTypeDescription
statusstringThis node's state.
detailstringExtra context — the failure reason on an errored node.
outputobjectThe node's primary out port: { type, value, storageId? }.
outputsobjectEvery output port, keyed by port id. Nodes that fan out (a text splitter emitting 1–12 branches) are only fully represented here.

In a port value, type is image, video, audio, or text. For media, value is a signed URL that expires and storageId is the durable handle — use storageId to chain results into further API calls. For text, value is the string itself.

Read output for the common case; read outputs when a node has multiple ports. output is always populated alongside outputs for compatibility.

:::tip Run URLs are re-signed on every read Media URLs recorded in a run expire well before most runs are looked at again, so every fetch re-signs them from storage. A run you cached a week ago will have dead URLs; re-fetch it. :::


POST /v1/workflows/:id/execute

Run a saved workflow, optionally overriding input nodes for this run only. Saved values are never modified.

Body

NameTypeRequiredDescription
inputsobjectnoMap of node id{ text } or { storageId }. Omitted nodes keep their saved values.
waitbooleannotrue blocks until the run finishes. Default false — returns immediately with a running run.
projectIdstringnoBilling context for a personal workflow run inside a shared project. A workflow that already belongs to a project inherits it; passing a different one is an error.

inputs rules

  • Keys are node ids from the workflow's graph.nodes, not node types.
  • prompt and generateText nodes require { "text": "…" } — non-empty.
  • imageInput and videoInput nodes require { "storageId": "…" } — a committed object you can read.
  • Any other node type is rejected: inputs.<id>: <type> nodes don't take run inputs.
  • An unknown node id is rejected: inputs: no node <id> in this workflow.

Returns { "run": Run }.

Request
curl -s -X POST https://api.corte.so/v1/workflows/7c93b1de-…/execute \
-H "Authorization: Bearer corte_sk_…" \
-H "Content-Type: application/json" \
-d '{
"inputs": {
"concept": { "text": "Alpine trail-running capsule, cold dawn light" },
"productPhoto": { "storageId": "b0e7c9a1-4d52-4f0e-8c31-77a2e5d9f6b8" }
},
"wait": false
}'
Response
{
"run": {
"id": "d4a80b13-6c7e-42f9-8a05-91b2c7e3f660",
"workflowId": "7c93b1de-2a48-4f60-91b7-5d0e6a2c8f13",
"status": "running",
"nodeResults": {},
"userId": "3f8c1a92-b5d7-4e60-9c14-8a72d0e5b391",
"createdAt": "2026-07-28T16:40:12.774Z",
"completedAt": null
}
}

With wait: true the same call returns once the run settles, with nodeResults fully populated. Fine for short pipelines; for anything long, poll instead — a blocked HTTP connection is a fragile way to wait several minutes.

Errors

CodeWhen
not_foundUnknown workflow, or one you can't reach.
invalid_paramsinputs isn't an object, names an unknown node, targets a node type that takes no inputs, omits text/storageId where required, references an unreadable storageId, or the workflow has no nodes. Also when projectId conflicts with the workflow's own project.
insufficient_credits · owner_insufficient_creditsThe payer's balance can't cover a step.

GET /v1/workflows/:id/runs/:runId

Fetch one run. This is the polling endpoint, and it streams progress: while status is running, each poll returns more entries in nodeResults as nodes finish.

Runs of a project workflow are visible to every member. Runs of a personal workflow are visible only to you.

Returns { "run": Run }.

Request
curl -s https://api.corte.so/v1/workflows/7c93b1de-…/runs/d4a80b13-… \
-H "Authorization: Bearer corte_sk_…"
Response
{
"run": {
"id": "d4a80b13-6c7e-42f9-8a05-91b2c7e3f660",
"workflowId": "7c93b1de-2a48-4f60-91b7-5d0e6a2c8f13",
"status": "complete",
"nodeResults": {
"productPhoto": {
"status": "complete",
"output": { "type": "image", "value": "https://media.corte.so/…/in.jpg?X-Amz-Signature=…", "storageId": "b0e7c9a1-…" }
},
"concept": {
"status": "complete",
"output": { "type": "text", "value": "Alpine trail-running capsule, cold dawn light" }
},
"reshoot": {
"status": "complete",
"output": { "type": "image", "value": "https://media.corte.so/…/out.jpg?X-Amz-Signature=…", "storageId": "c74a1e08-…" },
"outputs": {
"out": { "type": "image", "value": "https://media.corte.so/…/out.jpg?X-Amz-Signature=…", "storageId": "c74a1e08-…" }
}
},
"final": {
"status": "complete",
"output": { "type": "image", "value": "https://media.corte.so/…/out.jpg?X-Amz-Signature=…", "storageId": "c74a1e08-…" }
}
},
"userId": "3f8c1a92-b5d7-4e60-9c14-8a72d0e5b391",
"createdAt": "2026-07-28T16:40:12.774Z",
"completedAt": "2026-07-28T16:41:58.203Z"
}
}

A failed run is a 200 with status: "error"; the node that broke carries the reason:

{
"run": {
"status": "error",
"nodeResults": {
"reshoot": { "status": "error", "detail": "Upstream provider timed out after 180s" }
},
"completedAt": "2026-07-28T16:44:10.554Z"
}
}

Read your output node's entry for the pipeline's final result — or any intermediate node if you want the in-between artifacts too.

Polling

import time, requests

def wait_for_run(workflow_id, run_id, interval=3, timeout=1800):
url = f"https://api.corte.so/v1/workflows/{workflow_id}/runs/{run_id}"
deadline = time.time() + timeout
while time.time() < deadline:
run = requests.get(url, headers={"Authorization": f"Bearer {KEY}"}).json()["run"]
done = sum(1 for r in run["nodeResults"].values() if r["status"] == "complete")
print(f"{run['status']}: {done} nodes done")
if run["status"] != "running":
return run
time.sleep(interval)
raise TimeoutError(run_id)

GET /v1/workflows/:id/runs

List runs for a workflow, newest first.

Same visibility rule as fetching a single run: all members' runs for a project workflow, only yours for a personal one.

Query parameters

NameTypeDefaultDescription
limitnumber20Max 100.
offsetnumber0Page through with limit.

Returns { "runs": Run[], "total": number }total counts every visible run, not the page.

Request
curl -s https://api.corte.so/v1/workflows/7c93b1de-…/runs \
-H "Authorization: Bearer corte_sk_…"
Response
{
"runs": [
{ "id": "d4a80b13-…", "workflowId": "7c93b1de-…", "status": "complete", "nodeResults": { "…": "…" }, "userId": "3f8c1a92-…", "createdAt": "2026-07-28T16:40:12.774Z", "completedAt": "2026-07-28T16:41:58.203Z" },
{ "id": "9e2f7c40-…", "workflowId": "7c93b1de-…", "status": "error", "nodeResults": { "…": "…" }, "userId": "3f8c1a92-…", "createdAt": "2026-07-27T09:38:01.442Z", "completedAt": "2026-07-27T09:41:22.006Z" }
],
"total": 47
}

POST /v1/workflows/:id/runs/:runId/stop

Abort a run that's still running. Partial nodeResults are kept; status becomes stopped.

Only the member who started the run may stop it. Idempotent — stopping an already-settled run returns it unchanged.

Nodes that already completed have already been billed. Stopping prevents further spend; it doesn't refund.

Returns { "run": Run }.

Request
curl -s -X POST https://api.corte.so/v1/workflows/7c93b1de-…/runs/d4a80b13-…/stop \
-H "Authorization: Bearer corte_sk_…"
Response
{
"run": {
"id": "d4a80b13-6c7e-42f9-8a05-91b2c7e3f660",
"workflowId": "7c93b1de-2a48-4f60-91b7-5d0e6a2c8f13",
"status": "stopped",
"nodeResults": { "productPhoto": { "status": "complete", "output": { "…": "…" } } },
"userId": "3f8c1a92-b5d7-4e60-9c14-8a72d0e5b391",
"createdAt": "2026-07-28T16:40:12.774Z",
"completedAt": "2026-07-28T16:40:51.318Z"
}
}

The abort settles the row within a poll tick, so a response may still read running for a moment — poll once to confirm.


Cost

A run bills per node, exactly as the same graph would in the app: generation nodes at their model's rate, Generate Text nodes by token usage. The run is billed to the key's account, or to the project owner for project-billed work.

Each step checks the balance before executing, so a run can settle as error part-way through with insufficient_credits on the node that couldn't be afforded. Completed nodes stay charged.

  • Workflows — managing pipeline definitions
  • Files — getting media in as a storageId
  • MCP tools — the same run flow as agent tools