Skip to main content
The Clawboo dashboard server (apps/web/server/index.ts) is an Express app wrapped in a raw http.Server so it can also handle the WebSocket upgrade for the Gateway proxy. Every JSON route lives under /api/, is registered in one router (apps/web/server/api/index.ts), and returns the standard { error: string } envelope on failure. This page covers the cross-cutting facts: base URL, the proxy, auth, body limits, the error shape, and the streaming endpoints, then links to the per-resource reference pages.
This documents the v0.2.0 working tree (commit 03b206a). The current npm latest is clawboo@0.1.9, so npx clawboo installs 0.1.9 until the v0.2.0 tag is published. Differences are noted in Known Issues.

Base URL

The server picks its port at boot, so there is no single hardcoded URL. The default is 18790; if it is taken the server scans upward through 18809 (20 consecutive ports) and binds the first free one.
http://localhost:18790
  • CLAWBOO_API_PORT=N pins the port exactly (no fallback; the server throws if N is taken).
  • CLAWBOO_API_PORT_START=M changes where the auto-scan begins (default 18790).
  • In a non---dev (production / CLI) boot with neither Clawboo override set, the legacy PORT env var is honored (Heroku/Render/Cloud Run compatibility).
After a successful bind the chosen port is written to <clawboo-home>/api-port.txt so the CLI, the Vite dev proxy, and e2e helpers can discover it without scanning. All curl examples in the per-resource pages assume the default 18790.

Host binding

The server binds loopback (127.0.0.1) by default; a fresh install is never reachable by other hosts. Set HOST or HOSTNAME to widen the bind (e.g. a headless/remote box). Binding a non-loopback interface without an access token logs a loud security warning at boot; pair a wide bind with STUDIO_ACCESS_TOKEN.

How the SPA reaches /api

The SPA and the API are served from the same origin in production: the Express app serves the Vite build (dist/ui/) as static files with a GET catch-all that returns index.html for client-side routes, and the same app mounts the API router. So a browser at http://localhost:18790 hits /api/... on that same origin with no CORS. In dev (pnpm dev), Vite serves the SPA on :5173 and proxies /api to the dynamic API port; CORS (origin: true, credentials: true) is enabled only in dev mode.

Request body limit

All routes share one JSON body parser, applied before the router:
app.use(express.json({ limit: '2mb' }))
A body larger than 2 MB is rejected by the parser. Routes that take no body (most GET/DELETE routes) ignore it.

The access gate

