Edition scope: the provider stack below — ~/.openclaw/… credential files, openclaw.json, and the gateway-pre-start.sh self-heal pass — is OpenClaw / dual only. None of it exists on the Hermes edition, where provider credentials are held by Hermes itself (hermes auth add, stored under ~/.hermes) and the provider ids are Hermes’ own. See AI Providers on Hermes.The Cloud defaults for speech and embeddings and Plan gates sections are ClawBox’s own and apply to every edition.
For choosing a provider as a user, see Choose Your AI Provider. This page is the technical reference: what actually gets written where when you configure each provider, and how the system repairs common broken states.

Provider lanes at a glance

OpenAI has two distinct lanes, and since OpenClaw 2 they share one namespace. Both are openai/…; what separates them is the credential and one config key. The API key is the auth profile openai:default; the ChatGPT subscription is the OAuth profile openai:chatgpt, and its models carry agents.defaults.models."openai/<id>".agentRuntime.id = "codex", which is what routes the turn through the Codex app-server and your ChatGPT plan. The catalogs still differ: the API lane carries the -pro tiers in addition to gpt-5.4/5.5/5.6, while the subscription serves gpt-5.6-sol/terra/luna, gpt-5.5, gpt-5.4, gpt-5.4-mini and no -pro at all. Mixing them up is the root of most “Incorrect API key” tickets — see self-healing below.The older codex/… and openai-codex/… namespaces are retired: the core answers Unknown model for them and openclaw doctor --fix rewrites them to openai/… while keeping the runtime key. The setup UI still calls the row “OpenAI Codex” — that is a label, not a namespace. A box that signed in before the upgrade holds a codex:default profile the core never consults, and the chat model list shows that row greyed with “Sign in again in Settings”; reconnecting OpenAI in Settings files it where the core looks.

Where credentials actually go

Configuring a provider writes up to four places:
  1. ~/.openclaw/agents/main/agent/auth-profiles.json — the real secret. API keys as {type:"api_key", provider, key}; subscriptions as {type:"oauth", provider, access, id, refresh, expires}.
  2. openclaw.json → auth.profiles.<provider>:default — metadata only ({provider, mode}). No secret lives here.
  3. openclaw.json → models.providers.<provider> — the provider definition (baseUrl, API dialect, model list) for lanes that need one (ClawBox AI, Anthropic, Google, OpenRouter, Ollama, llama.cpp).
  4. ~/.openclaw/agents/<agent>/agent/codex-home/auth.json (+ ~/.codex/auth.json) — the ChatGPT-subscription session, synthesized from the auth profile on every gateway start.
The active model is agents.defaults.model.primary (e.g. openai/gpt-5.5), with agents.defaults.model.fallbacks behind it (typically ClawBox AI or a local model). Per-provider credential preference, when a box holds both OpenAI credentials, is the core’s own openclaw models auth order set --agent main --provider openai <profileIds…> — stored in the agent’s auth store, revised by whichever OpenAI save happened last, and cleared when only one OpenAI profile is left.

Compatibility routing (why some lanes use “OpenAI-compat”)

Some native OpenClaw provider plugins expect auth stores ClawBox doesn’t populate. ClawBox therefore routes Anthropic via https://api.anthropic.com/v1 and Google via https://generativelanguage.googleapis.com/v1beta/openai — both OpenAI-compatible endpoints with the key inline in the provider definition. Behavior is identical for users; the win is call-time auth that always works. OpenRouter likewise requires an explicit provider definition (https://openrouter.ai/api/v1) — without one, chats silently return empty usage.

ClawBox AI specifics

  • Pairing uses a device-code flow against the portal (clawbox.com) — the wizard shows a short code you confirm in your portal account; the resulting token is stored on-device.
  • Requests proxy through the portal’s AI endpoint; both models advertise reasoning support including X-High effort (mapped upstream to DeepSeek’s maximum reasoning mode).
  • Changing the linked portal account automatically unpairs ClawKeep (backups belong to the account).
  • Two tier values are kept side by side and answer different questions: the device badge (clawai_tier) is what this box should default to, and the plan (clawai_plan_tier) is what the account pays for. Only the second may be read before refusing an entitlement — a Max subscriber whose box is badged Flash is a state the product preserves on purpose.

Cloud defaults for speech and embeddings

