REST API Overview
Applies to: ChengOS v0.1.0+ | Last verified: 2026-08-12 | Source:
crates/cheng-api/src/rest/routes.rs,crates/cheng-api/src/rest/dto/common.rs,crates/cheng-api/src/middleware/workspace_context.rs
Everything in ChengOS talks to one backend service, cheng-api. The editor, the chat
gateway, and the cheng CLI are all ordinary clients of the API described here — there is
no private channel they use that you cannot.
Base URL and conventions
Versioned endpoints live under /api/v1. Against a default installation that is
http://127.0.0.1:19225/api/v1.
A few routes sit deliberately outside the version prefix, because they are infrastructure rather than product API:
| Path | Purpose |
|---|---|
GET /health | Liveness check. Use this for load balancers and chengos.sh status. |
GET /ready | Readiness check, including datastore connectivity. |
GET /metrics | Prometheus metrics. Unauthenticated, per standard Prometheus practice. |
/ws/executions | WebSocket stream of execution events. |
/mcp | App-level MCP server, mounted only when MCP_ENABLED=true. |
Request and response bodies are JSON with camelCase field names, even though the
backend is Rust. A login response is {"token": …, "refreshToken": …, "expiresIn": 3600}.
Authenticating
Most endpoints require a JWT. Get one from POST /api/v1/auth/login:
curl -X POST http://127.0.0.1:19225/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email": "[email protected]", "password": "…"}'
{
"token": "eyJhbGciOi…",
"refreshToken": "eyJhbGciOi…",
"expiresIn": 3600,
"user": { "id": "…", "email": "…", "username": "…" }
}
Send the token on every subsequent request:
Authorization: Bearer <token>
Access tokens expire after one hour. Exchange the refresh token at
POST /api/v1/auth/refresh rather than logging in again. POST /api/v1/auth/logout
returns 204 and is client-side only — it tells you to discard the token; it does not
revoke it server-side.
The rest of the auth surface:
| Endpoint | Purpose |
|---|---|
GET /auth/bootstrap-status | Whether the installation has any users yet (needsSetup). Drives first-run setup. |
POST /auth/register | Create an account, subject to the deployment's registration policy. |
GET /auth/validate | Check a token without doing anything else. |
GET /auth/me | The current user. |
POST /auth/password/forgot · /reset · /change | Password lifecycle. Reset mail needs EMAIL_ENABLED. |
Three other credential types
JWTs are not the only way in, and mixing them up is a common source of 401s:
- Channel keys (
chk_prefix) authenticate external channel adapters — WhatsApp, Telegram, Slack, and so on — against/api/v1/channel/*only. They are stored as credentials and never carry a user session. - WebSocket tickets (
chtk_prefix) are minted through the authenticated channel REST surface and consumed once on WebSocket upgrade. They exist so channel keys never appear in a URL. - MCP tokens authenticate against the MCP server, either database-backed (preferred) or
a static
MCP_SERVER_TOKEN.
Selecting a workspace
Almost all resources belong to a workspace. Rather than repeating the workspace in every path, authenticated requests carry it in a header:
X-Workspace-Id: <workspace-uuid>
This is authorized, not merely read: the middleware runs after authentication and rejects
the request if the caller has no access to the workspace named in the header. Passing a
workspace id you cannot see is a 403, not a silent fallback to a default.
The main endpoint groups
| Prefix | What it covers |
|---|---|
/workflows | Create, read, update, delete, duplicate, export; favorites; template flags; per-workflow LLM node configuration. GET /workflows/templates is public and unauthenticated. |
/executions | Start runs and inspect them: POST /executions to run, then /:id, /:id/logs, /:id/trace-snapshot, /active, /history. Control with /:id/cancel, /:id/pause, /:id/resume, /:id/approve. |
/nodes | The node catalog and their JSON Schemas. Reads are public; writes need auth. |
/workspaces | Workspaces, plus nested /members, /settings, /knowledge-bases, /channels, /channel-keys. |
/credentials | The encrypted credential vault. |
/documents · /tables | Document and spreadsheet workspaces. |
/skills · /skill-registry | Installed skills, and the bridge to the public registry. |
/schedules | Timed and triggered runs. Mounted only when a scheduler is configured. |
/mcp/servers | MCP server registry. Mounted only when MCP state is available. |
/llm · /tools · /settings · /analytics · /i18n | Model providers, tool metadata, instance settings, usage analytics, and node UI translation bundles. |
/channel | Channel adapter ingress. Authenticated by chk_ keys, not JWT. |
Two groups are conditionally mounted — /schedules and /mcp/servers. If they return
404 on an otherwise healthy system, the feature is not configured rather than broken.
Pagination
List endpoints take offset and limit as query parameters. limit defaults to 20 and
is clamped to a maximum of 100 — asking for more is not an error, you simply get 100.
A limit of 0 is treated as the default rather than an empty page. Where supported,
sortBy and order (asc / desc) control ordering.
Responses wrap the page:
{
"total": 137,
"items": [ … ],
"hasMore": true,
"offset": 0,
"limit": 20
}
Use hasMore rather than comparing offset + limit against total; it already accounts
for the actual number of items returned.
Errors
Errors share one envelope:
{
"code": "VALIDATION_ERROR",
"message": "Human-readable description",
"details": { },
"timestamp": "2026-08-12T10:30:00Z",
"requestId": "…"
}
Match on code, not on message — messages are meant for humans and may be reworded.
requestId is what to quote when correlating a client failure against server logs.
One code deserves special mention: 403 DEMO_MODE_RESTRICTED means the installation is
running with CHENG_DEMO_MODE=true, which denies writes and executions by an allowlist. It
indicates configuration, not a permission problem with your account.
Real-time execution events
Workflow runs stream over WebSocket at /ws/executions, also reachable as
/api/v1/ws/executions. Authenticate with a JWT, passed either as an Authorization
header or as a ?token= query parameter; channel clients connect with ?ticket=chtk_…
instead. The stream carries node-level progress, incremental output from streaming nodes,
and terminal state, which is what makes the editor's live execution view work.
Next steps
- Environment variable reference — the settings that decide which of these endpoints are mounted.
- Build your first workflow — the same operations through the editor.