Authentication is opt-in via the STUDIO_ACCESS_TOKEN env var. When it is unset or blank, the gate is disabled and every route is open; the secure-by-default posture relies on the loopback bind, not on a token. When the token is set, the gate (packages/gateway-proxy/src/access-gate.ts) is enforced as middleware before the router:
StepBehavior
First visitOpen http://localhost:18790/?access_token=<token> once. The gate validates the token (constant-time, SHA-256-hashed compare), sets an HttpOnly cookie clawboo_access, and 302-redirects to strip the token from the URL.
Bad token in ?access_token=401 { "error": "Invalid Clawboo access token." }
/api/* without a valid cookie401 { "error": "Clawboo access token required. Open /?access_token=<token> once to set a cookie." }
Loopback /api/mcp/*Exempt: an MCP-transport request from a loopback peer (127.0.0.1 / ::1) passes without a cookie, so a server-spawned runtime can reach its own MCP control plane (its env is scrubbed of the token by design). A non-loopback /api/mcp/* request still requires the cookie.
WS upgradeAllowed only when the cookie is valid (always allowed when the gate is disabled).
The cookie is marked Secure only when the request arrives over TLS (X-Forwarded-Proto: https). On a plain-http loopback origin the cookie is set without Secure so the gate still works.
The /api/ prefix check is case-folded (pathname.toLowerCase()), so /API/settings, /Api/..., etc. are gated identically; there is no case-sensitivity bypass (the server also sets Express case sensitive routing: true). The configured token is validated against a safe charset (^[A-Za-z0-9._~-]+$) when the gate is built; a token containing a disallowed character disables the gate with a loud warning (fail-loud, never a silent lockout).
The query param (access_token) and cookie name (clawboo_access) are the defaults; both are configurable in createAccessGate(...) but the server passes neither, so the defaults apply.

The WebSocket upgrade: /api/gateway/ws

The one non-HTTP endpoint. The raw http.Server routes the upgrade event: a request whose pathname is exactly /api/gateway/ws is handed to the Gateway proxy; every other upgrade has its socket destroyed (Vite HMR runs on its own port). The proxy opens an upstream WebSocket to the OpenClaw Gateway, injects the server-side auth token + device signature into the connect frame, and forwards frames bidirectionally; the browser never sees the upstream token. This is a protocol stream, not a request/response endpoint. The frame shapes and lifecycle are documented in the Gateway & events concept page, not here.
The access gate’s allowUpgrade(req) check runs before the proxy forwards the upgrade. If STUDIO_ACCESS_TOKEN is set, a browser must already hold the clawboo_access cookie or the upgrade is refused (socket destroyed).

The error envelope

Every JSON route returns the same envelope on failure:
{ error: string }
The HTTP status carries the category; the error string is the human-readable message. Common statuses across the surface:
StatusMeaning
400Malformed/invalid body or path param
401Access gate rejected the request (token set, cookie missing/invalid)
404Unknown resource, or a flag/segment that does not resolve (e.g. an unknown runtime id)
409Atomic-claim conflict on the board (a 409 is data; do not retry it)
422The request was well-formed but refused for a domain reason (e.g. budget paused, delegation too deep)
500An unexpected throw inside the handler
A handful of routes deviate from the bare { error } shape, and those deviations are documented on the resource page:
  • The native /api/runtimes/:id/healthcheck route returns { ok: false, error } (success is { ok: true }).
  • /api/runtimes/:id/run returns { ok: false, reason } on its non-200 board/runtime refusals.
  • SSE routes emit error-typed event frames inside the stream rather than an HTTP error body (see below).

Streaming endpoints (SSE)

Three routes are Server-Sent Events, not request/response. They set Content-Type: text/event-stream, flush headers, and emit data: <json>\n\n frames. They are documented with an event-stream catalog (event type → payload) on their resource pages, not with a response body:
RouteResource page
POST /api/system/install-openclaw, POST /api/system/gateway (start/restart)System API
POST /api/runtimes/:id/installRuntimes API
GET /api/obs/streamObservability API

Route index

124 routes across 13 resource groups: 54 GET · 47 POST · 11 DELETE · 7 PATCH · 5 PUT. Each group has a dedicated reference page.
Resource pageRoutesWhat it covers
Settings & health4/api/settings, /api/health, /api/health/recheck
Agents10/api/agents*: the registry of record, files, sessions, sync, cleanup-ghosts
Teams12/api/teams*, /api/team-rules/:teamId, /api/team-chat*
Board16/api/board*: tasks, claim, comments, executions, deps, worktree workspace
Runtimes7/api/runtimes*, /api/onboarding/seed-native-team
Memory4/api/memory*: search, save, browse, provider
Tools & MCP17/api/tools*, /api/mcp/* (the four MCP transports + attach config)
Governance7/api/governance/*, /api/approvals
Capabilities2/api/capabilities, /api/capabilities/:action
Observability8/api/obs/* (incl. SSE), /api/eval/smoke
Schedules5/api/schedules*: the unified Routines + Gateway-cron surface
System8/api/system/*: status, install, configure, gateway, models, device pairing
Misc24cost-records, chat-history, graph-layout, personality, skills, exec-settings, fleet, boo-zero
The route counts above sum to 124 and partition the full surface; every registered route belongs to exactly one resource page. The “Misc” page is the catch-all for the smaller UI-backing resources that do not warrant their own page.

See also