Workflows
A workflow is a saved node graph — inputs, generation steps, text steps, and outputs wired together — that runs as one pipeline. This page covers managing workflow definitions. To execute one, see Workflow runs.
Most API callers build workflows on the
canvas and only ever call
GET /v1/workflows/:id (to discover input node ids) and
execute. Full CRUD is here for tooling that generates pipelines
programmatically.
The workflow object
| Field | Type | Description |
|---|---|---|
id | string | Workflow id (UUID). |
name | string | Display name. Defaults to "Untitled Workflow". |
graph | object | { nodes, edges }. See The graph. |
projectId | string | null | The project the workflow belongs to. Every workflow has one; a personal workflow lives in your default project. |
createdAt | string | ISO 8601. |
updatedAt | string | ISO 8601. |
List responses return a lighter summary instead:
| Field | Type | Description |
|---|---|---|
id · name · projectId · updatedAt | As above. | |
nodeCount | number | Number of nodes in the graph. |
previewUrl | string | null | Tile visual: the latest complete run's first media output, else an attached input image. Signed and expiring. |
previewKind | "image" | "video" | null | What previewUrl points at. |
The graph
graph.nodes[]
| Field | Type | Description |
|---|---|---|
id | string | Node id — this is the key you pass run inputs under. |
type | string | Node type, e.g. prompt, imageInput, generateImage, output. |
x · y | number | Canvas coordinates. |
data | object | Node-specific configuration. Opaque to the server. |
graph.edges[]
| Field | Type | Description |
|---|---|---|
id | string | Edge id. |
source · target | string | Node ids. |
sourceHandle · targetHandle | string | null | Port ids on each end. |
The server validates structure only — that nodes and edges are arrays, that
each node has id/type/x/y, that each edge has id/source/target, and
that the serialized graph is under 1 MB. Node semantics belong to the
client, which is why hand-authoring a graph is harder than it looks. Build on the
canvas, then read it back.
Input node types
Four node types accept per-run overrides. These are the ones worth finding in
graph.nodes:
type | Takes | Overrides |
|---|---|---|
prompt | { "text": "…" } | the node's data.text |
generateText | { "text": "…" } | the node's data.instruction |
imageInput | { "storageId": "…" } | the node's data.storageId |
videoInput | { "storageId": "…" } | the node's data.storageId |
Passing an input for any other node type is invalid_params (400).
GET /v1/workflows
List workflows in one project.
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
projectId | string | your default project | Which project's workflows to list. You must be a member. |
limit | number | unset | Page size, 1–500. Omit for the full list (the app gallery renders everything). |
offset | number | 0 | Only meaningful with limit. |
sort | string | updated | updated (newest first) · created (newest first) · name (alphabetical). |
Because scoping is by project rather than by author, this returns every member's workflows in a shared project — not just yours.
Returns { "workflows": WorkflowSummary[], "total": number } — total
counts every workflow in the project, not the page.
curl -s "https://api.corte.so/v1/workflows?sort=name" \
-H "Authorization: Bearer corte_sk_…"
{
"workflows": [
{
"id": "7c93b1de-2a48-4f60-91b7-5d0e6a2c8f13",
"name": "Product Reshoot Pipeline",
"nodeCount": 6,
"updatedAt": "2026-07-27T09:41:22.006Z",
"projectId": "1b6d4f90-77c2-4a5e-b3d8-2e9a0c15f7b4",
"previewUrl": "https://media.corte.so/…/frame.jpg?X-Amz-Signature=…",
"previewKind": "image"
}
],
"total": 12
}
GET /v1/workflows/:id
Fetch one workflow with its full graph. This is how you discover input node ids before executing.
Returns { "workflow": Workflow }.
curl -s https://api.corte.so/v1/workflows/7c93b1de-2a48-4f60-91b7-5d0e6a2c8f13 \
-H "Authorization: Bearer corte_sk_…"
{
"workflow": {
"id": "7c93b1de-2a48-4f60-91b7-5d0e6a2c8f13",
"name": "Product Reshoot Pipeline",
"projectId": "1b6d4f90-77c2-4a5e-b3d8-2e9a0c15f7b4",
"createdAt": "2026-07-14T11:02:55.310Z",
"updatedAt": "2026-07-27T09:41:22.006Z",
"graph": {
"nodes": [
{ "id": "productPhoto", "type": "imageInput", "x": 40, "y": 120, "data": { "storageId": "b0e7c9a1-…", "previewUrl": "https://media.corte.so/…" } },
{ "id": "concept", "type": "prompt", "x": 40, "y": 300, "data": { "text": "Alpine trail-running capsule, cold dawn light" } },
{ "id": "reshoot", "type": "generateImage", "x": 360, "y": 200, "data": { "modelId": "nano-banana-2", "aspectRatio": "4:5" } },
{ "id": "final", "type": "output", "x": 680, "y": 200, "data": {} }
],
"edges": [
{ "id": "e1", "source": "productPhoto", "target": "reshoot", "targetHandle": "image" },
{ "id": "e2", "source": "concept", "target": "reshoot", "targetHandle": "prompt" },
{ "id": "e3", "source": "reshoot", "target": "final", "targetHandle": "in" }
]
}
}
}
Here the run-input keys are productPhoto (takes storageId) and concept
(takes text).
:::note Preview URLs are re-signed on every read
data.previewUrl on media input nodes is regenerated each time you fetch the
workflow. Don't cache it.
:::
POST /v1/workflows
Create a workflow.
Body
| Name | Type | Required | Description |
|---|---|---|---|
graph | object | no | { nodes, edges }. Defaults to an empty graph. |
name | string | no | Defaults to "Untitled Workflow". |
projectId | string | no | Bind to a shared project you belong to. Defaults to your default project. |
Returns { "workflow": Workflow }.
curl -s -X POST https://api.corte.so/v1/workflows \
-H "Authorization: Bearer corte_sk_…" \
-H "Content-Type: application/json" \
-d '{
"name": "Nightly Ad Variants",
"graph": { "nodes": [], "edges": [] }
}'
{
"workflow": {
"id": "e51f7a09-c3b6-4e82-a97d-4b810c2f6d55",
"name": "Nightly Ad Variants",
"graph": { "nodes": [], "edges": [] },
"projectId": "1b6d4f90-77c2-4a5e-b3d8-2e9a0c15f7b4",
"createdAt": "2026-07-28T16:11:03.887Z",
"updatedAt": "2026-07-28T16:11:03.887Z"
}
}
PATCH /v1/workflows/:id
Update a workflow. Partial: omitted fields are left alone.
Body
| Name | Type | Description |
|---|---|---|
graph | object | Replaces the whole graph. Not merged. |
name | string | New name. Cannot be empty or whitespace. |
Returns { "workflow": Workflow }.
curl -s -X PATCH https://api.corte.so/v1/workflows/e51f7a09-… \
-H "Authorization: Bearer corte_sk_…" \
-H "Content-Type: application/json" \
-d '{ "name": "Nightly Ad Variants (EU)" }'
{
"workflow": {
"id": "e51f7a09-c3b6-4e82-a97d-4b810c2f6d55",
"name": "Nightly Ad Variants (EU)",
"graph": { "nodes": [], "edges": [] },
"projectId": "1b6d4f90-77c2-4a5e-b3d8-2e9a0c15f7b4",
"createdAt": "2026-07-28T16:11:03.887Z",
"updatedAt": "2026-07-28T16:24:40.512Z"
}
}
DELETE /v1/workflows/:id
Delete a workflow and all of its run history. Not reversible.
Returns { "ok": true }.
curl -s -X DELETE https://api.corte.so/v1/workflows/e51f7a09-… \
-H "Authorization: Bearer corte_sk_…"
{ "ok": true }
Access rules
Access follows the workflow's project. Any member of a shared project can read, run, and update its workflows. Deleting is narrower: only the project owner or the workflow's creator. A workflow in your own default project is yours alone.
| Code | When |
|---|---|
not_found | Unknown id, or a workflow in a project you don't belong to. |
forbidden | Deleting a project workflow you didn't create, without being the project owner. |
invalid_params | Malformed graph, graph over 1 MB, or an empty name. |