Skip to main content
REST surface for the four non-OpenClaw runtimes (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.
The :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):
For clawboo-native, omit provider to store under ANTHROPIC_API_KEY, or pass provider: "openai" / "openrouter" to store under that provider’s env var. An unrecognized provider falls back to the descriptor’s default envVar.

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).
This signs out the ChatGPT subscription that every tool on the machine shares, not only clawboo’s view of it.
  • 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 not clawboo-native).
  • Request body:
Provider → probe endpoint: anthropichttps://api.anthropic.com/v1/models; openaihttps://api.openai.com/v1/models; openrouterhttps://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:
The result returned on the 200/409/404/422 paths is the executor runner’s RunTaskResult. The handler maps the !ok reasons to status codes: not_found404, conflict409, 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:
Per-provider model defaults (leader = a capable model, specialist = a cheap one) come from MODEL_DEFAULTS in apps/web/server/lib/runtimes/native/nativeProviderDefaults.ts, which is the drift-free source: anthropicclaude-sonnet-5 / claude-haiku-4-5; openaigpt-5.4 / gpt-4o-mini; openrouteranthropic/claude-haiku-4.5 / openai/gpt-4o-mini; ollamallama3.2 / llama3.2; googlegemini-2.0-flash (both tiers); xaigrok-2-latest (both tiers); groqllama-3.3-70b-versatile / llama-3.1-8b-instant; mistralmistral-large-latest / mistral-small-latest; togethermeta-llama/Llama-3.3-70B-Instruct-Turbo (both tiers); cerebrasllama-3.3-70b (both tiers); moonshotmoonshot-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 of codex, 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

Last modified on August 21, 2026