Appearance
Z.E.N. exposes a REST API via a Hono server with OpenAPI spec generation. All endpoints are prefixed with /api/.
Base URL
By default, the API server runs at:
http://localhost:3090/api/Override the port with the PORT environment variable or let Z.E.N. auto-allocate when running inside a worktree (range 3190-4089).
OpenAPI Specification
A machine-readable OpenAPI 3.0 spec is available at:
GET /api/openapi.jsonYou can feed this into tools like Swagger UI or use it to generate typed API clients.
Authentication
None. Z.E.N. is a single-developer tool; there is no authentication on the API by default. If you expose Z.E.N. on a network, use a reverse proxy or firewall to restrict access.
Health
| Method | Path | Description |
|---|---|---|
| GET | /health | Basic health check |
| GET | /api/health | API-level health check |
bash
curl http://localhost:3090/health
# {"status":"ok"}
curl http://localhost:3090/api/health
# {"status":"ok","adapter":"...","concurrency":{...},"runningWorkflows":0}Conversations
| Method | Path | Description |
|---|---|---|
| GET | /api/conversations | List conversations |
| GET | /api/conversations/{id} | Get a single conversation |
| POST | /api/conversations | Create a new conversation |
| PATCH | /api/conversations/{id} | Update a conversation (rename) |
| DELETE | /api/conversations/{id} | Soft-delete a conversation |
| GET | /api/conversations/{id}/messages | List messages in a conversation |
| POST | /api/conversations/{id}/message | Send a message to a conversation |
List Conversations
bash
curl http://localhost:3090/api/conversationsQuery parameters:
workspace_id(optional, legacy field name); Filter by workspaceinclude_deleted(optional); Include soft-deleted conversations
Create a Conversation
bash
curl -X POST http://localhost:3090/api/conversations \
-H "Content-Type: application/json" \
-d '{}'Optionally specify a workspace:
bash
curl -X POST http://localhost:3090/api/conversations \
-H "Content-Type: application/json" \
-d '{"workspace_id": "your-workspace-id"}'Returns the created conversation with its platform_conversation_id.
Send a Message
bash
curl -X POST http://localhost:3090/api/conversations/{id}/message \
-H "Content-Type: application/json" \
-d '{"message": "What does this workspace do?"}'The message is dispatched to the orchestrator asynchronously. The response confirms dispatch; actual AI responses arrive via SSE streaming or can be polled via the messages endpoint.
Get Messages
bash
curl http://localhost:3090/api/conversations/{id}/messagesQuery parameters:
limit(optional); Number of messages to returnbefore(optional); Cursor for pagination
Update a Conversation
bash
curl -X PATCH http://localhost:3090/api/conversations/{id} \
-H "Content-Type: application/json" \
-d '{"title": "My feature discussion"}'Delete a Conversation
bash
curl -X DELETE http://localhost:3090/api/conversations/{id}Performs a soft delete; the conversation is hidden but not destroyed.
Workspaces
| Method | Path | Description |
|---|---|---|
| GET | /api/workspaces | List registered workspaces |
| GET | /api/workspaces/{id} | Get a single workspace |
| POST | /api/workspaces | Register a workspace (clone or local path) |
| PATCH | /api/workspaces/{id} | Update env-key consent (allowEnvKeys) |
| DELETE | /api/workspaces/{id} | Delete a workspace and clean up resources |
| GET | /api/workspaces/{id}/environments | List isolation environments for a workspace |
The API path and workspace_id field name are retained for compatibility while the runtime term moves to workspace.
List Workspaces
bash
curl http://localhost:3090/api/workspacesRegister a Workspace
Clone from a URL:
bash
curl -X POST http://localhost:3090/api/workspaces \
-H "Content-Type: application/json" \
-d '{"url": "https://github.com/user/repo"}'Register a local path:
bash
curl -X POST http://localhost:3090/api/workspaces \
-H "Content-Type: application/json" \
-d '{"path": "/home/user/projects/my-repo"}'Update Env-Key Consent
Flip the env-leak-gate consent bit (allow_env_keys) on an existing workspace. Audit-logged on every grant and revoke as env_leak_consent_granted / env_leak_consent_revoked (warn-level) including workspaceId, path, scanned files, matched keys, scanStatus, and actor.
bash
curl -X PATCH http://localhost:3090/api/workspaces/{id} \
-H "Content-Type: application/json" \
-d '{"allowEnvKeys": true}'Delete a Workspace
bash
curl -X DELETE http://localhost:3090/api/workspaces/{id}Removes the workspace registration and cleans up associated worktrees and isolation environments.
List Environments
bash
curl http://localhost:3090/api/workspaces/{id}/environmentsReturns the isolation environments (worktrees) associated with a workspace.
Workflows
Definitions
| Method | Path | Description |
|---|---|---|
| GET | /api/workflows | List available workflows |
| GET | /api/workflows/{name} | Get a single workflow definition |
| POST | /api/workflows/validate | Validate a workflow definition (in-memory, no save) |
| PUT | /api/workflows/{name} | Save (create or update) a workflow |
| DELETE | /api/workflows/{name} | Delete a user-defined workflow |
List Workflows
bash
curl http://localhost:3090/api/workflowsQuery parameters:
cwd(optional); Working directory to discover project-specific workflows
Returns { workflows: [...], errors?: [...] }. The errors array contains any YAML parsing failures encountered during discovery.
Get a Workflow
bash
curl http://localhost:3090/api/workflows/morning-briefQuery parameters:
cwd(optional); Working directory for project-specific lookup
Returns { workflow, filename, source: "project" | "bundled" }.
Validate a Workflow
bash
curl -X POST http://localhost:3090/api/workflows/validate \
-H "Content-Type: application/json" \
-d '{"definition": {"name": "my-wf", "description": "Test", "nodes": [{"id": "a", "prompt": "hello"}]}}'Returns { valid: true } or { valid: false, errors: ["..."] }. Does not save anything.
Save a Workflow
bash
curl -X PUT http://localhost:3090/api/workflows/my-workflow \
-H "Content-Type: application/json" \
-d '{"definition": {"name": "my-workflow", "description": "My custom workflow", "nodes": [{"id": "plan", "prompt": "Plan the feature"}]}}'Query parameters:
cwd(optional); Target directory (must have.zen/workflows/)
Validates the definition before saving. Returns the saved workflow.
Delete a Workflow
bash
curl -X DELETE http://localhost:3090/api/workflows/my-workflowOnly user-defined workflows can be deleted. Bundled defaults cannot be removed.
Runs
| Method | Path | Description |
|---|---|---|
| POST | /api/workflows/{name}/run | Run a workflow |
| GET | /api/workflows/runs | List workflow runs |
| GET | /api/workflows/runs/{runId} | Get run details with events |
| GET | /api/workflows/runs/by-worker/{platformId} | Look up a run by worker conversation ID |
| POST | /api/workflows/runs/{runId}/cancel | Cancel a running workflow |
| POST | /api/workflows/runs/{runId}/resume | Resume a failed workflow |
| POST | /api/workflows/runs/{runId}/abandon | Abandon a non-terminal run |
| POST | /api/workflows/runs/{runId}/approve | Approve a paused workflow |
| POST | /api/workflows/runs/{runId}/reject | Reject a paused workflow |
| DELETE | /api/workflows/runs/{runId} | Delete a terminal run and its events |
Run a Workflow
bash
curl -X POST http://localhost:3090/api/workflows/morning-brief/run \
-H "Content-Type: application/json" \
-d '{"message": "Explain the auth module", "conversationId": "conv-123"}'Resume a Failed Run
bash
curl -X POST http://localhost:3090/api/workflows/runs/{runId}/resumeMarks the run for auto-resume. The next invocation re-runs the workflow, skipping already-completed nodes.
Approve / Reject a Paused Run
bash
# Approve (optionally with a comment)
curl -X POST http://localhost:3090/api/workflows/runs/{runId}/approve \
-H "Content-Type: application/json" \
-d '{"comment": "Looks good, proceed"}'
# Reject (optionally with a reason)
curl -X POST http://localhost:3090/api/workflows/runs/{runId}/reject \
-H "Content-Type: application/json" \
-d '{"reason": "Please add error handling first"}'Commands
| Method | Path | Description |
|---|---|---|
| GET | /api/commands | List available command names |
bash
curl http://localhost:3090/api/commandsQuery parameters:
cwd(optional); Working directory for project-specific commands
Returns { commands: [{ name, source: "bundled" | "project" }] }.
Dashboard
| Method | Path | Description |
|---|---|---|
| GET | /api/dashboard/runs | List enriched workflow runs for the dashboard |
Query parameters include status filters, date ranges, and pagination. Used by the Command Center UI.
Configuration
| Method | Path | Description |
|---|---|---|
| GET | /api/config | Get read-only configuration (safe subset) |
| PATCH | /api/config/assistants | Update assistant configuration |
bash
# Read current config
curl http://localhost:3090/api/config
# Update assistant defaults
curl -X PATCH http://localhost:3090/api/config/assistants \
-H "Content-Type: application/json" \
-d '{"claude": {"model": "opus"}}'System
| Method | Path | Description |
|---|---|---|
| GET | /api/update-check | Check for available updates (binary builds only) |
Returns { updateAvailable, currentVersion, latestVersion, releaseUrl }. For non-binary (source) builds, always returns updateAvailable: false without making external requests.
Benchmarks
| Method | Path | Description |
|---|---|---|
| GET | /api/runs/benchmark | Per-node stats for a workflow over recent runs |
| GET | /api/runs/benchmark/compare | Side-by-side delta between two runs |
| GET | /api/runs/benchmark/workflows | List workflow names that have run data |
Get Benchmark Stats
bash
curl "http://localhost:3090/api/runs/benchmark?workflow=morning-brief&since=7d&limit=50"Query parameters:
workflow(required) — workflow name to aggregatesince(optional) — relative (24h,7d,2w) or ISO date; omit for all historylimit(optional) — max recent runs to include (default: 20, max: 200)
Returns:
json
{
"summary": { "p50Ms": 12400, "p95Ms": 31200, "p99Ms": 58700, "runs": 38, "usd": 0.2141 },
"nodes": [{ "stepName": "plan", "p50Ms": 4100, "p95Ms": 9800, "totalRuns": 38, "runsWithCost": 38, "totalCostUsd": 0.0912 }]
}Compare Two Runs
bash
curl "http://localhost:3090/api/runs/benchmark/compare?runA=<uuid>&runB=<uuid>"Query parameters:
runA(required) — first run IDrunB(required) — second run ID
Returns { nodes: [{ stepName, runAMs, runBMs, deltaMs, runACostUsd, runBCostUsd }] }. deltaMs is runBMs - runAMs (positive = run B was slower).
List Benchmark Workflows
bash
curl "http://localhost:3090/api/runs/benchmark/workflows"Returns { workflows: string[] } — all workflow names that have at least one run record.
SSE Streaming
| Path | Description |
|---|---|
/api/stream/{conversationId} | Real-time events for a conversation |
/api/stream/__dashboard__ | Multiplexed workflow events across all conversations |
These are Server-Sent Events (SSE) endpoints; connect with EventSource in a browser or any SSE client.
bash
# Listen to a conversation stream
curl -N http://localhost:3090/api/stream/your-conversation-idEvents are JSON-encoded with a type field. See the Web UI documentation for the full list of event types.
Common Patterns
Create a Conversation and Send a Message
bash
# 1. Create a conversation
CONV_ID=$(curl -s -X POST http://localhost:3090/api/conversations \
-H "Content-Type: application/json" \
-d '{}' | jq -r '.platform_conversation_id')
# 2. Send a message
curl -X POST http://localhost:3090/api/conversations/$CONV_ID/message \
-H "Content-Type: application/json" \
-d '{"message": "/status"}'
# 3. Poll for messages
curl http://localhost:3090/api/conversations/$CONV_ID/messagesRun a Workflow via the API
bash
# 1. Create a conversation scoped to a workspace
CONV_ID=$(curl -s -X POST http://localhost:3090/api/conversations \
-H "Content-Type: application/json" \
-d '{"workspace_id": "your-workspace-id"}' | jq -r '.platform_conversation_id')
# 2. Start the workflow
curl -X POST http://localhost:3090/api/workflows/morning-brief/run \
-H "Content-Type: application/json" \
-d "{\"message\": \"How does auth work?\", \"conversationId\": \"$CONV_ID\"}"
# 3. Monitor via SSE
curl -N http://localhost:3090/api/stream/$CONV_ID