Workflows API
Workflows orchestrate multi-step agent execution across six patterns. Each workflow contains a definition with steps, optional loop or swarm configuration, and persisted execution state. Workflows manage their own task lifecycle — creating, executing, and monitoring step tasks automatically.
Quick Start
Create a workflow with sequential steps, execute it, poll for status, and check the results:
// 1. Create a sequence workflow with three steps
interface Workflow {
id: string;
name: string;
status: string;
runNumber: number;
}
const wfRes: Response = await fetch("http://localhost:3000/api/workflows", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "Content Pipeline",
projectId: "proj-8f3a-4b2c",
definition: {
pattern: "sequence",
steps: [
{
id: "research",
name: "Research",
prompt: "Research competitor pricing pages and summarize patterns",
assignedAgent: "claude-code",
agentProfile: "researcher",
},
{
id: "draft",
name: "Draft",
prompt: "Write a pricing page draft based on the research findings",
dependsOn: ["research"],
},
{
id: "review",
name: "Review",
prompt: "Review the draft for clarity, accuracy, and tone",
requiresApproval: true,
dependsOn: ["draft"],
},
],
},
}),
});
const workflow: Workflow = await wfRes.json();
// → { id: "wf-4a7c-e2d1", status: "draft", runNumber: 0, ... }
// 2. Execute the workflow — returns 202 immediately
await fetch(`http://localhost:3000/api/workflows/${workflow.id}/execute`, { method: "POST" });
// 3. Poll for status until all steps are done
interface StepState { stepId: string; status: string }
interface WorkflowStatus { status: string; steps: StepState[] }
const poll = async (): Promise<string> => {
const statusRes: Response = await fetch(`http://localhost:3000/api/workflows/${workflow.id}/status`);
const status: WorkflowStatus = await statusRes.json();
console.log(`Workflow: ${status.status}`);
for (const step of status.steps) {
console.log(` ${step.stepId}: ${step.status}`);
}
return status.status;
};
// 4. Check output documents produced by the workflow
const docsRes: Response = await fetch(`http://localhost:3000/api/workflows/${workflow.id}/documents`);
const docs: { documentId: string }[] = await docsRes.json();
console.log(`${docs.length} documents produced`); Base URL
/api/workflows
Patterns
| Pattern | Description |
|---|---|
| sequence | Steps execute in order, each waits for the previous to complete |
| parallel | Steps execute concurrently with dependency tracking |
| loop | Single step repeats for configurable iterations with time/signal budget |
| planner-executor | Planning phase followed by execution phase |
| checkpoint | Steps with approval gates between execution stages |
| swarm | Concurrent worker agents with configurable concurrency limit |
Endpoints
List Workflows
/api/workflows Retrieve all workflows with task count and output document count aggregation, ordered newest first.
Response Body (Array)
| Field | Type | Req | Description |
|---|---|---|---|
| id | string (UUID) | * | Workflow identifier |
| name | string | * | Workflow name |
| projectId | string (UUID) | — | Associated project |
| definition | string (JSON) | * | Serialized WorkflowDefinition |
| status | enum | * | draft, active, paused, completed, or failed |
| runNumber | number | * | Execution iteration counter |
| taskCount | number | * | Total step tasks created |
| outputDocCount | number | * | Output documents produced |
| createdAt | ISO 8601 | * | Creation timestamp |
| updatedAt | ISO 8601 | * | Last modification |
Fetch all workflows to build a pipeline overview — includes task and document counts:
// List all workflows with aggregated counts
const response: Response = await fetch("http://localhost:3000/api/workflows");
const workflows: Workflow[] = await response.json();
workflows.forEach((wf) => {
const def: { pattern: string } = JSON.parse(wf.definition);
console.log(`${wf.name} [${wf.status}] — ${def.pattern}, ${wf.taskCount} tasks, ${wf.outputDocCount} docs`);
}); Example response:
[
{
"id": "wf-4a7c-e2d1",
"name": "Content Pipeline",
"projectId": "proj-8f3a-4b2c",
"status": "completed",
"runNumber": 2,
"taskCount": 3,
"outputDocCount": 1,
"createdAt": "2026-04-01T08:00:00.000Z",
"updatedAt": "2026-04-01T09:15:00.000Z"
}
] Create Workflow
/api/workflows Create a new workflow in draft state. The definition must include a valid pattern and at least one step.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| name | string | * | Workflow name (non-empty) |
| projectId | string (UUID) | — | Project to associate with |
| definition | WorkflowDefinition | * | Pattern, steps, and optional config |
WorkflowDefinition
| Field | Type | Req | Description |
|---|---|---|---|
| pattern | enum | * | sequence, parallel, loop, planner-executor, checkpoint, or swarm |
| steps | WorkflowStep[] | * | At least one step |
| loopConfig | LoopConfig | — | Required for loop pattern |
| swarmConfig | SwarmConfig | — | Optional concurrency settings for swarm |
| sourceTaskId | string | — | Task that originated this workflow |
WorkflowStep
| Field | Type | Req | Description |
|---|---|---|---|
| id | string | * | Step identifier (unique within workflow) |
| name | string | * | Step display name |
| prompt | string | * | Agent prompt for this step |
| requiresApproval | boolean | — | Pause for human approval before executing |
| dependsOn | string[] | — | Step IDs that must complete first |
| assignedAgent | string | — | Override agent runtime for this step |
| agentProfile | string | — | Override profile for this step |
| documentIds | string[] | — | Documents available to this step |
LoopConfig
| Field | Type | Req | Description |
|---|---|---|---|
| maxIterations | number | * | Maximum loop iterations |
| timeBudgetMs | number | — | Total time budget in milliseconds |
| assignedAgent | string | — | Agent runtime for loop iterations |
| agentProfile | string | — | Profile for loop iterations |
| completionSignals | string[] | — | Agent output signals that end the loop early |
Response 201 Created — Workflow with status: "draft", runNumber: 0
Errors: 400 — Invalid name, definition, or assignment validation failure
Create a parallel workflow where independent steps run concurrently, with a final step that depends on both:
// Create a parallel workflow — pricing and features run concurrently,
// synthesis waits for both to finish
const response: Response = await fetch("http://localhost:3000/api/workflows", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "Competitive Analysis",
projectId: "proj-8f3a-4b2c",
definition: {
pattern: "parallel",
steps: [
{ id: "pricing", name: "Pricing Research", prompt: "Analyze competitor pricing models" },
{ id: "features", name: "Feature Comparison", prompt: "Compare feature sets across top 5 competitors" },
{ id: "synthesis", name: "Synthesis", prompt: "Combine pricing and feature analysis into a final report", dependsOn: ["pricing", "features"] },
],
},
}),
});
const workflow: Workflow = await response.json();
console.log(workflow.id); // "wf-4a7c-e2d1"
console.log(workflow.status); // "draft" Example response:
{
"id": "wf-4a7c-e2d1",
"name": "Competitive Analysis",
"projectId": "proj-8f3a-4b2c",
"status": "draft",
"runNumber": 0,
"taskCount": 0,
"outputDocCount": 0,
"createdAt": "2026-04-03T10:00:00.000Z",
"updatedAt": "2026-04-03T10:00:00.000Z"
} Update Workflow
/api/workflows/{id} Edit a draft workflow's name or definition, or pause an active workflow. Active/paused workflows cannot be edited — only paused.
Mode A — Edit (draft, completed, or failed workflows only):
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| name | string | — | Updated name |
| definition | WorkflowDefinition | — | Updated definition |
Mode B — Pause (active workflows only):
{ "status": "paused" }Editing a completed or failed workflow resets it to draft and clears execution state.
Errors: 404 — Not found, 409 — Cannot edit active/paused workflow, invalid status transition
Update the name and add a step to a draft workflow:
// Rename a draft workflow before execution
await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Competitive Analysis v2" }),
}); Delete Workflow
/api/workflows/{id} Delete a workflow and all related resources. Active workflows cannot be deleted — pause first.
Response 200 — { "deleted": true }
Cascade deletes: usage ledger, agent logs, notifications, documents, learned context, tasks, then workflow.
Errors: 404 — Not found, 409 — Cannot delete active workflow
// Delete a workflow — must not be in active state
const res: Response = await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1", { method: "DELETE" });
if (res.status === 409) {
console.log("Pause the workflow first before deleting");
} Execute Workflow
/api/workflows/{id}/execute Start workflow execution. Returns 202 immediately while the workflow engine runs steps asynchronously. Increments runNumber on each execution.
Response 202 Accepted — { "status": "started", "workflowId": "..." }
For completed or failed workflows, re-execution resets the internal state and starts fresh.
Errors: 404 — Not found, 409 — Already running
Execute a workflow and track its progress — the engine creates tasks for each step automatically:
// Start the workflow — returns immediately while steps run in background
const res: Response = await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1/execute", {
method: "POST",
});
if (res.status === 202) {
const { workflowId }: { workflowId: string } = await res.json();
console.log("Workflow started — poll /status to track progress");
} else if (res.status === 409) {
console.log("Workflow is already running");
} Example response:
{
"status": "started",
"workflowId": "wf-4a7c-e2d1"
} Get Workflow Status
/api/workflows/{id}/status Get detailed execution status including per-step states, loop progress, documents, and run history.
The response shape depends on the workflow pattern:
Non-loop patterns return steps[] with per-step state:
Step State Fields
| Field | Type | Req | Description |
|---|---|---|---|
| stepId | string | * | Step identifier |
| status | enum | * | pending, running, completed, failed, waiting_approval, or waiting_dependencies |
| taskId | string | — | Associated task ID (once created) |
| result | string | — | Step execution result |
| error | string | — | Error message if failed |
| startedAt | ISO 8601 | — | Step start time |
| completedAt | ISO 8601 | — | Step completion time |
Loop patterns return loopState with iteration history:
Loop State Fields
| Field | Type | Req | Description |
|---|---|---|---|
| currentIteration | number | * | Current iteration index |
| iterations | array | * | Per-iteration status, result, timing |
| status | enum | * | running, completed, paused, or failed |
| stopReason | enum | — | max_iterations, time_budget, agent_signaled, human_cancel, human_pause, or error |
| totalDurationMs | number | — | Total execution time |
Both shapes include stepDocuments (per-task output files), parentDocuments (workflow-level inputs), and runHistory (per-run aggregation).
Errors: 404 — Not found
Poll workflow status to build a real-time progress tracker:
// Poll workflow status to display step-by-step progress
const response: Response = await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1/status");
const status: WorkflowStatus = await response.json();
console.log(`Workflow: ${status.status}, Run #${status.runNumber}`);
// Display per-step progress
for (const step of status.steps) {
const duration: string = step.completedAt && step.startedAt
? ((new Date(step.completedAt).getTime() - new Date(step.startedAt).getTime()) / 1000).toFixed(1) + "s"
: "—";
console.log(` ${step.stepId}: ${step.status} (${duration})`);
} Example response for a running sequence workflow:
{
"status": "active",
"runNumber": 1,
"steps": [
{
"stepId": "research",
"status": "completed",
"taskId": "task-b3e1-7f2a",
"result": "Found 5 competitor pricing pages with common patterns...",
"startedAt": "2026-04-03T10:01:00.000Z",
"completedAt": "2026-04-03T10:03:22.000Z"
},
{
"stepId": "draft",
"status": "running",
"taskId": "task-d9c4-1a8e",
"startedAt": "2026-04-03T10:03:23.000Z"
},
{
"stepId": "review",
"status": "waiting_dependencies"
}
],
"stepDocuments": {},
"parentDocuments": [],
"runHistory": []
} List Workflow Documents
/api/workflows/{id}/documents List all document bindings for a workflow, including step-scoped and workflow-level documents.
Response Body (Array)
| Field | Type | Req | Description |
|---|---|---|---|
| bindingId | string (UUID) | * | Binding record ID |
| documentId | string (UUID) | * | Document ID |
| stepId | string | — | Step scope (null = all steps) |
| document | object | — | Document metadata (name, MIME type, size, direction, status) |
List all documents attached to a workflow, including step-scoped and workflow-wide bindings:
// List workflow documents to see inputs and outputs
const response: Response = await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1/documents");
const docs: { bindingId: string; documentId: string; stepId?: string }[] = await response.json();
// Separate workflow-wide vs step-scoped documents
const globalDocs = docs.filter((d) => !d.stepId);
const scoped = docs.filter((d) => d.stepId);
console.log(`${globalDocs.length} global, ${scoped.length} step-scoped documents`); Attach Documents
/api/workflows/{id}/documents Attach documents to a workflow. Optionally scope to a specific step. Duplicate bindings are silently ignored.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| documentIds | string[] | * | Document UUIDs to attach (non-empty) |
| stepId | string | — | Scope to a specific step (null = all steps) |
Response 201 Created — { "attached": 2, "workflowId": "...", "stepId": null }
Errors: 400 — Empty array, 404 — Workflow or documents not found
Attach reference documents to a specific step — only that step’s agent will see these files:
// Attach documents scoped to the research step only
const response: Response = await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1/documents", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
documentIds: ["doc-competitor-data-q4", "doc-pricing-spreadsheet"],
stepId: "research",
}),
});
const { attached }: { attached: number } = await response.json();
console.log(`Attached ${attached} documents to research step`); Remove Documents
/api/workflows/{id}/documents Remove document bindings. Pass specific IDs to remove selectively, or omit to remove all bindings.
Request Body (optional)
| Field | Type | Req | Description |
|---|---|---|---|
| documentIds | string[] | — | Specific documents to unbind |
| stepId | string | — | Filter removal by step |
Response 200 — { "ok": true }
// Remove a specific document binding
await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1/documents", {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ documentIds: ["doc-competitor-data-q4"] }),
}); Retry Step
/api/workflows/{id}/steps/{stepId}/retry Retry a failed workflow step. Returns 202 while the retry runs asynchronously.
Response 202 Accepted — { "status": "retry_started", "workflowId": "...", "stepId": "..." }
Retry a failed step without re-running the entire workflow:
// Retry a failed step — the workflow engine picks up from here
const res: Response = await fetch("http://localhost:3000/api/workflows/wf-4a7c-e2d1/steps/research/retry", {
method: "POST",
});
if (res.status === 202) {
console.log("Retry started — downstream steps will re-execute when this completes");
} Create from AI Assist
/api/workflows/from-assist Create a workflow with pre-built step tasks in a single atomic operation. Optionally execute immediately.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| name | string | * | Workflow name |
| projectId | string (UUID) | — | Project association |
| definition | WorkflowDefinition | * | Pattern and steps |
| priority | number | — | Default priority for step tasks(default: 2) |
| assignedAgent | string | — | Default agent for steps |
| executeImmediately | boolean | — | Start execution after creation |
Response Body
| Field | Type | Req | Description |
|---|---|---|---|
| workflow | object | * | Created workflow |
| taskIds | string[] | * | IDs of created step tasks |
| status | enum | * | created or started |
Errors: 400 — Validation failure
Create a workflow and start it immediately in one call — useful for AI-assisted task decomposition:
// Create and execute in one call — no need for a separate execute request
const response: Response = await fetch("http://localhost:3000/api/workflows/from-assist", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "Quick Analysis",
definition: {
pattern: "sequence",
steps: [
{ id: "analyze", name: "Analyze", prompt: "Analyze the Q4 revenue dataset and produce a summary" },
],
},
executeImmediately: true,
}),
});
const { workflow, taskIds, status }: { workflow: Workflow; taskIds: string[]; status: string } = await response.json();
console.log(`Workflow ${workflow.id}: ${status}`); // "started"
console.log(`Task IDs: ${taskIds.join(", ")}`); Example response:
{
"workflow": {
"id": "wf-7b2e-c4a9",
"name": "Quick Analysis",
"status": "active",
"runNumber": 1
},
"taskIds": ["task-e5f1-8d3c"],
"status": "started"
} Workflow Statuses
| Status | Description |
|---|---|
| draft | Created but not yet executed |
| active | Currently executing steps |
| paused | Execution suspended (can resume via execute) |
| completed | All steps finished successfully |
| failed | One or more steps failed |
Execution State
Workflow execution state is persisted inside the definition field:
_state— Used by sequence, parallel, planner-executor, checkpoint, and swarm patterns. TrackscurrentStepIndex, per-step states, and overall workflow status._loopState— Used by loop pattern. Tracks iteration count, per-iteration results, stop reason, and timing.
Both are automatically cleared when a completed or failed workflow is re-executed or edited back to draft.