claude-code, codex, hermes, clawboo-native): list their capabilities and connection state, install a runtime CLI, connect or disconnect a provider key, verify a key before use, and drive a board task on a chosen runtime. This group also covers the /api/onboarding/* first-run routes and the /api/providers* group that backs the Settings → Providers panel.
OpenClaw is the fifth runtime but it is NOT in this group; it is a connected substrate driven over the Gateway, not a CLI you install or a key you paste. These routes 404 the
openclaw id (it is not a member of NonOpenClawRuntimeId). See System API for OpenClaw lifecycle and Agents API for the agent registry.:id path segment is validated against the runtime set on every route except seed-native-team. An unknown id returns 404 { error: "unknown runtime '<id>'" }. All POST routes read a JSON body parsed by express.json({ limit: '2mb' }).
Routes
POST /api/runtimes/:id/install and POST /api/auth/cli-login/:tool are Server-Sent Events, not request/response. They are documented below with an event-stream catalog (event type → payload), not a JSON response body. For install, a built-in runtime (clawboo-native) short-circuits with a plain JSON 400 before the stream opens; for cli-login, an unknown :tool short-circuits with a plain JSON 404.GET /api/runtimes
Lists every runtime with its capabilities, live health, and install/auth status. The runtimes[] entries lead with the back-compat fields (id, participantKind, capabilities, health) followed by the install/auth status fields. The sibling available[] array advertises the full catalog so a UI can render “available to add” cards for runtimes the user has not connected.
- Path/query params: none.
- Request body: none.
Responses
200 OK: the runtime list plus the descriptor catalog:
installed is true for the built-in clawboo-native (it ships inside the server) and otherwise reflects whether healthBin resolves on PATH or a known user-install dir. hasCredential is presence-only; the secret value is never read into the response. hasVaultCredential is the same presence check but vault-only (via getRuntimeSecret): it ignores a key that resolves only from a bare process.env var, so it reflects a deliberate connect. The onboarding-landing decision (GatewayBootstrap.hasConnectedRuntime) reads hasVaultCredential, not hasCredential, so an env-var-only environment is not mistaken for a connected runtime. connectionState is derived as: not-installed if not installed; otherwise ready for authKind: 'none', ready/needs-login for oauth (Codex) depending on whether an existing codex login is detected, and ready/needs-auth for api-key depending on hasCredential. loggedIn is true only for an installed oauth runtime whose terminal login was detected, and it is what flips that runtime’s connectionState to ready. codexAuth is the Hermes-only ChatGPT-subscription signal (a usable openai-codex credential in ~/.hermes/auth.json, which has no env var and no vault slot); it folds into hasCredential. Both are false for every other runtime.
500 Internal Server Error: any failure constructing an adapter or probing health:
Example
POST /api/runtimes/:id/install
Installs the runtime’s CLI. npm runtimes (claude-code, codex) run npm install -g <pkg>; the pip runtime (hermes) prefers pipx install <pkg> and falls back to python -m pip install --user, retrying once with --break-system-packages on a PEP-668 externally-managed environment. The built-in clawboo-native returns a plain 400 before any stream opens.
- Path params:
id(runtime id; 404 on unknown). - Request body: none.
Responses
400 Bad Request: the runtime is built in (no stream opens):
200 OK + text/event-stream: for an installable runtime the handler sets Content-Type: text/event-stream, flushes headers, and streams SSE frames. Each frame is data: <json>\n\n. The active child process is killed if the client closes the connection.
Event catalog
The terminal
error code values:
On success the handler emits
complete with success: true; if healthBin still does not resolve, complete carries a warning advising a server restart.
Example frames:
Example
POST /api/runtimes/:id/connect
Stores a provider key in the encrypted vault and marks the runtime connected. Behavior branches on the descriptor’s authKind: an oauth runtime (Codex) cannot be connected with a pasted key and returns the terminal login command; an api-key runtime writes the key to its vault slot. The native runtime is multi-provider; the optional provider field routes the key to the correct env var. The key is never echoed in the response.
- Path params:
id(runtime id; 404 on unknown). - Request body:
Responses
200 OK: oauth runtime (Codex): a key-less branch that probes the existing terminal login and returns the current state plus the login command:
ready when clawboo detects and reuses an existing codex login; not-installed when the CLI is absent; needs-login otherwise.
200 OK: authKind: 'none', or no envVar, or a keyless native provider (provider: 'ollama'): nothing stored, state re-derived:
400 Bad Request: an api-key runtime with a missing or blank apiKey:
200 OK: key stored, state re-derived (typically ready):
Example
POST /api/runtimes/:id/disconnect
Clears the stored credential for the runtime. The binary stays installed and the runtime stays available; the card returns to needs-auth (the vault slot is empty). For a runtime with no envVar (Codex), this is a no-op on storage.
- Path params:
id(runtime id; 404 on unknown). - Request body: none.
Responses
200 OK: credential cleared, state re-derived:
Example
POST /api/runtimes/:id/logout
Signs an oauth runtime (Codex) out by spawning the CLI’s own logout; clawboo never deletes the vendor’s tokens itself. The spawn is best-effort and the answer comes from a fresh codex login status re-probe, not from the child’s exit code. An api-key runtime has no sign-out (use /disconnect).
- Path params:
id(runtime id; 404 on unknown). - Request body: none.
Responses
404 Not Found: the runtime’s authKind is not oauth:
400 Bad Request: the runtime’s CLI binary does not resolve:
200 OK: the re-probe result. ok is the inverse of the probed login state, so a sign-out that did not take reads ok: false:
Example
POST /api/runtimes/:id/healthcheck
Verifies a provider credential with a single authenticated GET to the provider’s models/health endpoint, before anything commits to it. The key is used for exactly one fetch; it is never persisted to the vault, never logged, and never echoed. A bad key or unreachable provider resolves to { ok: false, error } (the handler does not throw into the request).
Every clawboo surface that accepts or reuses a credential calls this first: the onboarding connect step, the Providers hub, the runtime connect card, and the Providers manager’s one-click Use.
This route is native-only. Any other
:id (still a valid runtime) returns 400. It probes a provider, not a runtime, so a surface holding a key for any other runtime verifies it by naming that key’s provider here.- Path params:
id(runtime id; 404 on unknown; 400 if notclawboo-native). - Request body:
anthropic → https://api.anthropic.com/v1/models; openai → https://api.openai.com/v1/models; openrouter → https://openrouter.ai/api/v1/models; the extra OpenAI-compatible providers (google, xai, groq, mistral, together, cerebras, moonshot) → that provider’s own <baseURL>/models; ollama → <OLLAMA_BASE_URL>/api/tags (keyless). The fetch is bounded by an 8-second timeout.
Responses
400 Bad Request: :id is not the native runtime:
400 Bad Request: unknown provider:
400 Bad Request: non-ollama provider with a blank apiKey and no key stored for it:
200 OK: provider reachable and the key authenticated:
200 OK: provider rejected the key or returned an error (note: still HTTP 200, the failure is in the body):
Example
POST /api/runtimes/:id/run
Drives a board task on the runtime end to end: claim → worktree → run → report-up, via the server-side executor runner. The runtime’s MCP client attaches to this server’s /api/mcp/* endpoints over a server-trusted loopback URL (never the client-supplied Host). Any connected provider key is injected from the vault into the spawned process env. If the client disconnects before the run finishes, the run (and its subprocess) is aborted and the task released.
- Path params:
id(runtime id; 404 on unknown). - Request body:
200/409/404/422 paths is the executor runner’s RunTaskResult. The handler maps the !ok reasons to status codes: not_found → 404, conflict → 409, and every other failure reason (too_deep, connected_substrate, budget_paused) → 422.
Responses
400 Bad Request: missing taskId:
400 Bad Request: assigneeAgentId is not a bare identifier (/^[A-Za-z0-9_-]+$/):
200 OK: the task ran (the success branch of RunTaskResult):
404 Not Found: the task does not exist (reason: 'not_found'):
409 Conflict: the task could not be atomically claimed (another worker won; reason: 'conflict'). Per the board’s atomic-claim contract, a 409 is data; do not retry it:
422 Unprocessable Entity: the run was refused for a board/runtime reason (reason is one of too_deep, connected_substrate, budget_paused):
too_deep = the delegation depth ceiling was hit; connected_substrate = the runtime is a connected substrate (it cannot be dispatched through this path); budget_paused = a budget kill-switch is paused.
500 Internal Server Error: an unexpected throw inside the run:
Example
GET /api/runtimes/openrouter/models
The live OpenRouter catalog behind the OpenRouter model pickers. It reads OpenRouter’s public list endpoint (no key), keeps text-capable models, sorts them by label, and caches the result in-process for 30 minutes behind an 8-second fetch timeout.
- Path/query params: none.
- Request body: none.
Responses
200 OK: the catalog. This route never errors: a failed, empty, or timed-out fetch falls back to the last good cache, and an empty array tells the client to fall back to its own hardcoded set:
Example
POST /api/onboarding/seed-native-team
Mints a default native team, a leader (capable model) and a specialist (cheap model), both with the Memory and Tools MCP, TeamChat, and read-only Tasks (board writes stay engine-owned), so a first-run user who just connected a provider key lands in a working team. Both agents are clawboo-native rows created through the native AgentSource (no Gateway, no provider SDK call). The team row is inserted first (the agents’ teamId FK requires it), the agents are created with that teamId, the leader is recorded, and the “Know Your Team” onboarding flags are pre-satisfied so the user lands straight in chat.
This route is not under
/api/runtimes and takes no :id segment. It is grouped here because it is the native runtime’s first-run seed step.- Path/query params: none.
- Request body:
MODEL_DEFAULTS in apps/web/server/lib/runtimes/native/nativeProviderDefaults.ts, which is the drift-free source: anthropic → claude-sonnet-5 / claude-haiku-4-5; openai → gpt-5.4 / gpt-4o-mini; openrouter → anthropic/claude-haiku-4.5 / openai/gpt-4o-mini; ollama → llama3.2 / llama3.2; google → gemini-2.0-flash (both tiers); xai → grok-2-latest (both tiers); groq → llama-3.3-70b-versatile / llama-3.1-8b-instant; mistral → mistral-large-latest / mistral-small-latest; together → meta-llama/Llama-3.3-70B-Instruct-Turbo (both tiers); cerebras → llama-3.3-70b (both tiers); moonshot → moonshot-v1-32k / moonshot-v1-8k.
Responses
400 Bad Request: provider is not one of the known native providers:
201 Created: the team and its two agents were created:
500 Internal Server Error: a failure creating the team or agents:
Example
Onboarding
The three remaining/api/onboarding/* routes. Like seed-native-team, they take no :id segment and are grouped here because they are the native runtime’s first-run surface.
POST /api/onboarding/native-leader-model
Records the provider + model the user picked when connecting a native key, so the lazily-created universal Boo Zero runs on it instead of the auto-resolved per-provider default. Body { provider, model }. The pick is also retro-applied to an existing native Boo Zero, best-effort: on a fresh install there is no Boo Zero yet, which is the normal case and not an error. Returns { ok: true }. 400 { error: "unknown provider '<provider>'" } when provider is not a known native provider, or { error: "model is required" } when model is missing or blank. 500 { error } if the setting cannot be written.
GET /api/onboarding/native-leader-model
The recorded pick, read by the Runtimes panel’s native provider manager so its “Default” tag and model dropdown reflect reality. Returns { provider, model }, or { provider: null, model: null } when nothing was ever recorded or the stored value does not parse. Never errors.
GET /api/onboarding/state
The aggregated first-run signals in one call, so a thin client (desktop, mobile, npm) can decide wizard-vs-dashboard without re-running the browser’s multi-call dance. Read-only; each field reuses the same detection the individual routes use:
hasConnectedRuntime is true when any runtime has a key in the vault (the hasVaultCredential signal GET /api/runtimes exposes), or when an existing codex login is detected, or when a Hermes ChatGPT-subscription login is present. Codex is the exception that needs the extra probes: it has no env var and no vault slot, so without them a subscription-only user would be re-trapped in the wizard on every reload.
500 { error } on an unexpected throw.
Providers
/api/providers* backs the Settings → Providers panel: the LLM provider keys that power clawboo-native (and any runtime routed through the same provider). A provider is not a runtime; anthropic is a provider, clawboo-native is a runtime that consumes one.
GET /api/providers
Returns { providers: ProviderStatus[] }: every known provider with its connection state. Never errors.
POST /api/providers/:id/connect
Body { apiKey }. Stores the key in the encrypted vault. Returns { ok: true, providers } (the refreshed list). 400 { error: "unknown provider '<id>'" } for an unknown id, or { error: 'apiKey is required' } when the key is missing or blank.
POST /api/providers/:id/disconnect
Clears the stored key. Returns { ok: true, providers }. 400 for an unknown id.
GET /api/providers/:id/models
The provider’s live model list, enumerated with the stored key. Returns { models: [] } for a keyless or non-enumerating provider rather than an error, so the client can fall back to its static catalog.
POST /api/providers/:id/models
Body { apiKey }. The live model list using a pasted, unsaved key: this is what the onboarding step calls before any key is stored. The key is used for exactly one fetch and is never logged, persisted, or echoed back. Returns { models: [] } when the provider does not enumerate or the key is blank.
Only providers that support live enumeration return a non-empty
models array; the rest return { models: [] } by design. See Connecting runtimes for the vault and the connection-state machine.POST /api/auth/cli-login/:tool
The UI-driven “Sign in with ChatGPT”. Spawns the official CLI’s own login command locally and relays its user-facing output (device code, auth URL) over SSE. The OAuth exchange stays inside the vendor CLI, the human authorizes in a browser, and clawboo never touches the tokens. Only one login child runs per tool: a new request kills the previous tree first (Retry semantics), and the run is capped at 16 minutes because the CLIs’ own device windows are 15.
- Path params:
tool, one ofcodex,hermes,openclaw. - Request body: none.
Responses
404 Not Found: unknown :tool. This is plain JSON sent before the stream opens:
200 OK + text/event-stream: otherwise the handler sets Content-Type: text/event-stream, flushes headers, and streams data: <json>\n\n frames. A plan that cannot be built (CLI not installed, or the OpenClaw flow on Windows) is reported as an error frame inside the stream, not as an HTTP error, so the UI can degrade to its copy-the-command fallback.
Event catalog
The
error code values:
Completion is driven by re-probing the real auth store (polled every 3 seconds), never by the exit code alone: a browser flow can leave the CLI holding an unanswered prompt long after the credential has landed on disk. The moment the credential appears, the stream emits
complete with success: true, loggedIn: true and the child tree is reaped. If the child exits without a usable credential, complete carries success: false, loggedIn: false and a message naming the terminal command to run by hand. Cancelling in the UI aborts the fetch, which closes the connection and kills the whole process tree.
Example
Error envelope
Every error response on these routes is the standard envelope{ error: string }, except the healthcheck route, which uses { ok: false, error: string } (its success shape is { ok: true }), and the run route’s !ok branches, which return { ok: false, reason: <string> }.
See also
- Runtimes overview + capability matrix
- Connecting runtimes (install/connect/disconnect, the encrypted vault)
- The board,
taskId, atomic claim, the 409-no-retry contract - Board API, create the task you pass to
:id/run - Tools & MCP API, the
/api/mcp/*endpoints a run attaches to - REST API overview