On a box linked to ClawBox AI, three capabilities default to the cloud the plan already covers rather than to an engine on the device. The rule is pure and lives in one place (clawai-cloud-defaults-state.ts); the applier runs 45 s after boot and again after any ClawBox AI save, and GET /setup-api/ai-cloud-defaults publishes the verdict and its reason. ¹ and only once POST <proxy>/embeddings has answered a probe (cached 6 h on success, 30 min on failure, cleared when the credential changes). The table is edition-neutral, embeddings included. On the edition where ClawBox owns the memory index itself rather than the agent, the index client accepts exactly two endpoints — the loopback embedder proxy on the device, and this box’s own ClawBox AI embeddings endpoint — checked per request, so the same cloud default applies there. Where that edition records the owner’s pick it does so as a word in ClawBox’s own store (memory_shard_embedder), never an address and never a copy of the credential; the older memory_embeddings_choice_source: "owner" mark is honoured only where that pin stands beside it, so a box that finished the wizard before the cloud option existed is promoted rather than pinned to the device for good. The three rules differ because the three capabilities are not sold the same way: the transcription route has no tier gate, the cloud voice is Max-only on the proxy and would otherwise cost a failed round trip before every fall-back, and moving the memory index invalidates its fingerprint and costs a full reindex — not something to spend on a guess. Two invariants:
  • It only ever promotes. Nothing here moves a capability from the cloud back onto the box; withdrawal is the boot script’s single, plan-gated act.
  • An explicit owner choice is never overwritten. Every surface that lets a person pick records owner beside the pick (tts_choice_source, stt_choice_source, memory_embeddings_choice_source). A box that predates those keys is read by what only a person could have written.
A refusal names one of not_linked, plan, route_unavailable or owner, and the UI prints the matching sentence rather than a bare “not available”. (edition is a fifth word the catalogue still carries and no shipping edition answers with — it is the build-time kill switch for cloud embeddings, kept worded in every locale so flipping it needs no new strings.)

Plan gates

paid-plan-gate.ts is the one place the tier arithmetic lives, and the internal names are off by one: internal flash is marketed Pro, internal pro is Max. Every refusal carries the stable code paid_plan_required — the same word the portal’s own 402 uses.

Reasoning effort

The chat header’s reasoning-effort picker is uniform across every cloud provider — Off, Low, Medium, High — so the control behaves the same whichever model you select. The gateway accepts this ladder for all of them and normalizes it per provider. Two consequences worth knowing:
  • A model with only one level shows no control at all. The header renders the picker only when more than one level is available, which is why the on-device local model has no reasoning dial.
  • An unsupported level falls back to that provider’s default, not to the nearest neighbouring level. Switching provider mid-conversation therefore resets the effort to the new provider’s default if the current one is not in its list. The selection is remembered per provider.
Provider-specific extras that the gateway vocabulary also accepts (minimal, xhigh, max, adaptive) are deliberately not offered in the picker, to keep the control consistent across models.
Model reasoning capability is declared programmatically, not by hand-editing config. Separately from the picker above, each model entry carries gateway compatibility metadata describing which efforts it accepts. Editing that by hand in openclaw.json does not stick — two mechanisms overwrite it:
  1. The gateway re-applies its expected values on every start. ClawBox’s boot-time configuration step rewrites the reasoning metadata on the ClawBox AI models each time the gateway starts, so a manual edit survives only until the next restart.
  2. System updates replace the bundled model catalog. Updating OpenClaw overwrites the packaged catalog wholesale, so edits made there are lost on the next update. ClawBox re-applies its own values on each boot precisely because of this.
If a level is not declared as supported, the gateway rejects it. Changes must therefore be made in ClawBox’s setup and boot-time configuration code, which is the single source of truth.

Local models (Ollama / llama.cpp)

  • Local lanes authenticate with a per-install bearer token through a local proxy — nothing is exposed unauthenticated, even on localhost.
  • Only one local runtime is active at a time (8 GB Jetson RAM). Ollama gets tuned automatically (quantized KV cache, flash attention, single loaded model).
  • A local model can be the primary or the fallback; the wizard’s fallback chain prefers a configured local model, else ClawBox AI.

Self-healing on boot (beta channel)

gateway-pre-start.sh runs before every gateway start and repairs known-bad provider states. On the beta channel (and the next stable release) this includes: All heals are idempotent — a healthy config is left untouched.

Troubleshooting pointers

  • 401 Incorrect API key (sk-proj-…) after switching to the subscription → Troubleshooting → AI provider errors
  • Config validation failed: … Unrecognized key → stale OpenClaw core; sudo bash install.sh --step openclaw_install
  • Codex chats crash immediately (createDiagnosticTraceContext… is not a function) → @openclaw/codex plugin version skew vs core; re-run sudo bash install.sh --step openclaw_install (re-pins plugins)
  • Model picker rejects a model ID → each lane validates against its own catalog; remember the two OpenAI lanes have different catalogs.