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:

PathPurpose
GET /healthLiveness check. Use this for load balancers and chengos.sh status.
GET /readyReadiness check, including datastore connectivity.
GET /metricsPrometheus metrics. Unauthenticated, per standard Prometheus practice.
/ws/executionsWebSocket stream of execution events.
/mcpApp-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:

EndpointPurpose
GET /auth/bootstrap-statusWhether the installation has any users yet (needsSetup). Drives first-run setup.
POST /auth/registerCreate an account, subject to the deployment's registration policy.
GET /auth/validateCheck a token without doing anything else.
GET /auth/meThe current user.
POST /auth/password/forgot · /reset · /changePassword 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:

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

PrefixWhat it covers
/workflowsCreate, read, update, delete, duplicate, export; favorites; template flags; per-workflow LLM node configuration. GET /workflows/templates is public and unauthenticated.
/executionsStart 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.
/nodesThe node catalog and their JSON Schemas. Reads are public; writes need auth.
/workspacesWorkspaces, plus nested /members, /settings, /knowledge-bases, /channels, /channel-keys.
/credentialsThe encrypted credential vault.
/documents · /tablesDocument and spreadsheet workspaces.
/skills · /skill-registryInstalled skills, and the bridge to the public registry.
/schedulesTimed and triggered runs. Mounted only when a scheduler is configured.
/mcp/serversMCP server registry. Mounted only when MCP state is available.
/llm · /tools · /settings · /analytics · /i18nModel providers, tool metadata, instance settings, usage analytics, and node UI translation bundles.
/channelChannel 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