OpenCode
Add RouteMux to opencode as a custom provider, for both the OpenAI and Anthropic protocol paths.
opencode reads providers from opencode.json in your project, or
~/.config/opencode/opencode.json globally. Each provider picks its own wire protocol, so
one RouteMux key can serve both paths.
opencode the client, not OpenCode Zen
This page is about the opencode agent. OpenCode Zen is a separate paid model service run by the same project — it is an upstream, not a client, and nothing here applies to it.
OpenAI-compatible path (most models)
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"routemux": {
"npm": "@ai-sdk/openai-compatible",
"name": "RouteMux",
"options": {
"baseURL": "https://api.routemux.com/v1",
"apiKey": "{env:ROUTEMUX_API_KEY}"
},
"models": {
"openai/gpt-5.5": {
"name": "GPT-5.5",
"limit": { "context": 1000000, "output": 128000 }
}
}
}
}
}Three things decide whether this works:
npmis the protocol selector.@ai-sdk/openai-compatiblesends/v1/chat/completions. Use@ai-sdk/openaiinstead if you want/v1/responses. You can overridenpmper model inside one provider block for a mixed setup.- Model keys must be exactly what the gateway returns.
GET /v1/modelsis authoritative. A key that does not match is not a typo opencode can recover from — it simply will not resolve. limitis not decoration. opencode uses it to track remaining context; leaving it out or copying the wrong numbers makes its context accounting wrong, not just cosmetic. The values are on each model's page.
A model is referenced as <providerID>/<modelID>. Our model ids already contain a slash, so
the full reference has three segments:
opencode run --model routemux/openai/gpt-5.5 "hello"Anthropic protocol path (Claude models)
Published anthropic/claude-* models are served on the Anthropic Messages protocol only —
they return 400 MODEL_PROTOCOL_UNSUPPORTED on
/v1/chat/completions. Override the built-in anthropic provider's base URL instead:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"anthropic": {
"options": { "baseURL": "https://api.routemux.com/v1" }
}
}
}This base URL keeps /v1 — unlike Claude Code
The AI SDK's Anthropic provider appends only /messages, so the base URL has to end at
/v1. That is the opposite of Claude Code, where ANTHROPIC_BASE_URL is the bare origin and
the client appends /v1/messages itself. Both land on the same endpoint; the field
conventions differ.
Credentials
/connect → Other stores a credential in ~/.local/share/opencode/auth.json and nothing
more — you still have to write the provider block, and the id you typed must match the key in
your config. A mismatch there is the first thing to check. opencode auth list shows what is
stored. Using {env:ROUTEMUX_API_KEY} in options.apiKey skips /connect entirely.
Trimming the model picker
whitelist narrows first, then blacklist subtracts — both take the model ids shown in the
picker. Useful when one RouteMux key can call more models than you want in the list.
What RouteMux does for you here
- Thinking parameters are sanitised. Some opencode releases send extra keys inside
thinking(a 1.18.26 build sentthinking.adaptive.block_binding). The upstream schema is strict and rejects the whole request over one unknown key, so RouteMux forwards only the keys the upstream accepts. - Your client User-Agent is not forwarded. Requests reach the upstream as
RouteMux/1, which avoids bot rules that have 403'd specific client user-agents outright.
Verify
curl https://api.routemux.com/v1/chat/completions \
-H "Authorization: Bearer $ROUTEMUX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"openai/gpt-5.5","messages":[{"role":"user","content":"ping"}]}'Troubleshooting
| Symptom | Cause |
|---|---|
| Provider missing from the model list | Provider id in opencode.json does not match the id used in /connect, or models is empty. |
400 MODEL_PROTOCOL_UNSUPPORTED | A Claude model on the openai-compatible provider. Use the anthropic base URL override above. |
| 404 on every request | baseURL missing /v1, or pointing at a full operation path instead of the API root. |
400 AUTH_AMBIGUOUS_API_KEY | A key in options.apiKey and a different one in options.headers. Keep one. |
| Context warnings look wrong | limit.context / limit.output do not match the model. |
422 BILLING_INSUFFICIENT_CREDITS | Wallet lacks credit. Failed requests are not billed. |