`POST /api/workflows` — create a workflow from a JSON body. The DAG is validated by the service; an invalid DAG yields 400.
const url = 'https://example.com/api/workflows';const options = { method: 'POST', headers: {'Content-Type': 'application/json'}, body: '{"name":"example","nodes":[{"config":"example","depends_on":[{"handle":"example","key":"example"}],"key":"example","kind":"example","parent_node_key":"example","position":{"x":1,"y":1}}],"project_id":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","stages":[{"node_key":"example","role":"example","who":"example"}],"steps":[{"agent_type":"example","depends_on":["example"],"key":"example","on_failure":"example","prompt":"example"}]}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://example.com/api/workflows \ --header 'Content-Type: application/json' \ --data '{ "name": "example", "nodes": [ { "config": "example", "depends_on": [ { "handle": "example", "key": "example" } ], "key": "example", "kind": "example", "parent_node_key": "example", "position": { "x": 1, "y": 1 } } ], "project_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "stages": [ { "node_key": "example", "role": "example", "who": "example" } ], "steps": [ { "agent_type": "example", "depends_on": [ "example" ], "key": "example", "on_failure": "example", "prompt": "example" } ] }'Request Bodyrequired
Section titled “Request Bodyrequired”POST /api/workflows body — create a workflow definition in a project.
A request may carry either the v1 steps (agent-only) or the typed nodes
array; nodes wins when present.
object
One typed node in a create/update request body (ADR 0020 V8 builder). The
builder authors all four kinds; config is the kind-specific blob the spec
layer persists verbatim, position is the canvas placement.
object
The kind-specific config blob; defaults to {}.
This node’s incoming edges.
One incoming-edge declaration of a node in the typed-node wire shape — the
source node_key plus the handle it leaves the source by (out for an
agent node, true/false for a gate, approved/rejected for a human).
object
The source’s output handle; defaults to out.
agent / gate / human / end / code / loop / notify_*.
WP V16: when set, this node lives INSIDE the named loop frame’s
sub-graph. Omitted (None) means top-level.
A node’s canvas position in a create/update request (ADR 0020 V8).
object
Explicit semantic stage declarations for the graph. Each item names a node plus an open role and optional intended worker/profile.
One explicit pipeline stage in a [WorkflowSpec].
role deliberately remains an open string instead of a closed enum: the
platform’s plan / implement / review roles are conventional, while a
Marketplace or custom workflow may declare additional domain stages without
requiring a server release. Validation still requires every declaration to
point at an existing node and rejects blank metadata.
object
The workflow node that implements this stage.
Semantic stage role, such as plan, implement, or review.
Optional intended worker/profile identifier. It is descriptive metadata; concrete per-node execution overrides still live on the node config.
One step in a create/update request body — the v1 agent-only wire shape,
kept so existing callers keep working. The V8 builder sends nodes instead.
object
stop (default) or continue; unknown values fall back to stop.
Examplegenerated
{ "name": "example", "nodes": [ { "config": "example", "depends_on": [ { "handle": "example", "key": "example" } ], "key": "example", "kind": "example", "parent_node_key": "example", "position": { "x": 1, "y": 1 } } ], "project_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "stages": [ { "node_key": "example", "role": "example", "who": "example" } ], "steps": [ { "agent_type": "example", "depends_on": [ "example" ], "key": "example", "on_failure": "example", "prompt": "example" } ]}Responses
Section titled “Responses”Workflow created; body contains the new workflow id
{ "id": <uuid> } — the create/import response body (the new workflow id);
the handlers return this struct directly.
object
Examplegenerated
{ "id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"}Structured client error
The canonical JSON body of every error response — the single source of truth
the frontend binds to. Every AppError serializes as this exact shape, and
the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is
what the frontend error schema is generated from, so there is no hand-written
error schema on either end.
object
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}Authentication required
The canonical JSON body of every error response — the single source of truth
the frontend binds to. Every AppError serializes as this exact shape, and
the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is
what the frontend error schema is generated from, so there is no hand-written
error schema on either end.
object
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}Permission denied
The canonical JSON body of every error response — the single source of truth
the frontend binds to. Every AppError serializes as this exact shape, and
the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is
what the frontend error schema is generated from, so there is no hand-written
error schema on either end.
object
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}Structured server error
The canonical JSON body of every error response — the single source of truth
the frontend binds to. Every AppError serializes as this exact shape, and
the generated OpenAPI component ApiErrorBody (with its ErrorCode enum) is
what the frontend error schema is generated from, so there is no hand-written
error schema on either end.
object
Machine-readable, stable error code.
Present only on a quota-exceeded 403 — the inline upgrade-CTA payload.
object
The entitlement feature key that was hit, e.g. apps.max_count.
The plan’s limit for this key.
Where to send the user to upgrade.
Current usage (count or bytes, per the key).
Human-readable message (the server’s English text; the client may localize
by code).
Example
{ "code": "not_found"}