Add a custom OpenAI-compatible provider to OpenCode.
Updated 2026-07-16
OpenCode reads custom providers straight from opencode.json. Declare a provider block with the @ai-sdk/openai-compatible package, point options.baseURL at https://api.apisrouter.com/v1, and every model you list becomes selectable in the /models picker under one key.
Quick answer: one provider block in opencode.json.
OpenCode supports custom OpenAI-compatible providers natively. Add a provider entry to opencode.json with npm set to "@ai-sdk/openai-compatible", set options.baseURL to https://api.apisrouter.com/v1, read the key from an environment variable with the {env:...} template, and list the model ids you want under models. Then set the top-level model field to "apisrouter/<model-id>" and OpenCode routes the whole agent loop through the gateway. This is the documented custom-provider path in the OpenCode docs, not a wrapper or a fork. The config file lives either at your project root (opencode.json) or globally at ~/.config/opencode/opencode.json, and the two are merged, so the provider block can be declared once and reused across every repo.
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"apisrouter": {
"npm": "@ai-sdk/openai-compatible",
"name": "APIsRouter",
"options": {
"baseURL": "https://api.apisrouter.com/v1",
"apiKey": "{env:APISROUTER_API_KEY}"
},
"models": {
"claude-sonnet-4-6": { "name": "Claude Sonnet 4.6" }
}
}
},
"model": "apisrouter/claude-sonnet-4-6"
}How OpenCode resolves providers and models.
OpenCode (anomalyco on GitHub, one of the most-starred terminal coding agents at roughly 186K stars) builds its provider layer on the Vercel AI SDK. The npm field in a provider block names which SDK package OpenCode loads to talk to that provider: "@ai-sdk/openai-compatible" speaks the standard /v1/chat/completions protocol, while "@ai-sdk/openai" speaks OpenAI's /v1/responses protocol. A multi-vendor gateway serves chat completions, so openai-compatible is the right package; picking "@ai-sdk/openai" against a chat-completions endpoint is the most common way this setup breaks. Models are addressed as provider/model pairs. The provider id is whatever key you chose in the provider block ("apisrouter" above), and the model id is the key inside the models map, so the default model becomes "apisrouter/claude-sonnet-4-6". Everything you declare shows up in the /models picker inside the TUI, switchable mid-session. One behavior worth internalizing: for custom providers, the models map is an allowlist. Built-in providers ship with a known catalog, but OpenCode cannot enumerate a custom endpoint's models on its own, so only ids you explicitly declare are addressable. When the endpoint behind baseURL serves Claude, GPT, DeepSeek, and Kimi ids side by side, declaring one entry per model turns the picker into a cross-vendor switchboard behind a single key.
Full setup: global config, project config, per-model limits.
The clean layout is to declare the provider once in the global config at ~/.config/opencode/opencode.json and keep only per-repo choices (which model, which agents) in each project's opencode.json. OpenCode merges config files rather than replacing them, so the project file stays tiny and the provider block never gets duplicated. The {env:APISROUTER_API_KEY} template resolves at load time from the environment, which keeps the key out of any file that might get committed. Export it from your shell profile so every terminal session that launches OpenCode can see it. Each model entry also accepts a limit object with context and output token ceilings. Declaring them matters more than it looks: OpenCode uses the context figure to decide when a session needs summarization, so a long-context model declared without limits gets treated more conservatively than it should. Set limit.context to what the model actually supports and long sessions compact later instead of earlier.
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"apisrouter": {
"npm": "@ai-sdk/openai-compatible",
"name": "APIsRouter",
"options": {
"baseURL": "https://api.apisrouter.com/v1",
"apiKey": "{env:APISROUTER_API_KEY}"
},
"models": {
"claude-opus-4-7": { "name": "Claude Opus 4.7", "limit": { "context": 200000, "output": 32000 } },
"claude-sonnet-4-6": { "name": "Claude Sonnet 4.6", "limit": { "context": 200000, "output": 64000 } },
"gpt-5.5": { "name": "GPT-5.5" },
"gpt-5.3-codex-spark": { "name": "GPT-5.3 Codex Spark" },
"kimi-k2.7-code": { "name": "Kimi K2.7 Code" }
}
}
},
"model": "apisrouter/claude-sonnet-4-6",
"small_model": "apisrouter/kimi-k2.7-code"
}Choosing model and small_model.
The practical workflow is to hold the main slot on the model you trust for edits and rotate candidates through real sessions rather than benchmarks: an afternoon of actual diffs against your own codebase tells you more than a leaderboard. Routing through one endpoint makes each candidate a one-line change, and the per-key usage view shows what each experiment actually cost.
- model drives the main agent loop: reading files, planning edits, writing diffs, running tools. This slot sees the longest contexts and does the actual engineering, so a frontier coding model (claude-sonnet-4-6, claude-opus-4-7, gpt-5.5) belongs here.
- small_model handles lightweight tasks like session title generation. It fires often but never carries the coding work, so a fast, inexpensive id is the right shape; there is no reason to burn frontier tokens on titles.
- Coding-tuned ids like gpt-5.3-codex-spark and kimi-k2.7-code are worth declaring even if they are not your default: switching to them for a refactor-heavy session is one /models selection, not a config edit.
- Because both slots take provider/model strings against the same provider block, the main and small slots can come from different vendors in the same session, something no single-vendor key allows.
Pay-as-you-go · transparent per-model pricing
Selected models are priced below official list prices. Exact input, output, cache, and per-request prices are shown for each model.
| Model | Official Price | Our Price |
|---|---|---|
| Claude Sonnet 4.6 | $3.00 / $15.00 per M | $2.40 / $12.00 per M |
| Claude Opus 4.7 | $5.00 / $25.00 per M | $4.00 / $20.00 per M |
| GPT-5.5 | $5.00 / $30.00 per M | $4.00 / $24.00 per M |
| GPT-5.3 Codex Spark | $1.75 / $14.00 per M | $1.40 / $11.20 per M |
| Kimi K2.7 Code | $0.95 / $4.00 per M | $0.85 / $3.60 per M |
The failure modes specific to OpenCode custom providers.
Wrong SDK package. "@ai-sdk/openai" posts to /v1/responses; a chat-completions gateway answers that route with an error. If your first request fails with a protocol- or route-shaped error rather than an auth error, check the npm field says "@ai-sdk/openai-compatible" exactly. Model absent from the picker. Custom-provider models only exist if declared; a typo in a models key, or an id you assumed but never added, simply does not appear in /models. Ids are exact strings including version suffixes, and the gateway's /v1/models listing is the source of truth to copy from. Unresolved {env:...}. The template resolves from the environment of the process that launched OpenCode. A key exported in one terminal does not reach an OpenCode instance launched from another terminal or from a desktop launcher that never sourced your profile. Put the export in the shell profile, not a one-off session. Config-merge surprises. Because global and project configs merge, a project opencode.json that sets model to a different provider silently overrides your global default, and a leftover provider block in an old project can shadow expectations. When routing looks wrong, read both files before assuming the gateway misbehaved. baseURL without /v1. The SDK appends route paths like /chat/completions to whatever base you give it, so https://api.apisrouter.com/v1 is correct and the bare host is not. A connection or 404-shaped failure on an otherwise-correct config is almost always this.
Who routes OpenCode through a gateway.
- Developers who live in the TUI all day and want Claude, GPT, and Kimi in one /models picker instead of maintaining separate provider credentials per vendor.
- Engineers comparing coding models on real work. Every candidate is one declared entry and one picker selection; session-by-session comparison needs no new accounts.
- Teams standardizing one secret. A single APISROUTER_API_KEY in the onboarding docs replaces a per-vendor key checklist, and per-key usage shows who spends what.
- Users pairing a frontier main model with a low-priced small_model from a different vendor, which single-vendor configs cannot express.
- Developers without access to a given vendor's billing. Top-up based access with no card requirement removes the sign-up dependency per provider.
Verify the endpoint and debug the first session.
Before starting a session, list what the gateway serves. The ids returned by /v1/models are exactly the strings your models map keys must match. First-session failures are consistent. A 401 means APISROUTER_API_KEY was not visible to the OpenCode process; echo the variable in the same terminal you launch from. A model-not-found error from the gateway means the declared key does not match a served id, including version suffixes. If the provider does not appear at all, validate the JSON, since a trailing comma or misplaced brace makes the whole file unreadable and OpenCode falls back to defaults. Once requests flow, the APIsRouter console shows per-request model, token counts, and spend. Coding agents are long-context, many-turn workloads, and seeing which sessions and which models consume the tokens is how you decide whether the main slot is earning its price.
curl -s https://api.apisrouter.com/v1/models \
-H "Authorization: Bearer $APISROUTER_API_KEY" | head -50FAQ
Can OpenCode use Claude, GPT, and Kimi models through one custom provider?
Yes. A custom provider is just a baseURL plus a models allowlist. When the endpoint serves multiple vendors, declare one entry per id and every declared model appears in the /models picker under the same provider and key, switchable mid-session.
Where does the API key go in opencode.json?
In options.apiKey using the environment template, for example "{env:APISROUTER_API_KEY}". The template resolves at load time so the literal key never sits in the config file. Export the variable from your shell profile so every terminal that launches OpenCode inherits it.
Should the provider block live in the global or the project config?
Global, at ~/.config/opencode/opencode.json. OpenCode merges config files, so declaring the provider once globally and setting only the model choice per project keeps repos free of credentials plumbing and avoids duplicated blocks drifting apart.
Why does my model not show up in the /models picker?
Custom-provider models must be declared explicitly; OpenCode cannot enumerate a custom endpoint. Check the models map contains the exact id string, including version suffixes, and copy ids from the gateway's /v1/models response rather than typing them from memory.
What is the difference between @ai-sdk/openai-compatible and @ai-sdk/openai here?
@ai-sdk/openai-compatible speaks /v1/chat/completions, the protocol multi-vendor gateways serve. @ai-sdk/openai speaks OpenAI's /v1/responses protocol. For APIsRouter, use @ai-sdk/openai-compatible; the other package will post to a route the gateway does not serve for this purpose.
Do declared context limits actually matter?
Yes. OpenCode uses limit.context to decide when a session needs compaction. Leaving limits undeclared on a long-context model means sessions get summarized earlier than necessary, so set limit.context and limit.output to what the model genuinely supports.