OpenClaw reference
The depth behind the OpenClaw guide. OpenClaw picks a model ref; the gateway picks the provider and the model id that reaches it.
Routing examples
Most orgs need exactly one config: dispatch by requested model. Machines carry a plain
model menu; the gateway decides the vendor. Set it on the console Routing page, or
PUT /admin/routing-config:
{
"strategy": {
"mode": "conditional",
"conditions": [
{ "query": { "params.model": { "$regex": "grok" } }, "then": "oci-grok" },
{ "query": { "params.model": { "$regex": "^gpt" } }, "then": "gpt" },
{ "query": { "params.model": { "$regex": "^claude-opus" } }, "then": "opus" },
{ "query": { "params.model": { "$regex": "^claude" } }, "then": "sonnet" }
],
"default": "sonnet"
},
"targets": [
{ "name": "oci-grok", "provider": "oracle",
"override_params": { "model": "xai.grok-4.3" } },
{ "name": "gpt", "provider": "openai",
"override_params": { "model": "gpt-5.5" } },
{ "name": "opus", "provider": "anthropic",
"override_params": { "model": "claude-opus-5" } },
{ "name": "sonnet", "provider": "anthropic",
"override_params": { "model": "claude-sonnet-5" } }
]
}
The other modes:
- Single: everything to Oracle
- Fallback: Oracle, then xAI direct
- Conditional: by team
- Load balance: mostly Grok, some gpt-oss
- Cross-vendor fallback chain
{
"strategy": { "mode": "single" },
"targets": [{ "provider": "oracle" }]
}
{
"strategy": { "mode": "fallback", "on_status_codes": [429, 500, 502, 503] },
"targets": [
{ "provider": "oracle" },
{ "provider": "x-ai", "override_params": { "model": "grok-4.3" } }
]
}
{
"strategy": {
"mode": "conditional",
"conditions": [{ "query": { "metadata.team": "research" }, "then": "frontier" }],
"default": "economy"
},
"targets": [
{ "name": "frontier", "provider": "oracle",
"override_params": { "model": "xai.grok-4.3" } },
{ "name": "economy", "provider": "oracle",
"override_params": { "model": "openai.gpt-oss-120b" } }
]
}
{
"strategy": { "mode": "loadbalance" },
"targets": [
{ "provider": "oracle", "weight": 3,
"override_params": { "model": "xai.grok-4.3" } },
{ "provider": "oracle", "weight": 1,
"override_params": { "model": "openai.gpt-oss-120b" } }
]
}
{
"strategy": { "mode": "fallback", "on_status_codes": [429, 500, 502, 503] },
"targets": [
{ "provider": "oracle", "override_params": { "model": "xai.grok-4.3" } },
{ "provider": "x-ai", "override_params": { "model": "grok-4.3" } },
{ "provider": "anthropic",
"override_params": { "model": "claude-sonnet-5" } }
],
"retry": { "attempts": 2 }
}
Every provider slug above is one Anyray serves directly (oracle, x-ai, anthropic,
openai, bedrock, vertex-ai, google, groq, deepseek, nebius, tensormesh,
openrouter, and more). Each target draws its credential from the server-held store; Oracle needs no api_key
in the config (region and credential come from the provider bag, and IAM signing has no key at
all). A client can override per request with the x-anyray-config header. Full semantics:
Configure → Routing.
Model ids do not transfer between vendors: xai.grok-4.3 is an OCI id, grok-4.3 is xAI's
own, and neither means anything to Anthropic. A fallback chain without
override_params.model on each target fails at the second hop with an upstream 404 rather
than failing over.
Rules that decide whether these behave as written:
- First match wins; the specific condition goes first.
^claude-opusmust precede^claude. The Grok arm matchesgrokunanchored because^grokwould miss OCI'sxai.grok-4.3. - Identity fields evaluate on the verified key-bound identity.
metadata.userandmetadata.teamcannot be steered viax-anyray-metadata; a team lane needs team-bearing keys (SSO enrollment). Othermetadata.<key>fields stay client-controlled, andparams.<field>reads the request body. - Routing applies to API-key traffic only.
passthroughsubscription traffic keeps the user's own token and provider. - A sole configured provider key is already the default: a single-provider deployment can skip routing config entirely.
Two layers of routing
| OpenClaw config | Gateway routing | |
|---|---|---|
| Lives in | openclaw.json, on the machine | Console Routing / PUT /admin/routing-config, server-side |
| Decides | Which provider entry and model ref a conversation uses | Which upstream provider serves it, and under which model id |
| Changed by | Editing the file (or /model), per machine | An admin, once, for every client at once |
| Holds credentials | The ark_… client key only | The provider keys (Oracle, xAI, Anthropic) |
| Sees | Nothing about upstreams | Fallback, load balance, conditions, retry |
Put in openclaw.json only what a user would legitimately choose (a fast model versus a
frontier one); leave which vendor serves that choice to the gateway.
An x-anyray-provider header on a provider entry wins over the org's routing config, and an
x-anyray-config header wins over both. With neither, the gateway applies the default
routing config; if none is stored and exactly one provider key is configured, that provider is
used. A container that sends no Anyray routing headers is fully steered by the console.
Stable aliases
Register aliases centrally (console, or PUT /admin/model-aliases) and the
Stable aliases variant becomes a config no vendor id ever appears in:
re-pointing frontier at another vendor is one admin change, and no machine is edited.
{
"frontier": "oracle/xai.grok-4.3",
"fast": "oracle/openai.gpt-oss-120b",
"claude": "anthropic/claude-sonnet-5"
}
An alias resolves only when the client sent no explicit provider (the gateway rewrites the
body model and names the resolved provider), so aliases and the
Two entries variant are mutually exclusive: a pinned x-anyray-provider
wins and the alias is ignored. contextWindow and maxTokens still describe whatever the
alias points at, so re-pointing across a large context-window gap means revisiting those
numbers.
Config variants
Ready-made openclaw.json shapes. All assume the gateway-side setup from
the guide; which vendors can share one entry is explained there, under
Adding more vendors.
These vary the models block only. Keep the guide's mcp.servers.anyray block alongside
whichever you pick: routing without it leaves most optimization held back
(why).
Grok direct from xAI
{
models: {
providers: {
anyray: {
baseUrl: 'https://gateway.example.anyray.ai',
api: 'anthropic-messages',
apiKey: '${ANYRAY_CLIENT_KEY}',
headers: {
'x-anyray-api-key': '${ANYRAY_CLIENT_KEY}',
'x-anyray-metadata': '{"tool":"openclaw"}',
},
// xAI's own ids, with NO dotted prefix. Same models as OCI serves,
// different namespace and a different provider key on the gateway.
models: [
{
id: 'grok-4.6',
name: 'Grok 4.6',
reasoning: true,
input: ['text'],
contextWindow: 500000,
maxTokens: 128000,
},
{
id: 'grok-build-0.1',
name: 'Grok Build 0.1 (cheap coding)',
reasoning: true,
input: ['text'],
contextWindow: 256000,
maxTokens: 128000,
},
],
},
},
},
agents: {
defaults: {
model: { primary: 'anyray/grok-4.6', fallbacks: ['anyray/grok-build-0.1'] },
},
},
}
Claude Opus and Sonnet
{
models: {
providers: {
anyray: {
baseUrl: 'https://gateway.example.anyray.ai',
api: 'anthropic-messages',
apiKey: '${ANYRAY_CLIENT_KEY}',
headers: {
'x-anyray-api-key': '${ANYRAY_CLIENT_KEY}',
'anthropic-beta': 'interleaved-thinking-2025-05-14',
'x-anyray-metadata': '{"tool":"openclaw"}',
},
// maxTokens is the model's OUTPUT ceiling, and it differs by family:
// 32000 for opus, 64000 for sonnet. Over-stating it is a 400.
models: [
{
id: 'claude-opus-5',
name: 'Claude Opus 5',
reasoning: true,
input: ['text', 'image'],
contextWindow: 1000000,
maxTokens: 32000,
},
{
id: 'claude-sonnet-5',
name: 'Claude Sonnet 5',
reasoning: true,
input: ['text', 'image'],
contextWindow: 1000000,
maxTokens: 64000,
},
],
},
},
},
agents: {
defaults: {
// Sonnet primary, Opus for the work that needs it (`/model
// anyray/claude-opus-5` per conversation).
model: {
primary: 'anyray/claude-sonnet-5',
fallbacks: ['anyray/claude-opus-5'],
},
// Never "none": that strips every cache_control marker, so each warm
// turn re-reads the whole prefix at full price.
params: { cacheRetention: 'short' },
},
},
}
Route these to anthropic or bedrock, never Oracle: the OCI translation loses
cache_control and thinking blocks, so a warm Claude session loses its prompt cache (Grok
on OCI is unaffected). Never set cacheRetention: "none"; unset resolves to "short".
GPT (needs its own entry)
{
models: {
providers: {
// GPT CANNOT share an anthropic-messages entry with Claude or Grok. The
// gateway's `openai` provider serves no /v1/messages endpoint, so a GPT
// id sent on that wire does not route. It needs this second entry, on
// the OpenAI wire, with the `/v1` base that transport expects.
'anyray-gpt': {
baseUrl: 'https://gateway.example.anyray.ai/v1',
api: 'openai-completions',
apiKey: '${ANYRAY_CLIENT_KEY}',
headers: {
'x-anyray-api-key': '${ANYRAY_CLIENT_KEY}',
'x-anyray-provider': 'openai',
'x-anyray-metadata': '{"tool":"openclaw"}',
},
models: [
{
id: 'gpt-5.5',
name: 'GPT-5.5',
reasoning: true,
input: ['text', 'image'],
contextWindow: 1050000,
maxTokens: 65536,
},
],
},
},
},
agents: {
defaults: { model: { primary: 'anyray-gpt/gpt-5.5' } },
},
}
Everything: Opus, Sonnet, Grok, GPT
Requires Dispatch by requested model from Routing examples.
{
models: {
providers: {
// TWO entries, because the wires differ. Claude and Grok share the
// anthropic-messages entry (the gateway translates Grok to xAI's chat
// path); GPT needs the OpenAI wire, since `openai` serves no
// /v1/messages. Both point at the same gateway and the same key.
//
// REQUIRES the "Dispatch by requested model" routing config. This
// entry pins no provider, and the gateway has no model->provider map, so
// without that config a Claude id and a Grok id are indistinguishable to
// it and both go wherever the default points.
anyray: {
baseUrl: 'https://gateway.example.anyray.ai',
api: 'anthropic-messages',
apiKey: '${ANYRAY_CLIENT_KEY}',
headers: {
'x-anyray-api-key': '${ANYRAY_CLIENT_KEY}',
'anthropic-beta': 'interleaved-thinking-2025-05-14',
'x-anyray-metadata': '{"tool":"openclaw"}',
},
models: [
{
id: 'claude-sonnet-5',
name: 'Claude Sonnet 5',
reasoning: true,
input: ['text', 'image'],
contextWindow: 1000000,
maxTokens: 64000,
},
{
id: 'claude-opus-5',
name: 'Claude Opus 5',
reasoning: true,
input: ['text', 'image'],
contextWindow: 1000000,
maxTokens: 32000,
},
{
id: 'xai.grok-4.3',
name: 'Grok 4.3 (OCI)',
reasoning: true,
input: ['text'],
contextWindow: 1000000,
maxTokens: 128000,
},
],
},
'anyray-gpt': {
baseUrl: 'https://gateway.example.anyray.ai/v1',
api: 'openai-completions',
apiKey: '${ANYRAY_CLIENT_KEY}',
headers: {
'x-anyray-api-key': '${ANYRAY_CLIENT_KEY}',
'x-anyray-provider': 'openai',
'x-anyray-metadata': '{"tool":"openclaw"}',
},
models: [
{
id: 'gpt-5.5',
name: 'GPT-5.5',
reasoning: true,
input: ['text', 'image'],
contextWindow: 1050000,
maxTokens: 65536,
},
],
},
},
},
agents: {
defaults: {
model: {
primary: 'anyray/claude-sonnet-5',
fallbacks: ['anyray/xai.grok-4.3'],
},
params: { cacheRetention: 'short' },
},
},
}
Stable aliases, no vendor ids
Register the aliases first: Stable aliases.
{
models: {
providers: {
anyray: {
baseUrl: 'https://gateway.example.anyray.ai',
api: 'anthropic-messages',
apiKey: '${ANYRAY_CLIENT_KEY}',
headers: {
'x-anyray-api-key': '${ANYRAY_CLIENT_KEY}',
'anthropic-beta': 'interleaved-thinking-2025-05-14',
'x-anyray-metadata': '{"tool":"openclaw"}',
},
// These ids are ALIASES registered on the gateway, not vendor models.
// Re-pointing `frontier` at another vendor is an admin change, and no
// machine is edited. See "Stable aliases" above.
models: [
{
id: 'frontier',
name: 'Frontier',
reasoning: true,
input: ['text'],
contextWindow: 1000000,
maxTokens: 128000,
},
{
id: 'fast',
name: 'Fast',
input: ['text'],
contextWindow: 131072,
maxTokens: 32000,
},
],
},
},
},
agents: {
defaults: {
model: { primary: 'anyray/frontier', fallbacks: ['anyray/fast'] },
params: { cacheRetention: 'short' },
},
},
}
Two entries, one provider each
{
models: {
providers: {
// Each entry names its gateway provider outright, so the model picker
// shows the vendor. An explicit provider header WINS over the org's
// routing config, so these two bypass it.
'anyray-oci': {
baseUrl: 'https://gateway.example.anyray.ai',
api: 'anthropic-messages',
apiKey: '${ANYRAY_CLIENT_KEY}',
headers: {
'x-anyray-api-key': '${ANYRAY_CLIENT_KEY}',
'x-anyray-provider': 'oracle',
'x-anyray-metadata': '{"tool":"openclaw"}',
},
models: [
{
id: 'xai.grok-4.3',
name: 'Grok 4.3 (OCI)',
reasoning: true,
input: ['text'],
contextWindow: 1000000,
maxTokens: 128000,
},
],
},
'anyray-claude': {
baseUrl: 'https://gateway.example.anyray.ai',
api: 'anthropic-messages',
apiKey: '${ANYRAY_CLIENT_KEY}',
headers: {
'x-anyray-api-key': '${ANYRAY_CLIENT_KEY}',
'x-anyray-provider': 'anthropic',
'anthropic-beta': 'interleaved-thinking-2025-05-14',
'x-anyray-metadata': '{"tool":"openclaw"}',
},
models: [
{
id: 'claude-sonnet-5',
name: 'Claude Sonnet 5',
reasoning: true,
input: ['text', 'image'],
contextWindow: 200000,
maxTokens: 64000,
},
],
},
},
},
agents: {
defaults: {
model: { primary: 'anyray-oci/xai.grok-4.3' },
params: { cacheRetention: 'short' },
},
},
}
Which OCI models to list
OCI fronts four vendors behind one provider slug; every model keeps OCI's dotted vendor prefix on the wire.
xAI Grok and OpenAI open-weights. Priced per million tokens, so they convert exactly.
| Model id | Context | Priced |
|---|---|---|
xai.grok-4.3 | 1M | Exact |
xai.grok-4.20 | 1M | Exact |
xai.grok-4.2 | 1M | Exact |
xai.grok-4-1-fast | 2M | Exact |
openai.gpt-oss-120b | 131k | Exact |
openai.gpt-oss-20b | 128k | Exact |
Meta Llama and Cohere Command. Route and meter normally, priced at a fallback rate. The OCI model reference is authoritative; any id it lists routes.
| Model id | Priced |
|---|---|
meta.llama-4-scout-17b-16e-instruct | Fallback rate |
meta.llama-3.3-70b-instruct | Fallback rate |
meta.llama-3.1-405b-instruct | Fallback rate |
cohere.command-a-03-2025 | Fallback rate |
cohere.command-a-vision | Fallback rate |
cohere.command-r-08-2024 | Fallback rate |
To list one, copy any variant above and swap the id; take contextWindow and maxTokens
from Oracle's model reference (maxTokens must be positive or the Messages transport refuses
to run; an over-stated contextWindow packs a prompt the model rejects).
- These ids look like Bedrock and Vertex ids, and are not. OCI's id is a prefix of
theirs: Vertex serves
meta.llama-3.3-70b-instruct-maas, Bedrockcohere.command-r-plus-v1:0. Same family, different upstream, different price. Send an OCI id only to theoracleprovider. - Meta and Cohere spend is indicative, not exact. Oracle bills those families per
character with no published characters-per-token ratio, so Anyray prices them at a
fallback rate (each row records it as
priceSource). Oracle's invoice is authoritative. - Only Grok caches on OCI. Oracle publishes a cached-input rate for the Grok ids (warm
prefixes price at the read tier) and no automatic prompt caching for
meta.llama-*,cohere.*, oropenai.gpt-oss-*, so cache-busting strategies stay free on those.
Other ways to connect
anyray-connect (unsandboxed machines)
Where you can install into the machine, Connect writes the whole config:
curl -fsSL https://app.anyray.ai/connect.sh | sh -s -- --gateway http://<gateway>:8787 --tools openclaw
Use the installer, not npx. Connect registers the retrieval MCP server below only when its
own binary is durable, and npm runs npx from a temporary cache, so that form routes OpenClaw
and then reports Skipped the Anyray retrieval MCP server.
Enroll first with --enroll <link> (or --sso). --dry-run previews. Connect merges
Anyray-owned keys into your existing config and writes:
models.providers.anthropic: the gateway origin asbaseUrl,api: "anthropic-messages", and amaxTokensvalue;models.providers.openai:http://<gateway>:8787/v1.- Your personal
ark_…key onapiKeyand thex-anyray-api-keyheader. - A default model only if you have none: primary
anthropic/claude-sonnet-4-5, fallbackanthropic/claude-haiku-4-5whenfallbacksis absent. An unrouted primary or fallback (sayopenrouter/…) is kept but reported: it bypasses the gateway. agents.defaults.params.cacheRetention: "short"unless you already set a value, plus the beta headers OpenClaw drops on a custom host.mcp.servers.anyray: the retrieval server (anyray_retrieve,anyray_recall) as a local stdio command, so a trim the optimizer replaced with a marker can be read back. Restart the gateway, or runopenclaw mcp reload, to connect it. Ananyrayserver you already have is left alone and reported. Container installs register the remote endpoint instead, since there is no binary to launch.
--revert removes only what Connect wrote; OpenClaw hot-reloads model config. Run
openclaw config validate to confirm.
The Anyray plugin
For per-conversation sessions, plus a model menu your admin config drives. Connect installs
the Anyray plugin (@anyray/openclaw-plugin) at ~/.anyray/openclaw-plugin, registered under
plugins.load.paths and plugins.entries.anyray. It contributes the provider entry, stamps
an opaque session id per call, and sets cache retention in code.
The plugin asks your gateway which models to show (GET /v1/me/models, plugin ≥ 0.4.0). It
lists every alias you registered plus every model a conditional routing lane names outright,
so adding a provider is a console action: store the key, add the lane or the alias, and
the model reaches every host's picker with nothing edited on the machine and the key never
leaving the gateway. A gateway that cannot answer (older build, unreachable) leaves the
plugin's built-in list in place, so the picker is never empty.
OpenClaw caches the resolved list per agent and does not re-read it on a restart, so the new
model lands when Connect next applies (the fleet drift watcher does this unattended, and
anyray-connect apply forces it). Both paths clear that cache for you.
A $regex lane contributes nothing to the menu — a regex matches ids, it cannot list them —
so publish the menu through aliases and keep the regex for dispatch:
Add them on the provider's card under Providers → Models, or with
PUT /admin/model-aliases:
{ "grok-4.6": "x-ai/grok-4.6", "gpt-5.5": "openai/gpt-5.5" }
Giving each OpenClaw host its own upstream key
Two OpenClaw hosts can share one account for most models and hold their own for one. Mint an agent per host on the Agents page, then on Routing add a lane per host with And also set to the model that must stay separate. Everything else falls through to the base strategy:
| Match | And also | Provider | Key |
|---|---|---|---|
agent id svc_a | model grok-4.6 | x-ai | xai-agent-a |
agent id svc_b | model grok-4.6 | x-ai | xai-agent-b |
| (base strategy) | bedrock | shared |
The user picks from the same menu on every host. Grok bills that host's own xAI key; Opus matches no lane and lands on the shared Bedrock account.
The agent identity comes from the client key the plugin sends, so each host must be enrolled with its own agent key. Two hosts sharing one key are one agent to the gateway, and both lanes collapse onto whichever matches first.
First match wins, and lanes cannot be reordered on the page. A plain agent id svc_a lane
above the narrowed one swallows its traffic, so the separate key never bills. The page flags
this, but adding them in order avoids it.
The plugin's built-in list (the fallback) carries Claude ids plus xAI-direct Grok ids
(grok-4.6, grok-4.3, grok-build-0.1), naming the x-ai provider on those calls itself
(not OCI's xai.grok-4.3, served by oracle). A model your gateway listed is never stamped:
your stored routing resolves it, which is what carries a provider_key_id pin.
To govern the built-in Grok ids by your routing config too (plugin ≥ 0.3.0), set
gatewayRouting: true in the plugin's config block; required for
named-key pinning (provider_key_id) and
conditional dispatch. Add the routing rule first, then flip the flag: an unstamped Grok
request with no matching rule lands on your default provider and fails there. The usual rule:
{ strategy: { mode: 'conditional',
conditions: [ { query: { 'params.model': { $regex: '^grok-' } }, then: 'xai' } ],
default: 'main' },
targets: [ { name: 'xai', provider: 'x-ai', provider_key_id: 'team-research' },
{ name: 'main', provider: 'anthropic' } ] }
Restart the OpenClaw gateway once after the first install; config-only changes hot-reload.
Run openclaw config validate and pick anyray/* models. Source:
anyrayHQ/openclaw-plugin, MIT; Connect ships an
embedded copy.
The plugin imports plugin-sdk/provider-entry, which landed in 2026.7.1. On older builds the
import fails at load and the gateway logs the plugin as unavailable; models.providers
entries still work there.
0.1.1 and earlier stamped every request as {"tool":"openclaw"}, dropping the configured
user and team, so that traffic reached the spend store unattributed. 0.2.0 sends the
configured values. Connect-installed copies were never affected.
Claude subscription stays native
OpenClaw can ride a personal Claude subscription (a pasted setup token, or its claude-cli
runtime). Connect holds a detected Claude seat on that native lane; pass
--org --tools openclaw to move it onto the org API bill. A seat appearing on a machine
Connect had already routed removes the stale gateway route.
Seat detection reads the machine's Claude sign-in state, not OpenClaw's credential store: a
setup token pasted only into OpenClaw is invisible to Connect. In that case skip openclaw
in the sweep (or accept that routing it moves that usage to org billing).
Troubleshooting
| What you see | Cause |
|---|---|
| Requests 404, or the optimizer never fires | No explicit api: 'anthropic-messages', so OpenClaw served the entry over openai-completions. |
Boot fails with expected array, received undefined | A customized provider entry must list its own models; it no longer inherits OpenClaw's catalog. |
| Nothing routes, no errors | Routing is inert until a model ref resolves to the provider. Set agents.defaults.model.primary, or /model anyray/xai.grok-4.3. |
| 401 naming an Oracle credential | No Oracle credential stored on the gateway. It fails closed rather than forwarding your ark_ key upstream. |
400 naming oracleRegion | The region is mandatory and has no default. |
| A new optimizer session mid-conversation | Switching model or thinking level changes OpenClaw's own Runtime line, which the session fingerprint covers. Costs one re-decided prefix, nothing after. |
openclaw mcp probe anyray --json does not show both retrieval tools | The server is unavailable or discovery returned an empty catalog. Run openclaw mcp reload, probe for anyray_retrieve and anyray_recall again, then run openclaw gateway restart if either is still absent. Anyray treats affected turns as no_discovery and keeps reversible trimming off. |