Run OpenClaw's model calls through Router One
OpenClaw is an open-source, self-hosted AI assistant: its Gateway daemon runs on your own machine and answers you in the chat apps you already use, such as Discord, iMessage, Slack, Teams, Telegram and WhatsApp, or in its browser Control UI. It reaches models through providers, and any OpenAI-compatible endpoint can be registered as a custom provider. Registered that way, Router One puts every model turn OpenClaw makes, whether you typed the message, a tool result came back or a scheduled heartbeat fired, on one key you can cap with maxSpend, with tokens, cost and status recorded per request in Dashboard → Logs. Routing is exact-model with same-model failover: the model you name serves the request, a failing route (429, 5xx or timeout) is retried on another route for the same model, and routes with a sustained upstream-error rate are moved to the back of the order until they recover. Below: the onboarding command and its now-mandatory --accept-risk flag, what onboarding writes, which protocol reaches which endpoint, and where unattended spend comes from. Checked against OpenClaw 2026.9.6 on 2026-09-27.
Install OpenClaw on a supported Node.js and create a capped key
OpenClaw 2026.9.6, the version behind the npm latest tag since 2026-09-23, requires Node.js 24.16+ or 26.1+ with Node 26 recommended, and it reports any other runtime, Node 22 and Node 25 included, as unsupported. The official installer provisions a supported Node for you: curl -fsSL https://openclaw.ai/install.sh | bash on macOS, Linux and WSL2, or iwr -useb https://openclaw.ai/install.ps1 | iex in Windows PowerShell. On a fresh install it then opens the interactive onboarding wizard, which offers the same custom-provider choice as the command below. If you manage Node yourself, install the package with npm; OpenClaw's README asks for --allow-scripts=openclaw on npm 12 or npm 11.16+ and no flag on npm 11.15 or earlier. The extended-stable channel (2026.7.35) accepts Node 22.22.3+, 24.15+ or 25.9+ and needs the same onboarding flags. Then create a Router One key only for this machine and set maxSpend on it. OpenClaw acts without being asked, and a key that reaches its cap gets HTTP 402 on the next request instead of spending more, while your other keys keep working. The console also sends an in-app notification when the key reaches 80% of its maxSpend and when it hits the cap.
# macOS / Linux / WSL2: the official installer provisions Node, then opens the onboarding wizard curl -fsSL https://openclaw.ai/install.sh | bash # Or, on Node 24.16+ / 26.1+ with npm 12 or npm 11.16+ (drop the flag on npm 11.15 or earlier): npm install -g openclaw@latest --allow-scripts=openclaw openclaw --version
Configure OpenClaw to use the Router One base URL
Export the key as CUSTOM_API_KEY, then run the command below. --non-interactive requires --accept-risk: without it onboarding exits with Non-interactive setup requires explicit risk acknowledgement before it writes anything, because the flag acknowledges OpenClaw's security notice that an agent with full system access is risky. --auth-choice custom-api-key selects a custom provider and reads the key from CUSTOM_API_KEY when --custom-api-key is omitted. --custom-base-url is the OpenAI-compatible base URL with exactly one /v1. --custom-model-id takes the exact catalog ID; OpenClaw splits model references at the first slash, so the vendor-prefixed openai/gpt-5.6-sol becomes router-one/openai/gpt-5.6-sol and still resolves. --custom-provider-id fixes the provider name to router-one; without it OpenClaw derives a name from the host, such as custom-api-router-one. --custom-compatibility openai selects OpenAI Chat Completions, so every turn is a POST to /v1/chat/completions, the endpoint that serves every chat model in the catalog. --install-daemon installs the Gateway as a background service (launchd, systemd, or a Scheduled Task on Windows). Before saving, onboarding checks the model with one small request, the message Hi with a 16-token output limit. It reaches Router One, so its entry in Dashboard → Logs confirms the key, URL and model ID. In PowerShell, set $env:CUSTOM_API_KEY and end each continued line with a backtick instead of a backslash:
export CUSTOM_API_KEY=sk-your-router-one-key openclaw onboard --non-interactive --accept-risk \ --auth-choice custom-api-key \ --custom-base-url https://api.router.one/v1 \ --custom-model-id openai/gpt-5.6-sol \ --custom-provider-id router-one \ --custom-compatibility openai \ --install-daemon
What onboarding writes to ~/.openclaw/openclaw.json
Onboarding stores the provider under models.providers.router-one and makes its model the default, leaving models.mode at merge so OpenClaw's built-in providers stay available. Since OpenClaw 2026.9.4 a custom provider must list its models explicitly, and running onboarding again with the same provider ID and base URL but another --custom-model-id appends that model and makes it the primary one. Three of the values it writes are placeholders to correct by hand, because OpenClaw cannot read them from Router One: the context window, the output cap and the prices. OpenClaw marks common vision model IDs as image-capable on its own; when the model page lists image input and OpenClaw's metadata for the ID does not, pass --custom-image-input.
| Key in openclaw.json | Written by onboarding | What to set for Router One |
|---|---|---|
| models.providers.router-one.baseUrl and api | https://api.router.one/v1 and openai-completions | Keep both; api follows --custom-compatibility (next section) |
| models.providers.router-one.apiKey | The key in plaintext, or { source: "env", id: "CUSTOM_API_KEY" } with --secret-input-mode ref | In ref mode, put CUSTOM_API_KEY in ~/.openclaw/.env: the background service does not read ~/.zshrc or ~/.bashrc |
| models.providers.router-one.models[].id | openai/gpt-5.6-sol | The exact catalog ID; one entry per model you want to use |
| models[].contextWindow | 128000 | The context window from the model page as a whole number, e.g. 1050000 for openai/gpt-5.6-sol or 1048576 for anthropic/claude-sonnet-5; OpenClaw budgets history and compaction with it |
| models[].maxTokens | 4096 | The output cap OpenClaw requests per turn: choose it for your own use, not from a catalog figure. OpenClaw sends it as max_completion_tokens by default, and Router One's Chat Completions reference documents max_tokens as the output cap, so if you rely on the cap also set compat.maxTokensField to "max_tokens" on the model entry |
| models[].cost | input, output, cacheRead and cacheWrite all 0 | With these zeros, OpenClaw's Usage view shows $0; Dashboard → Logs has the real charge |
| agents.defaults.model.primary | router-one/openai/gpt-5.6-sol | A provider/model reference; change it with openclaw models set |
Pick --custom-compatibility by endpoint: openai, openai-responses or anthropic
The flag decides which Router One endpoint OpenClaw calls, and each endpoint serves a different set of catalog models. It is fixed per provider entry, so to use two protocols, onboard twice with two provider IDs, for example router-one and router-one-anthropic. Reusing router-one with a different base URL does not overwrite the first entry: OpenClaw saves the new one as router-one-2. For anthropic, OpenClaw 2026.9.6 reaches /v1/messages from either https://api.router.one or https://api.router.one/v1; use the host root, the Anthropic-compatible base URL Router One documents. On openai-completions, a custom endpoint counts as non-native, so OpenClaw sends your system prompt with the system role rather than developer. Onboarding's check request follows the protocol too: Hi with max_tokens 16 on Chat Completions, input Hi with max_output_tokens 16 on Responses, and max_tokens 1 on Messages.
| --custom-compatibility | api in openclaw.json · base URL | Router One endpoint | Catalog models it serves |
|---|---|---|---|
| openai (default) | openai-completions · https://api.router.one/v1 | POST /v1/chat/completions | Every chat model, e.g. anthropic/claude-sonnet-5, openai/gpt-5.6-sol, openai/gpt-6-sol, google/gemini-3.7-flash, deepseek-v4.1-flash, grok-4.7 |
| openai-responses | openai-responses · https://api.router.one/v1 | POST /v1/responses | GPT-family models, DeepSeek IDs and Grok chat models only; a Claude-family ID gets HTTP 400 model '<id>' must be called via … before any model runs, so onboarding's check fails too, and Gemini IDs are not served there either |
| anthropic | anthropic-messages · https://api.router.one | POST /v1/messages | Claude-family models and DeepSeek IDs, e.g. anthropic/claude-sonnet-5, aws/claude-sonnet-5, deepseek-v4.1-flash |
Where unattended spend comes from: heartbeat, wakes and fallbacks
OpenClaw is built to act on its own, so Dashboard → Logs can show requests nobody typed. The largest source is heartbeat, a scheduled turn of the main session, every 30 minutes by default, that works through a short checklist and may message you. Scheduled heartbeats need automations enabled (cron.enabled) and an owner to report to: the first entry of commands.ownerAllowFrom, or a channel allowFrom. Without a resolvable owner route each poll is skipped with reason=no-route before the model runs, so billed heartbeats usually begin once you connect a chat channel or name an owner, not right after onboarding. To control them, set agents.defaults.heartbeat.every to a longer interval, or to 0m to stop the recurring cadence; add activeHours to limit them to a time window; and set isolatedSession: true, so each heartbeat starts without the conversation history, plus lightContext: true to skip the workspace bootstrap files. The heartbeat docs put the saving of isolated runs at roughly 100K tokens down to 2–5K per run. 0m does not stop event-driven wakes: when a background command finishes, OpenClaw can run one turn to report it unless tools.exec.notifyOnExit is false. Two more sources are worth knowing. OpenClaw's own model fallbacks can move a failed turn to another model, provider or key you configured, outside Router One's same-model retries, so configure fallbacks deliberately. And memory search needs an embeddings provider, which Router One does not offer (there is no /v1/embeddings), so configure a separate one or accept keyword-only results.
Verify the first turn and switch models
Run openclaw gateway status to confirm the daemon is up, then openclaw dashboard to open the Control UI and send a message; openclaw models status shows the resolved default model and an overview of its credentials. Each message becomes at least one request in Dashboard → Logs under the key you created, with its model, tokens, cost, status and total time, and a streamed reply that produced output also shows its time to first token (TTFT). Tool calls add follow-up requests within the same turn, so a busy turn can be several requests. To change model for one chat, send /model router-one/<exact-id> -s; to change the default, run openclaw models set router-one/<exact-id>. Since custom providers need an explicit model list, add the ID to models.providers.router-one.models first, or onboard again with it. Prefer IDs whose model page lists tool calling, because OpenClaw's tools depend on it; for other IDs, test one tool call first.
Which model ID should OpenClaw send?
Copy the exact model ID from /models, preserving case, hyphens, and version suffixes; do not substitute a display name. Open its detail page and match the supported API endpoints, context window, and capabilities such as tool calling to the provider and features selected in OpenClaw. A catalog listing does not mean the client can use every feature of that model. Give each client or application a dedicated API key with a maxSpend cap.
Which API protocol is OpenClaw using?
OpenAI-compatible describes an interface format; it does not make Chat Completions (/v1/chat/completions), Responses (/v1/responses), and Anthropic Messages (/v1/messages) interchangeable. Check the installed client version, provider configuration, and actual request path against the model detail page and API compatibility fact sheet. A successful plain-text chat does not establish support for hosted tools, conversation state, or file-editing features.
Verify the OpenClaw call in your request trace
Send a simple text request from OpenClaw, then match its trace in Dashboard → Logs by time, model, and request_id: tokens, cost, latency, and status. Next, test streaming, tool calls, and multi-turn history separately. For failures, retain the actual request path, full error message, and request_id. If there is no matching log, check client configuration and connectivity before attributing the error to the gateway or upstream.
FAQ
openclaw onboard stops with Non-interactive setup requires explicit risk acknowledgement. What changed?
--non-interactive now requires --accept-risk, and both current release channels enforce it: the npm latest tag (2026.9.6) and extended-stable (2026.7.35). The flag acknowledges OpenClaw's security notice that agents with full system access are risky (docs.openclaw.ai/security). Onboarding checks for it before touching any configuration, so adding --accept-risk to the same command is all that is needed. Guides written before the flag existed, including earlier versions of this one, omit it. The flag does not approve plugin capabilities: if setup needs an external plugin, onboarding still stops for a capability review, and you preinstall the plugin and rerun.
Why does my Router One spend grow while nobody is chatting with OpenClaw?
Usually heartbeat: once OpenClaw has an owner route (a connected channel or commands.ownerAllowFrom), it runs a main-session turn every 30 minutes by default, each one a billed request that carries the session's history unless isolatedSession is on. In Dashboard → Logs they appear at a steady interval under OpenClaw's key. Lengthen agents.defaults.heartbeat.every or set it to 0m, add activeHours, or turn on isolatedSession and lightContext; the section above has the details. The key's maxSpend cap bounds the total either way.
OpenClaw's Usage view says $0. Which number is right?
Dashboard → Logs. Onboarding writes a cost of 0 for input, output, cacheRead and cacheWrite on custom models, so OpenClaw's Usage view shows $0 however much you use. Router One charges each request at the model's current rate and records the charge per request. You can copy rates from the model page into models[].cost to make OpenClaw's estimate meaningful, but treat Logs, and the Usage page that ranks your keys by spend, as the bill.
How do I call Claude through /v1/messages instead of Chat Completions?
Onboard a second provider with --custom-compatibility anthropic, --custom-base-url https://api.router.one (the host root), a new --custom-provider-id such as router-one-anthropic, and a Claude-family or DeepSeek ID such as anthropic/claude-sonnet-5. OpenClaw then uses its anthropic-messages transport and posts to /v1/messages; the check request sends an x-api-key header, which Router One accepts as an equivalent of Bearer. On this non-direct endpoint OpenClaw leaves out its implicit anthropic-beta headers; set models.providers.<id>.headers only if you need a specific one. Keep the Chat Completions provider for GPT, Gemini and Grok IDs, which /v1/messages does not serve.
Can OpenClaw use the Responses API through Router One?
Yes, for GPT-family models, DeepSeek IDs and Grok chat models: onboard a provider with --custom-compatibility openai-responses and one of those IDs, for example openai/gpt-6-sol or grok-4.7, and OpenClaw posts to /v1/responses. A Claude-family ID on that provider fails with HTTP 400 must be called via … before any model runs, and Gemini IDs are not served there either, so keep both on the default openai compatibility.
Where is the key stored, and why can't the daemon see the key I exported?
By default onboarding writes the key in plaintext into models.providers.router-one.apiKey in ~/.openclaw/openclaw.json, so the background service has it without any environment. With --secret-input-mode ref, it stores a reference to CUSTOM_API_KEY instead, and the service started by launchd, systemd or the Windows scheduler does not inherit variables exported in ~/.zshrc or ~/.bashrc. Put CUSTOM_API_KEY=sk-… in ~/.openclaw/.env, which OpenClaw's docs recommend for provider keys, and restart the Gateway. A .env in a workspace folder is ignored for provider credentials.
Memory search only finds keyword matches. Is Router One the cause?
Indirectly. OpenClaw's memory search uses an embeddings provider, OpenAI's by default, and falls back to keyword-only ranking when embedding setup fails. Router One serves chat, image and video endpoints but no /v1/embeddings, so it cannot be that provider. Configure a separate embeddings provider under memory.search.provider, or set provider to none if keyword search is enough.
How do I stop OpenClaw from spending past a budget?
Give OpenClaw its own Router One key and set maxSpend on it. When the key reaches its cap, the next request gets HTTP 402 and OpenClaw stops making paid calls on that key, while your other keys keep working; the console sends an in-app notification at 80% of maxSpend and again at the cap. Dashboard → Logs filters by that key, and Dashboard → Usage ranks keys by spend, so you can see what heartbeats and conversations actually cost before you raise the cap.
Which models can OpenClaw use through the gateway?
Choose a current catalog model that supports both the endpoint and the features OpenClaw uses. Check /models and the model detail page for the exact ID, current rates, and capabilities; a family name such as GPT or Claude is not a compatibility guarantee. A client that lists a model has only read the ID, from its own configuration or from GET /v1/models; verify an actual request too.
Models are listed, but requests fail with 400 or 404. What should I check?
Record the actual request path and error message, then check the exact model ID. A 400 can indicate invalid parameters, unsupported tools, or a model/endpoint mismatch; a 404 can indicate an incorrect path or missing resource, so it does not by itself establish that a model was retired. If the error says must be called via, use the named endpoint or select a model supported on the current endpoint. Do not add or remove /v1 or /chat/completions across all clients indiscriminately.
Does this work from Mainland China?
Yes. The gateway is reachable from Mainland China without a VPN, and the configuration is identical to the global setup.
How do I debug a 401/402/403/429?
Match the request and error message in Dashboard → Logs. For 401, check whether the key was sent and is valid; for 402, check wallet balance and maxSpend; for 403, check key permissions and access restrictions. For 429, distinguish request/token limits from upstream throttling using the error details. Keep the request_id and follow the error-codes reference.