Flow Builder
Read, edit, estimate and run Flow Builder canvases through the API.
A Flow is a Flow Builder canvas: nodes (inputs, generation steps, editing steps, notes, groups) connected by edges. The API reads and edits the same saved canvas the app shows, and runs it on the server with a credit ceiling you set. Open editor_url from any response to see the canvas in the app.
Endpoints
GET /api/v1/flows/schema node catalog, writable fields and limits (?node_type=)
GET /api/v1/flows?project_id=... list Flows in a project
POST /api/v1/flows create a Flow
GET /api/v1/flows/:flowId read a Flow
PATCH /api/v1/flows/:flowId apply edit operations
POST /api/v1/flows/:flowId/validate validate the saved graph
POST /api/v1/flows/:flowId/undo undo one of your edits
POST /api/v1/flows/:flowId/duplicate copy a Flow
POST /api/v1/flows/:flowId/move move to another project
POST /api/v1/flows/:flowId/trash move to trash
POST /api/v1/flows/:flowId/restore restore from trash
POST /api/v1/flows/:flowId/snapshot replace the whole graph (canvas save)
POST /api/v1/flows/:flowId/estimate free credit quote
POST /api/v1/flows/:flowId/runs start a run
GET /api/v1/flows/:flowId/runs list runs
GET /api/v1/flows/:flowId/runs/:runId read a run
POST /api/v1/flows/:flowId/runs/:runId/cancel cancel a run
POST /api/v1/flows/:flowId/runs/:runId/retry retry failed nodes of a runResponses use { "status": true, "data": { ... } }. Errors use { "status": false, "code": "...", "message": "..." } (some add details) and default to HTTP 400. Every write and every run action (create, edit, snapshot, undo, duplicate, move, trash, restore, start, cancel and retry a run) needs a write-enabled API key — a key without write, including an older grandfathered one, gets 403 FORBIDDEN. Reads, validate and estimate work with any key. The routes share a limit of 180 requests per minute per account, counting every request; a limited request receives the plain-text body Too many requests, please try again later. Access follows the Flow's project: reading needs view access, editing needs edit access, and removing nodes, undoing, trashing or moving needs delete access. A request body may not exceed 2 MB.
The Flow object
Reads and writes return:
| Field | Notes |
|---|---|
flow_session_id, project_id, name | Identity |
revision | String. Send it back as expected_revision on every write and run |
schema_version | Graph schema version |
instructions | Session instructions (up to 10,000 characters) |
flow_data | { nodes, edges }. Omitted in list results. Each node keeps only its latest run unless you read with include_history=true |
isPinned, isArchived, isLocked, deleted | State flags |
editor_url | Opens the canvas in the Kolbo web app |
updated_at | Timestamp |
Writes also return operation_id (keep it to undo), created_node_ids, deleted_node_ids, changed_node_ids and changed.
List, read and create
GET /api/v1/flowsrequiresproject_id. Optional:limit(1–100, default 25),offset,query(name search),include_archived,is_archived,include_trashed. Returnsdata: { sessions, has_more, offset, limit }.GET /api/v1/flows/:flowIdacceptsinclude_history=true,include_trashed=true,node_ids=a,b(only those nodes and their edges) andproject_id(returns403 PROJECT_MISMATCHif the Flow lives elsewhere).POST /api/v1/flowstakesproject_idandidempotency_key(required), plus optionalname(1–200 characters, defaultNew Flow),instructionsandflow_data. Repeating the key with the same body returns the existing Flow withreplayed: true; a different body returns409 IDEMPOTENCY_CONFLICT.
Read GET /api/v1/flows/schema before building nodes. It lists every node type with its handles and default config, the allowed node and config fields, and the limits: 500 nodes, 2,500 edges, 200 operations per request and a 2 MB graph.
Edit
Every write takes expected_revision (the revision you last read) and an idempotency_key of 8–100 letters, digits, hyphens or underscores. A stale revision returns 409 REVISION_CONFLICT: read the Flow again and reapply the edit. A locked Flow returns 409 SESSION_LOCKED.
PATCH /api/v1/flows/:flowId
{
"expected_revision": "7",
"idempotency_key": "edit-0001",
"operations": [
{ "op": "node.add", "node_id": "text1", "type": "text-input", "fields": { "position": { "x": 0, "y": 0 }, "text": "A red bicycle on a beach" } },
{ "op": "node.add", "node_id": "img1", "type": "image-generation", "fields": { "position": { "x": 420, "y": 0 } } },
{ "op": "edge.add", "source": "text1", "target": "img1", "source_handle": "text-output", "target_handle": "text-input" }
]
}Send 1–200 operations, applied in order as one change. Add "dry_run": true to validate without saving.
op | Fields |
|---|---|
node.add | type, optional node_id, fields |
node.update, group.update | node_id, fields, optional unset (field names to remove) |
node.remove | node_id (also removes its edges) |
edge.add | source, target, source_handle, target_handle, optional edge_id |
edge.update | edge_id, fields (source, target, sourceHandle, targetHandle, label) |
edge.remove | edge_id |
nodes.duplicate | node_ids, optional offset { x, y } (default 80, 80) |
input.reorder | node_id, handle, edge_ids (every edge into that handle, once each) |
group.create | optional node_id, node_ids, fields |
group.set_members | node_id, node_ids |
group.remove | node_id (members stay) |
layout.apply | optional node_ids (default all) |
session.update | fields: name, instructions, isPinned, isArchived |
output.select | node_id, run_id, index, optional handle: choose which completed output a node passes on |
fields patch a node: omitted fields stay, config keys merge and arrays replace. Unknown fields, incompatible connections (INCOMPATIBLE_PORT), cycles (GRAPH_CYCLE) and unknown models (MODEL_NOT_FOUND) are rejected. Media referenced by URL must be http(s), and media-library items must be accessible to you (MEDIA_FORBIDDEN, MEDIA_NOT_FOUND).
Other writes:
| Route | Body |
|---|---|
undo | expected_revision, idempotency_key, operation_id of your own most recent edit. Fails with 409 REVISION_CONFLICT if later edits exist, 404 UNDO_UNAVAILABLE if the receipt is gone. Runtime results are kept |
duplicate | idempotency_key, optional project_id and name (default <name> (copy)). Run results are not copied |
move | expected_revision, idempotency_key, project_id (you need create access there) |
trash, restore | expected_revision, idempotency_key |
snapshot | expected_revision, idempotency_key, flow_data, optional name. Replaces the whole editable graph |
Moving or trashing a Flow with an active run returns 409 RUN_STATE_CONFLICT. validate returns data: { valid, node_count, edge_count, warnings, revision }.
Estimate and run
POST /api/v1/flows/:flowId/estimate is free:
| Field | Notes |
|---|---|
expected_revision | Required |
scope | all (default), selected or downstream (the selected nodes plus everything after them) |
node_ids | The nodes for selected / downstream |
prerequisites | reuse (default: use upstream outputs already selected) or run_missing (also run upstream nodes that have no output yet) |
It returns data: { total_credits, known_credits, provisional, requires_max_credits, nodes }. Each entry in nodes has node_id, credits and provisional. total_credits is null when a node is only priced at admission (Smart Select, token-priced assistant steps, or steps priced by a source's length).
POST /api/v1/flows/:flowId/runs takes the same fields plus:
| Field | Notes |
|---|---|
idempotency_key | Required. 8–128 characters of letters, digits, _, ., : or -. The same key and body returns the original run; a different body returns 409 IDEMPOTENCY_CONFLICT |
max_credits | The most the run may spend. Required (positive, up to 1,000,000) whenever any selected node costs credits (400 MAX_CREDITS_REQUIRED). A quote above it returns 402 FLOW_CREDIT_CEILING |
A run snapshots the graph at expected_revision, so later edits do not change it. A node type that cannot run on the server returns NODE_EXECUTION_UNSUPPORTED; an upstream node with no output returns INPUT_MISSING (use prerequisites: "run_missing"). A locked Flow returns 409 FLOW_LOCKED.
The run object
| Field | Notes |
|---|---|
run_id, flow_session_id, project_id, actor_id, revision, parent_run_id | Identity |
status | queued, running, cancel_requested, completed, failed, cancelled or needs_attention |
max_credits, admitted_credits | Ceiling and credits admitted so far |
nodes | Per node: node_id, status, attempt, outputs (keyed by output handle), error, generation_id, progress, cancellation, started_at, finished_at |
created_at, finished_at | Timestamps |
Poll GET /api/v1/flows/:flowId/runs/:runId until status is completed, failed, cancelled or needs_attention. GET /api/v1/flows/:flowId/runs lists runs with limit (1–100, default 20), offset and before (a date); it returns runs, offset, limit, has_more and next_offset.
- Cancel (
.../cancel): optionalnode_ids(default all). Only the user who started the run can cancel it. Returns the run withstatus: "cancel_requested". - Retry (
.../retry): a newidempotency_key, optionalnode_ids(only failed, cancelled or blocked nodes) andmax_credits(defaults to the original run's). Only terminal runs can be retried (409 RUN_STATE_CONFLICT), and only while the Flow is still at the run's revision (409 REVISION_CONFLICT); to run edited nodes, start a new run.
MCP
get_flow_schema, list_flow_sessions, create_flow_session, get_flow_session, update_flow_session, validate_flow_session, duplicate_flow_session, undo_flow_edit, move_flow_session, trash_flow_session, restore_flow_session, estimate_flow_run, run_flow_session, get_flow_run, list_flow_runs, cancel_flow_run and retry_flow_run.