Version: v3.3
Last Updated: 2026-02-15
Base URL: http://localhost:3001/api
- Authentication
- Workflow CRUD
- Workflow Runs
- Gate Operations
- Tool Policies
- Sandbox Policies
- Task Dependencies (NEW — v3.3)
- Crash-Recovery Checkpointing (NEW — v3.3)
- Observational Memory (NEW — v3.3)
- Agent Filter (NEW — v3.3)
- WebSocket Events
- TypeScript Interfaces
- Error Responses
All API endpoints currently accept an optional authentication key via x-api-key header. RBAC permissions are enforced based on workflow ACLs (Access Control Lists).
Header:
x-api-key: <your-api-key>
Permissions:
view— View workflow definitions and runscreate— Create new workflowsedit— Update workflow definitionsdelete— Delete workflows (owner only)execute— Start workflow runs, approve gates
List all workflows (metadata only, filtered by user permissions).
Request:
curl http://localhost:3001/api/workflowsResponse:
[
{
"id": "feature-dev",
"name": "Feature Development Workflow",
"version": 2,
"description": "End-to-end feature development pipeline"
},
{
"id": "security-audit",
"name": "Security Audit & Remediation",
"version": 1,
"description": "Scan, prioritize, and fix security issues"
}
]Status Codes:
200 OK— Success
Permissions: Any authenticated user can list workflows they have view permission for.
Get a specific workflow definition (full YAML content).
Request:
curl http://localhost:3001/api/workflows/feature-devResponse:
{
"id": "feature-dev",
"name": "Feature Development Workflow",
"version": 2,
"description": "End-to-end feature development pipeline",
"config": {
"timeout": 7200,
"fresh_session_default": true,
"progress_file": "progress.md",
"telemetry_tags": ["workflow", "feature-dev"]
},
"agents": [
{
"id": "planner",
"name": "Planner",
"role": "planner",
"model": "github-copilot/claude-opus-4.6",
"description": "Decomposes tasks into user stories"
},
{
"id": "developer",
"name": "Developer",
"role": "developer",
"model": "github-copilot/claude-sonnet-4.5",
"description": "Implements features"
}
],
"steps": [
{
"id": "plan",
"name": "Plan: Decompose into stories",
"type": "agent",
"agent": "planner",
"input": "Decompose this task into stories...",
"output": {
"file": "plan.yml"
},
"acceptance_criteria": ["stories:"],
"on_fail": {
"retry": 2,
"escalate_to": "human"
},
"timeout": 600
}
],
"variables": {
"repo_path": "{{task.git.worktreePath}}",
"test_command": "npm test"
}
}Status Codes:
200 OK— Success404 Not Found— Workflow not found403 Forbidden— No view permission
Headers:
ETag: "workflow:feature-dev:2"
X-Resource-Revision: 2Permissions: Requires view permission.
Resolve workflow-specific actions and provenance for the current identity. Clients use this server-owned result to distinguish editable user workflows from built-in or shared read-only definitions.
Request:
curl http://localhost:3001/api/workflows/feature-dev/accessResponse:
{
"workflowId": "feature-dev",
"canView": true,
"canEdit": false,
"canExecute": true,
"canDuplicate": true,
"readOnlyReason": "Built-in workflows are read-only. Duplicate this workflow to customize it.",
"provenance": {
"kind": "built-in",
"owner": "system",
"createdBy": "system",
"updatedBy": "system",
"createdAt": "2026-02-09T12:00:00Z",
"updatedAt": "2026-02-09T14:30:00Z"
}
}provenance.kind is built-in, user-owned, or shared. canEdit and canExecute combine the authenticated request permissions with the workflow ACL decision. canDuplicate reflects whether the authenticated request may create workflows. A read-only response includes an actionable reason suitable for the workflow browser.
Status Codes:
200 OK— Access and provenance resolved403 Forbidden— No view permission404 Not Found— Workflow not found
Permissions: Requires workflow:read and workflow-level view permission.
Create a new workflow.
Request:
curl -X POST http://localhost:3001/api/workflows \
-H "Content-Type: application/json" \
-d '{
"id": "hello-world",
"name": "Hello World Workflow",
"version": 1,
"description": "A simple test workflow",
"agents": [
{
"id": "writer",
"name": "Writer",
"role": "developer",
"model": "github-copilot/claude-sonnet-4.5",
"description": "Writes messages"
}
],
"steps": [
{
"id": "greet",
"name": "Greet user",
"type": "agent",
"agent": "writer",
"input": "Write a hello message",
"output": {
"file": "greeting.md"
}
}
]
}'Response:
{
"success": true,
"workflowId": "hello-world"
}Status Codes:
201 Created— Workflow created successfully400 Bad Request— Validation error (missing required fields, invalid references)409 Conflict— Workflow ID already exists
Permissions: Any authenticated user can create workflows (becomes owner).
Validation:
id: Required, alphanumeric + dashes, max 100 charactersname: Required, max 200 charactersversion: Required, integer ≥ 0description: Required, max 2000 charactersagents: Required, 1-20 agentssteps: Required, 1-50 steps- All
step.agentreferences must match anagents.id - All
on_fail.retry_stepreferences must match asteps.id
Update an existing workflow (auto-increments version).
Request:
curl -X PUT http://localhost:3001/api/workflows/hello-world \
-H "Content-Type: application/json" \
-H 'If-Match: "workflow:hello-world:1"' \
-d '{
"id": "hello-world",
"name": "Hello World Workflow v2",
"version": 1,
"description": "Updated workflow with farewell step",
"agents": [
{
"id": "writer",
"name": "Writer",
"role": "developer",
"model": "github-copilot/claude-sonnet-4.5",
"description": "Writes messages"
}
],
"steps": [
{
"id": "greet",
"name": "Greet user",
"type": "agent",
"agent": "writer",
"input": "Write a hello message",
"output": {
"file": "greeting.md"
}
},
{
"id": "farewell",
"name": "Say goodbye",
"type": "agent",
"agent": "writer",
"input": "Write a goodbye message",
"output": {
"file": "farewell.md"
}
}
]
}'Response:
{
"success": true,
"version": 2
}Status Codes:
200 OK— Workflow updated successfully400 Bad Request— Validation error or ID mismatch404 Not Found— Workflow not found403 Forbidden— No edit permission409 Conflict— Workflow has changed since the suppliedIf-Matchrevision
Permissions: Requires edit permission.
Notes:
- Version is auto-incremented (ignore
versionin request body) - Workflow
versionis also the optimistic-concurrency revision. Read the workflow ETag, then send it back withIf-Matchon update. - Workflow definitions include
createdBy,updatedBy,createdAt, andupdatedAtwhen saved through the API. - Active runs continue with their snapshotted version (no interruption)
- Changes are logged to
.veritas-kanban/workflows/.audit.jsonl
Conflict response:
{
"code": "CONFLICT",
"message": "workflow hello-world has changed since it was loaded. Reload and retry with the latest revision.",
"details": {
"resourceType": "workflow",
"resourceId": "hello-world",
"expectedRevision": 1,
"currentRevision": 2,
"current": {
"id": "hello-world",
"version": 2,
"description": "Latest workflow body"
}
}
}Delete a workflow.
Request:
curl -X DELETE http://localhost:3001/api/workflows/hello-worldResponse:
(Empty body, 204 status)
Status Codes:
204 No Content— Workflow deleted successfully404 Not Found— Workflow not found403 Forbidden— Not owner (only owners can delete)
Permissions: Requires delete permission (owner only).
Notes:
- Deletes workflow YAML file
- Does NOT delete historical run data (runs remain accessible)
- Audit event logged
Validate an unsaved workflow without starting a run. The response includes lint messages, executable checks, and a skill audit for referenced shared skills.
Request:
curl -X POST http://localhost:3001/api/workflows/authoring/dry-run \
-H "Content-Type: application/json" \
-d '{
"workflow": {
"id": "release-helper",
"name": "Release Helper",
"version": 1,
"description": "Uses a shared release skill",
"agents": [
{
"id": "runner",
"name": "Runner",
"role": "developer",
"tools": ["Read", "skill:release-helper"]
}
],
"steps": [{ "id": "run", "name": "Run", "type": "agent", "agent": "runner" }]
},
"context": { "clientMode": "remote" }
}'Response excerpt:
{
"status": "blocked",
"canRun": false,
"checks": [{ "id": "skill", "label": "Skill audit", "status": "fail" }],
"messages": [
{
"category": "skill",
"severity": "error",
"message": "Skill Release Helper has no persisted scan and cannot run in remote mode."
}
],
"skillAudit": {
"status": "fail",
"mode": "remote",
"references": [
{
"reference": "release-helper",
"skillId": "release-helper",
"status": "blocked",
"message": "Skill Release Helper has no persisted scan and cannot run in remote mode."
}
]
}
}The skill audit recognizes skill:<id> and skill/<id> references in agents,
tools, steps, variables, inputs, and descriptions. Local mode warns on unscanned
skills. Remote and cloud modes fail missing, unscanned, or blocked skills unless
the skill has an active reviewed exception.
Dry-run responses may include pipelineSummary when a workflow declares
pipeline. Pipeline lint validates the parent agent, subagent role references,
deliverable contracts, verification steps, dependencies, and whether each role is
used by a workflow step or parallel substep.
Returns reusable workflow recipes. v5 recipes include .openclaw Audit, which
materializes an orchestrated pipeline with config, storage, security, docs, and
follow-up task subagent roles.
Materializes a recipe into workflow JSON/YAML and a preview. When a recipe has a
pipeline, preview.pipeline contains role, status, scope, deliverable,
dependency, verification, and telemetry-budget metadata for the authoring UI.
Dry-run a saved workflow definition with the same response shape and context
rules as /api/workflows/authoring/dry-run.
Start a new workflow run.
Request:
curl -X POST http://localhost:3001/api/workflows/feature-dev/runs \
-H "Content-Type: application/json" \
-d '{
"taskId": "US-42",
"context": {
"clientMode": "remote",
"priority": "high",
"deadline": "2026-02-15"
}
}'Request Body:
{
taskId?: string; // Optional: VK task ID to associate with run
context?: {
clientMode?: "local" | "remote" | "cloud"; // Optional: workflow skill gate mode
[key: string]: unknown;
};
budget?: {
enabled?: boolean;
limits?: {
totalTokens?: number;
costUsd?: number;
toolCalls?: number;
runtimeSeconds?: number;
idleRuntimeSeconds?: number;
retries?: number;
fanOut?: number;
};
softThresholdPercent?: number;
hardAction?: "pause" | "require-approval" | "downgrade" | "cancel";
downgradeModel?: string;
};
}Before a run starts, the server dry-runs the saved workflow and blocks remote or
cloud execution when a referenced shared skill is missing, unscanned, or blocked.
The run context includes the resulting skillAudit summary when execution is
allowed. Workflows with pipeline metadata also include context.pipeline,
which rolls subagent role status and time/token telemetry into the run record.
Run budgets are merged with workspace, workflow, and workflow-agent defaults
using the strictest positive limit. Soft thresholds write budget-policy
governance traces. Hard thresholds pause/block, require approval, downgrade the
model route, or cancel according to the effective policy.
The workflow root reserves durable admission capacity before the run becomes
active. Every provider-backed step then obtains a child reservation against
its resolved provider and selected host before its attempt becomes running.
The response exposes the root binding and the latest step decision without
including prompts or credentials. Use /api/admission?workflowRunId=<run-id>
or vk admission list --workflow-run <run-id> for current lease and limiting
policy details.
Response:
{
"id": "run_20260209_abc123",
"workflowId": "feature-dev",
"workflowVersion": 2,
"taskId": "US-42",
"status": "running",
"currentStep": "plan",
"context": {
"task": {
"id": "US-42",
"title": "Implement user registration",
"description": "Add registration endpoint with email validation"
},
"priority": "high",
"deadline": "2026-02-15"
},
"startedAt": "2026-02-09T12:00:00Z",
"steps": [
{
"stepId": "plan",
"status": "running",
"agent": "planner",
"startedAt": "2026-02-09T12:00:00Z",
"retries": 0
},
{
"stepId": "implement",
"status": "pending",
"retries": 0
}
]
}Status Codes:
201 Created— Run started successfully400 Bad Request— Validation error404 Not Found— Workflow not found403 Forbidden— No execute permission
Permissions: Requires execute permission.
Notes:
- Workflow execution begins immediately (asynchronous)
- Monitor progress via WebSocket or polling
/api/workflow-runs/:id - Run state persisted to
.veritas-kanban/workflow-runs/{runId}/run.json
List workflow runs with optional filters.
Query Parameters:
workflowId(string, optional) — Filter by workflow IDtaskId(string, optional) — Filter by task IDstatus(string, optional) — Filter by status:pending,running,blocked,completed,failed
Request:
# All runs
curl http://localhost:3001/api/workflow-runs
# Runs for a specific workflow
curl "http://localhost:3001/api/workflow-runs?workflowId=feature-dev"
# Runs for a specific task
curl "http://localhost:3001/api/workflow-runs?taskId=US-42"
# Failed runs only
curl "http://localhost:3001/api/workflow-runs?status=failed"Response:
[
{
"id": "run_20260209_abc123",
"workflowId": "feature-dev",
"workflowVersion": 2,
"taskId": "US-42",
"status": "completed",
"startedAt": "2026-02-09T12:00:00Z",
"completedAt": "2026-02-09T12:45:00Z",
"duration": 2700,
"stepsCompleted": 7,
"stepsTotal": 7
},
{
"id": "run_20260209_def456",
"workflowId": "security-audit",
"workflowVersion": 1,
"status": "running",
"currentStep": "fix",
"startedAt": "2026-02-09T13:00:00Z",
"stepsCompleted": 2,
"stepsTotal": 5
}
]Status Codes:
200 OK— Success
Permissions: Filtered by workflow view permissions.
Get full details of a specific workflow run.
Request:
curl http://localhost:3001/api/workflow-runs/run_20260209_abc123Response:
{
"id": "run_20260209_abc123",
"workflowId": "feature-dev",
"workflowVersion": 2,
"taskId": "US-42",
"status": "completed",
"currentStep": null,
"context": {
"task": { "id": "US-42", "title": "..." },
"plan": { "stories": [...] },
"implement": { "changes": "..." }
},
"startedAt": "2026-02-09T12:00:00Z",
"completedAt": "2026-02-09T12:45:00Z",
"lastCheckpoint": "2026-02-09T12:45:00Z",
"steps": [
{
"stepId": "plan",
"status": "completed",
"agent": "planner",
"sessionKey": "session_xyz",
"startedAt": "2026-02-09T12:00:00Z",
"completedAt": "2026-02-09T12:10:00Z",
"duration": 600,
"retries": 0,
"output": ".veritas-kanban/workflow-runs/run_20260209_abc123/step-outputs/plan.yml"
},
{
"stepId": "implement",
"status": "completed",
"agent": "developer",
"startedAt": "2026-02-09T12:10:00Z",
"completedAt": "2026-02-09T12:35:00Z",
"duration": 1500,
"retries": 1,
"output": ".veritas-kanban/workflow-runs/run_20260209_abc123/step-outputs/implement-0.md",
"loopState": {
"totalIterations": 5,
"currentIteration": 5,
"completedIterations": 5,
"failedIterations": 0
}
}
]
}Status Codes:
200 OK— Success404 Not Found— Run not found403 Forbidden— No view permission
Permissions: Requires view permission on the workflow.
Get currently running workflow runs only.
Request:
curl http://localhost:3001/api/workflow-runs/activeResponse:
[
{
"id": "run_20260209_def456",
"workflowId": "security-audit",
"workflowVersion": 1,
"status": "running",
"currentStep": "fix",
"startedAt": "2026-02-09T13:00:00Z",
"stepsCompleted": 2,
"stepsTotal": 5
}
]Status Codes:
200 OK— Success
Permissions: Filtered by workflow view permissions.
Notes: Returns metadata only (not full run state).
Get aggregated workflow statistics for a given period.
Query Parameters:
period(string, optional) — Period for stats:24h,7d,30d(default:7d)
Request:
curl "http://localhost:3001/api/workflow-runs/stats?period=7d"Response:
{
"period": "7d",
"totalWorkflows": 5,
"activeRuns": 2,
"completedRuns": 42,
"failedRuns": 8,
"avgDuration": 1800000,
"successRate": 0.84,
"perWorkflow": [
{
"workflowId": "feature-dev",
"workflowName": "Feature Development Workflow",
"runs": 25,
"completed": 20,
"failed": 5,
"successRate": 0.8,
"avgDuration": 1800000
},
{
"workflowId": "security-audit",
"workflowName": "Security Audit & Remediation",
"runs": 17,
"completed": 15,
"failed": 2,
"successRate": 0.88,
"avgDuration": 900000
}
]
}Field Descriptions:
avgDuration— Average duration in millisecondssuccessRate— Decimal (0.0 to 1.0) representing percentageperWorkflow— Per-workflow breakdown
Status Codes:
200 OK— Success400 Bad Request— Invalid period value
Permissions: Filtered by workflow view permissions.
Resume a blocked workflow run (after human approval or escalation).
Request:
curl -X POST http://localhost:3001/api/workflow-runs/run_20260209_abc123/resume \
-H "Content-Type: application/json" \
-d '{
"context": {
"reviewerComments": "Looks good, proceed"
}
}'Request Body:
{
context?: Record<string, unknown>; // Optional: Additional context for resume
}Response:
{
"id": "run_20260209_abc123",
"workflowId": "feature-dev",
"status": "running",
"currentStep": "deploy",
...
}Status Codes:
200 OK— Run resumed successfully400 Bad Request— Run not blocked (current status: running/completed/failed)404 Not Found— Run not found403 Forbidden— No execute permission
Permissions: Requires execute permission on the workflow.
Notes:
- Only runs with status
blockedcan be resumed - Workflow execution continues from where it left off
- Context can be updated during resume
Approve a gate step (allows workflow to continue).
Request:
curl -X POST http://localhost:3001/api/workflow-runs/run_20260209_abc123/steps/quality-gate/approveResponse:
{
"id": "run_20260209_abc123",
"workflowId": "feature-dev",
"status": "running",
"context": {
"_gateApproval": {
"stepId": "quality-gate",
"approved": true,
"approvedBy": "user-123",
"approvedAt": "2026-02-09T14:00:00Z"
}
},
...
}Status Codes:
200 OK— Gate approved, run resumed400 Bad Request— Step not awaiting approval or not a gate step404 Not Found— Run or step not found403 Forbidden— No execute permission
Permissions: Requires execute permission on the workflow.
Notes:
- Only works for steps with
type: gateand statusfailed - Approval context is added to run context
- Workflow continues execution
Reject a gate step (marks workflow as failed).
Request:
curl -X POST http://localhost:3001/api/workflow-runs/run_20260209_abc123/steps/quality-gate/rejectResponse:
{
"id": "run_20260209_abc123",
"workflowId": "feature-dev",
"status": "failed",
"error": "Step quality-gate rejected by user-123",
"completedAt": "2026-02-09T14:00:00Z",
...
}Status Codes:
200 OK— Gate rejected, run marked as failed400 Bad Request— Step not awaiting approval or not a gate step404 Not Found— Run or step not found403 Forbidden— No execute permission
Permissions: Requires execute permission on the workflow.
Get detailed status of a specific step (useful for parallel sub-steps).
Request:
curl http://localhost:3001/api/workflow-runs/run_20260209_abc123/steps/implement/statusResponse:
{
"stepId": "implement",
"status": "completed",
"agent": "developer",
"sessionKey": "session_xyz",
"startedAt": "2026-02-09T12:10:00Z",
"completedAt": "2026-02-09T12:35:00Z",
"duration": 1500,
"retries": 1,
"output": ".veritas-kanban/workflow-runs/run_20260209_abc123/step-outputs/implement-0.md",
"loopState": {
"totalIterations": 5,
"currentIteration": 5,
"completedIterations": 5,
"failedIterations": 0
}
}Status Codes:
200 OK— Success404 Not Found— Run or step not found403 Forbidden— No view permission
Permissions: Requires view permission on the workflow.
List all tool policies (default + custom).
Request:
curl http://localhost:3001/api/tool-policiesResponse:
[
{
"role": "planner",
"allowed": ["read", "web_search", "web_fetch", "browser", "image", "nodes"],
"denied": ["write", "edit", "exec", "message"],
"description": "Analysis and planning — read-only access"
},
{
"role": "developer",
"allowed": ["*"],
"denied": [],
"description": "Feature implementation — full access"
},
{
"role": "custom-auditor",
"allowed": ["read", "web_search"],
"denied": ["exec", "write", "edit"],
"description": "Security auditor — read-only with web access"
}
]Status Codes:
200 OK— Success
Permissions: Public (no authentication required).
Get a specific tool policy by role.
Request:
curl http://localhost:3001/api/tool-policies/plannerResponse:
{
"role": "planner",
"allowed": ["read", "web_search", "web_fetch", "browser", "image", "nodes"],
"denied": ["write", "edit", "exec", "message"],
"description": "Analysis and planning — read-only access"
}Status Codes:
200 OK— Success404 Not Found— Policy not found
Permissions: Public.
Create a new custom tool policy.
Request:
curl -X POST http://localhost:3001/api/tool-policies \
-H "Content-Type: application/json" \
-d '{
"role": "custom-auditor",
"allowed": ["read", "web_search", "web_fetch", "browser"],
"denied": ["exec", "write", "edit"],
"description": "Security auditor — read-only with web access"
}'Request Body:
{
role: string; // Required: role name (alphanumeric + dash/underscore, max 50 chars)
allowed: string[]; // Required: tool names (use '*' for all tools)
denied: string[]; // Required: tool names (takes precedence over allowed)
description: string; // Required: what this role does (max 500 chars)
}Response:
{
"success": true,
"role": "custom-auditor"
}Status Codes:
201 Created— Policy created successfully400 Bad Request— Validation error409 Conflict— Role already exists
Permissions: Any authenticated user can create custom policies.
Validation:
role: Required, alphanumeric + dash/underscore, max 50 charactersallowed: Required array, max 100 toolsdenied: Required array (can be empty), max 100 toolsdescription: Required, max 500 characters- Cannot use reserved role names (planner, developer, reviewer, tester, deployer)
Update an existing tool policy (including defaults).
Request:
curl -X PUT http://localhost:3001/api/tool-policies/custom-auditor \
-H "Content-Type: application/json" \
-d '{
"role": "custom-auditor",
"allowed": ["read", "web_search"],
"denied": ["exec", "write", "edit", "message"],
"description": "Updated auditor policy"
}'Response:
{
"success": true,
"role": "custom-auditor"
}Status Codes:
200 OK— Policy updated successfully400 Bad Request— Validation error or role mismatch404 Not Found— Policy not found
Permissions: Any authenticated user can update policies.
Notes: Default policies can be edited but not deleted.
Delete a custom tool policy.
Request:
curl -X DELETE http://localhost:3001/api/tool-policies/custom-auditorResponse:
(Empty body, 204 status)
Status Codes:
204 No Content— Policy deleted successfully400 Bad Request— Cannot delete default policy404 Not Found— Policy not found
Permissions: Any authenticated user can delete custom policies.
Notes: Default policies (planner, developer, reviewer, tester, deployer) cannot be deleted.
Validate if a specific tool is allowed for a role.
Request:
curl -X POST http://localhost:3001/api/tool-policies/planner/validate \
-H "Content-Type: application/json" \
-d '{
"tool": "exec"
}'Request Body:
{
tool: string; // Required: tool name to validate
}Response:
{
"role": "planner",
"tool": "exec",
"allowed": false,
"reason": "Tool 'exec' is in the denied list"
}Status Codes:
200 OK— Success404 Not Found— Policy not found
Permissions: Public.
Workflow agents can set sandboxPresetId to select a sandbox policy preset for
that role. The workflow executor dry-runs the preset against the selected
provider before launching the step. Required unsupported controls block the
step before execution and write a sandbox-policy governance trace; advisory
unsupported controls continue with warnings.
Use /api/sandbox-policies/validate to preflight a workflow agent's preset in
the authoring UI or custom automation. Public preflights must include a
providerRuntimeManifestDigest currently registered by a connected agent host;
the API rejects stale, disconnected, unknown, or provider-mismatched manifests.
Presets can also be assigned visually in the workflow authoring panel.
Sandbox policies are complementary to tool policies:
- Tool policies decide which Veritas/OpenClaw tools a workflow role may use.
- Sandbox policies decide what the underlying provider process may access at launch time: filesystem paths, network egress, environment variables, and credentials.
Workflow definitions can set config.budget for workflow-wide defaults and
agents[].budget for stricter role-specific caps. Launch callers can also pass
a stricter budget override to POST /api/workflows/:id/runs.
Supported budget limits:
totalTokens,inputTokens, andoutputTokenscostUsdtoolCallsruntimeSecondsandidleRuntimeSecondsretriesfanOut
Budget evaluation is policy enforcement, not dashboard-only analytics. Soft
thresholds create visible warnings and budget-policy governance traces. Hard
thresholds enforce the configured action:
pauseorrequire-approvalblocks the workflow run for operator review.downgraderecords a routed decision and appliesdowngradeModelto Codex workflow steps.cancelfails the run immediately.
The run record includes budget.usage, budget.thresholdEvents, budget.traceIds,
and budget.modelOverride so run detail, timelines, and completion packets can
show exactly what happened.
Get the full dependency graph for a task (recursive tree traversal).
Request:
curl http://localhost:3001/api/tasks/US-42/dependenciesResponse:
{
"task": "US-42",
"depends_on": [
{
"id": "US-40",
"title": "Create database schema",
"status": "done",
"depends_on": []
},
{
"id": "US-41",
"title": "Implement auth middleware",
"status": "in-progress",
"depends_on": [
{
"id": "US-39",
"title": "Setup JWT library",
"status": "done",
"depends_on": []
}
]
}
],
"blocks": [
{
"id": "US-43",
"title": "Add user permissions",
"status": "todo",
"blocks": []
}
]
}Status Codes:
200 OK— Success404 Not Found— Task not found400 Bad Request— Circular dependency detected
Notes:
- Returns recursive tree with all upstream (depends_on) and downstream (blocks) dependencies
- Cycle detection prevents infinite loops
- Batch-loaded for performance (no N+1 queries)
Add a dependency to a task.
Request:
curl -X POST http://localhost:3001/api/tasks/US-42/dependencies \
-H "Content-Type: application/json" \
-d '{
"dependsOn": "US-40",
"direction": "depends_on"
}'Request Body:
{
dependsOn: string; // Required: task ID of the dependency
direction: 'depends_on' | 'blocks'; // Required: direction of dependency
}Status Codes:
200 OK— Dependency added400 Bad Request— Would create circular dependency404 Not Found— Task not found
Notes:
depends_on: This task depends on the specified taskblocks: This task blocks the specified task- Validates for cycles before adding
Remove a dependency from a task.
Request:
curl -X DELETE http://localhost:3001/api/tasks/US-42/dependencies/US-40?direction=depends_onQuery Parameters:
direction(required):depends_onorblocks
Status Codes:
200 OK— Dependency removed404 Not Found— Task or dependency not found
Save checkpoint state for a task.
Request:
curl -X POST http://localhost:3001/api/tasks/US-42/checkpoint \
-H "Content-Type: application/json" \
-d '{
"state": {
"current_step": 3,
"completed": ["step1", "step2"],
"api_key": "sk-1234567890",
"context": "Working on user authentication"
}
}'Request Body:
{
state: any; // Required: checkpoint state (auto-sanitized for secrets)
}Response:
{
"success": true,
"checkpoint": {
"taskId": "US-42",
"state": {
"current_step": 3,
"completed": ["step1", "step2"],
"api_key": "[REDACTED]",
"context": "Working on user authentication"
},
"createdAt": "2026-02-15T12:00:00Z",
"expiresAt": "2026-02-16T12:00:00Z",
"resumeCount": 0
}
}Status Codes:
200 OK— Checkpoint saved400 Bad Request— State exceeds 1MB limit404 Not Found— Task not found
Notes:
- Auto-sanitizes 20+ secret patterns (API keys, tokens, passwords, etc.)
- 1MB size limit enforced
- 24h expiry with automatic cleanup
- Secrets are sanitized in response but preserved in file for resume
Resume checkpoint state for a task.
Request:
curl http://localhost:3001/api/tasks/US-42/checkpointResponse:
{
"success": true,
"checkpoint": {
"taskId": "US-42",
"state": {
"current_step": 3,
"completed": ["step1", "step2"],
"api_key": "[REDACTED]",
"context": "Working on user authentication"
},
"createdAt": "2026-02-15T12:00:00Z",
"expiresAt": "2026-02-16T12:00:00Z",
"resumeCount": 1
}
}Status Codes:
200 OK— Checkpoint retrieved (increments resumeCount)404 Not Found— Task or checkpoint not found410 Gone— Checkpoint expired
Notes:
- Each GET increments the resumeCount
- Secrets are sanitized in response
- Use for sub-agent context injection
Clear checkpoint state for a task.
Request:
curl -X DELETE http://localhost:3001/api/tasks/US-42/checkpointStatus Codes:
200 OK— Checkpoint cleared404 Not Found— Task or checkpoint not found
Add an observation to a task.
Request:
curl -X POST http://localhost:3001/api/observations \
-H "Content-Type: application/json" \
-d '{
"taskId": "US-42",
"type": "decision",
"content": "Chose React Query over Redux for simpler data fetching and better caching",
"importance": 8
}'Request Body:
{
taskId: string; // Required: task ID
type: 'decision' | 'blocker' | 'insight' | 'context'; // Required
content: string; // Required: observation text (XSS-sanitized)
importance: number; // Required: 1-10 (1-3: low, 4-7: medium, 8-10: high)
}Response:
{
"success": true,
"observation": {
"id": "obs_abc123",
"taskId": "US-42",
"type": "decision",
"content": "Chose React Query over Redux for simpler data fetching and better caching",
"importance": 8,
"createdAt": "2026-02-15T12:00:00Z",
"createdBy": "veritas"
}
}Status Codes:
201 Created— Observation added400 Bad Request— Invalid type or importance score404 Not Found— Task not found
Notes:
- Content is XSS-sanitized (strips script tags, dangerous attributes)
- Activity log entry created automatically
Get all observations for a task.
Request:
curl http://localhost:3001/api/tasks/US-42/observationsResponse:
{
"success": true,
"observations": [
{
"id": "obs_abc123",
"taskId": "US-42",
"type": "decision",
"content": "Chose React Query over Redux",
"importance": 8,
"createdAt": "2026-02-15T12:00:00Z",
"createdBy": "veritas"
},
{
"id": "obs_def456",
"taskId": "US-42",
"type": "blocker",
"content": "Waiting on API key from ops team",
"importance": 6,
"createdAt": "2026-02-15T13:00:00Z",
"createdBy": "codex"
}
]
}Status Codes:
200 OK— Success404 Not Found— Task not found
Full-text search across all observations for all tasks.
Request:
curl "http://localhost:3001/api/observations/search?query=react+query&limit=10&offset=0"Query Parameters:
query(required): search terms (full-text search)limit(optional): max results per page (default: 50, max: 200)offset(optional): pagination offset (default: 0)
Response:
{
"success": true,
"results": [
{
"id": "obs_abc123",
"taskId": "US-42",
"taskTitle": "Implement user authentication",
"type": "decision",
"content": "Chose React Query over Redux for simpler data fetching",
"importance": 8,
"createdAt": "2026-02-15T12:00:00Z",
"createdBy": "veritas"
}
],
"total": 1,
"limit": 10,
"offset": 0
}Status Codes:
200 OK— Success400 Bad Request— Missing query or invalid limit/offset
Notes:
- Searches across all tasks
- Results include task title for context
- Max 200 results per page
Delete an observation.
Request:
curl -X DELETE http://localhost:3001/api/observations/obs_abc123Status Codes:
200 OK— Observation deleted404 Not Found— Observation not found
Notes:
- Activity log entry created automatically
Filter tasks by assigned agent name.
Request:
curl "http://localhost:3001/api/tasks?agent=codex"Query Parameters:
agent(optional): agent name (trimmed, max 100 chars)status(optional): filter by status (todo, in-progress, blocked, done)limit(optional): max results (default: 100)offset(optional): pagination offset (default: 0)
Response:
{
"success": true,
"tasks": [
{
"id": "US-42",
"title": "Implement user authentication",
"status": "in-progress",
"agents": ["codex"],
"priority": "high",
"type": "feature"
},
{
"id": "US-45",
"title": "Add input validation",
"status": "todo",
"agents": ["codex", "veritas"],
"priority": "medium",
"type": "code"
}
],
"total": 2
}Status Codes:
200 OK— Success
Notes:
- Agent name is case-insensitive and trimmed
- Works with existing pagination and filters
- Returns tasks where the agent is in the
agents[]array
All workflow state changes are broadcast via WebSocket for real-time UI updates.
Connection:
const ws = new WebSocket('ws://localhost:3001/ws');
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log('Event:', message.type, message.data);
};Emitted when a workflow run starts.
Payload:
{
"type": "workflow:started",
"data": {
"id": "run_20260209_abc123",
"workflowId": "feature-dev",
"status": "running",
"startedAt": "2026-02-09T12:00:00Z",
...
}
}Emitted when a step begins execution.
Payload:
{
"type": "workflow:step:started",
"data": {
"runId": "run_20260209_abc123",
"stepId": "plan",
"status": "running",
"startedAt": "2026-02-09T12:00:00Z"
}
}Emitted when a step completes successfully.
Payload:
{
"type": "workflow:step:completed",
"data": {
"runId": "run_20260209_abc123",
"stepId": "plan",
"status": "completed",
"completedAt": "2026-02-09T12:10:00Z",
"duration": 600
}
}Emitted when a step fails.
Payload:
{
"type": "workflow:step:failed",
"data": {
"runId": "run_20260209_abc123",
"stepId": "plan",
"status": "failed",
"error": "Acceptance criteria not met",
"completedAt": "2026-02-09T12:10:00Z"
}
}Emitted when a workflow run completes (all steps succeeded).
Payload:
{
"type": "workflow:completed",
"data": {
"id": "run_20260209_abc123",
"workflowId": "feature-dev",
"status": "completed",
"completedAt": "2026-02-09T12:45:00Z",
"duration": 2700
}
}Emitted when a workflow run fails (step failed with no retry policy).
Payload:
{
"type": "workflow:failed",
"data": {
"id": "run_20260209_abc123",
"workflowId": "feature-dev",
"status": "failed",
"error": "Step 'test' failed after 2 retries",
"completedAt": "2026-02-09T12:30:00Z"
}
}Emitted when a workflow run is blocked (waiting for human approval or gate).
Payload:
{
"type": "workflow:blocked",
"data": {
"id": "run_20260209_abc123",
"workflowId": "feature-dev",
"status": "blocked",
"currentStep": "quality-gate",
"error": "Quality gate failed — manual review required"
}
}Emitted when a task associated with a workflow run is updated (triggers counter/metrics refresh).
Payload:
{
"type": "task:changed",
"data": {
"taskId": "US-42",
"status": "done",
"workflowRunId": "run_20260209_abc123"
}
}Emitted when an agent's status changes (for multi-agent coordination).
Payload:
{
"type": "agent:status",
"data": {
"agent": "developer",
"status": "working",
"taskTitle": "Implement feature X",
"workflowRunId": "run_20260209_abc123"
}
}export interface WorkflowDefinition {
id: string;
name: string;
version: number;
description: string;
config?: WorkflowConfig;
agents: WorkflowAgent[];
steps: WorkflowStep[];
variables?: Record<string, unknown>;
schemas?: Record<string, unknown>;
}export interface WorkflowConfig {
timeout?: number; // seconds
fresh_session_default?: boolean;
progress_file?: string;
telemetry_tags?: string[];
budget?: AgentBudgetPolicy;
}export interface WorkflowAgent {
id: string;
name: string;
role: string; // maps to tool policy
sandboxPresetId?: string; // maps to a sandbox policy preset
budget?: AgentBudgetPolicy; // stricter workflow-agent budget
model?: string; // default model for this agent
description: string;
tools?: string[]; // tool restrictions (overrides role policy)
}export type StepType = 'agent' | 'loop' | 'gate' | 'parallel';
export interface WorkflowStep {
id: string;
name: string;
agent?: string; // agent ID (required for agent/loop steps)
type: StepType;
fresh_session?: boolean; // legacy: use session config instead
session?: StepSessionConfig;
input?: string; // template for agent prompt
output?: StepOutput;
acceptance_criteria?: string[];
on_fail?: FailurePolicy;
timeout?: number;
// Loop-specific config
loop?: LoopConfig;
// Gate-specific config
condition?: string; // expression evaluating to boolean
on_false?: EscalationPolicy;
// Parallel-specific config
parallel?: ParallelConfig;
}export interface StepOutput {
file: string; // filename in step-outputs/
schema?: string; // schema ID for validation
}export interface FailurePolicy {
retry?: number;
retry_delay_ms?: number; // delay between retries
retry_step?: string; // retry a different step ID
escalate_to?: 'human' | `agent:${string}` | 'skip';
escalate_message?: string;
on_exhausted?: EscalationPolicy;
}export interface EscalationPolicy {
escalate_to: 'human' | `agent:${string}` | 'skip';
escalate_message?: string;
}export interface LoopConfig {
over: string; // expression returning array
item_var?: string; // variable name for current item
index_var?: string; // variable name for loop index
completion: 'all_done' | 'any_done' | 'first_success';
fresh_session_per_iteration?: boolean;
verify_each?: boolean;
verify_step?: string; // step ID to run after each iteration
max_iterations?: number;
continue_on_error?: boolean; // if true, failed iterations don't fail the loop
}export interface GateStepConfig {
condition: string; // expression evaluating to boolean
on_false: EscalationPolicy;
}export interface ParallelConfig {
steps: ParallelSubStep[]; // sub-steps to execute in parallel
completion: 'all' | 'any' | number; // wait for all, any, or N sub-steps
fail_fast?: boolean; // if true, abort others when one fails
timeout?: number; // max time to wait (seconds)
}
export interface ParallelSubStep {
id: string;
agent: string;
input: string; // template for sub-step input
output?: StepOutput;
timeout?: number;
}export type WorkflowRunStatus = 'pending' | 'running' | 'blocked' | 'completed' | 'failed';
export type WorkflowAdmissionState = 'waiting' | 'dispatching' | 'active' | 'terminal';
export interface WorkflowRun {
id: string; // run_<timestamp>_<nanoid>
workflowId: string;
workflowVersion: number;
taskId?: string; // optional task association
admission?: WorkflowRootAdmissionBinding; // durable root reservation or queue identity
status: WorkflowRunStatus;
currentStep?: string; // current step ID
context: Record<string, unknown>; // shared context across steps
budget?: AgentBudgetState; // effective budget, usage, threshold events, traces
startedAt: string;
completedAt?: string;
lastCheckpoint?: string; // last state persistence timestamp
error?: string;
steps: StepRun[];
}export type StepRunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
export interface StepRun {
stepId: string;
status: StepRunStatus;
agent?: string;
sessionKey?: string; // OpenClaw session key
startedAt?: string;
completedAt?: string;
duration?: number; // seconds
retries: number;
output?: string; // path to output file
error?: string;
admission?: WorkflowStepAdmissionBinding; // latest executable attempt or queue decision
// Loop-specific state
loopState?: {
totalIterations: number;
currentIteration: number;
completedIterations: number;
failedIterations: number;
};
}When root or step capacity is temporarily unavailable, the corresponding
admission binding has state: "waiting" and a durable queueEntryId. The run
and step remain pending; provider execution is not marked active. A claimed
entry briefly uses dispatching while Veritas transfers durable ownership to
workflow recovery, then becomes active before provider execution.
export interface ToolPolicy {
role: string;
allowed: string[]; // tool names (use '*' for all)
denied: string[]; // tool names (takes precedence)
description: string;
}export interface StepSessionConfig {
mode: 'fresh' | 'reuse'; // fresh = new session, reuse = continue existing
context: 'minimal' | 'full' | 'custom'; // how much context to pass
cleanup: 'delete' | 'keep'; // delete session after step or keep
timeout: number; // session timeout in seconds
includeOutputsFrom?: string[]; // step names for 'custom' context
}export type WorkflowPermission = 'view' | 'create' | 'edit' | 'delete' | 'execute';
export interface WorkflowACL {
workflowId: string;
owner: string; // user ID or 'system'
editors: string[]; // users who can edit
viewers: string[]; // users who can view
executors: string[]; // users who can trigger runs
isPublic: boolean; // anyone can view/execute
}export interface WorkflowAuditEvent {
timestamp: string;
userId: string;
action: 'create' | 'edit' | 'delete' | 'run';
workflowId: string;
workflowVersion?: number;
changes?: Array<{ field: string; oldValue: unknown; newValue: unknown }>;
runId?: string;
}All errors follow this structure:
{
"error": {
"message": "Human-readable error message",
"code": "ERROR_CODE",
"details": {}
}
}| Code | Name | Description |
|---|---|---|
400 |
Bad Request | Validation error, missing required fields, invalid input |
401 |
Unauthorized | Missing or invalid authentication |
403 |
Forbidden | No permission to perform action |
404 |
Not Found | Resource not found |
409 |
Conflict | Resource already exists (e.g., workflow ID collision) |
500 |
Internal Server Error | Unexpected server error |
{
"error": {
"message": "Workflow must define at least one agent",
"code": "VALIDATION_ERROR",
"details": {
"field": "agents"
}
}
}{
"error": {
"message": "Workflow feature-dev not found",
"code": "NOT_FOUND",
"details": {
"workflowId": "feature-dev"
}
}
}{
"error": {
"message": "No edit permission for workflow feature-dev",
"code": "PERMISSION_DENIED",
"details": {
"workflowId": "feature-dev",
"requiredPermission": "edit",
"userId": "user-123"
}
}
}{
"error": {
"message": "Workflow hello-world already exists",
"code": "CONFLICT",
"details": {
"workflowId": "hello-world"
}
}
}End of API Reference