How to Use FreeRouter with Opencode
Opencode is an AI coding agent you drive from the terminal, with Web and IDE surfaces alongside it. It talks to models through providers — 75+ of them via the AI SDK and Models.dev, plus local models — and adding one is always the same two steps: store the key with /connect, describe the provider in the provider section of opencode.json. That is a reasonable setup — the editor absorbs provider differences and you pick from /models.
The first-party options are OpenCode Zen and OpenCode Go. Zen is the team's list of models tested and verified to work well with Opencode — the docs recommend it as the starting point for newcomers — and Go is a low-cost subscription for reliable access to popular open coding models. Both are optional and behave like any other provider in the picker.
What any single choice commits you to is a fixed roster. The model ids in your config are the ones that list or plan carries. Its availability is your availability: when the gateway is slow, rate-limits you, or hasn't listed the model you want, the fix is another provider block, another key, another round of edits.
The constraint that bites first is model access. Coding models ship often, and they rarely land on every gateway the same week. When the one you want lives somewhere else — a gateway neither Zen nor Go covers — you either wait or you maintain parallel configs per gateway and remember which key goes with which id.
FreeRouter slots into the same custom-provider mechanism as any other OpenAI-compatible gateway. One fr_live_ key speaks OpenAI-compatible chat completions plus the Responses API at POST /v1/responses, so Opencode keeps sending the requests it already sends, and the key's routing rule decides which gateway serves them. The migration is the familiar two changes: base URL to https://api.freerouter.com/v1 and your gateway key for a FreeRouter one. A key speaks one request shape — the default openai shape is what Opencode's OpenAI-compatible provider expects.
Below: both ways to wire it up — the custom-provider dialog and opencode.json — then why a router earns its place behind an editor, and where the edges are.
What it looks like when Opencode talks to FreeRouter
Opencode sends OpenAI-style requests to a FreeRouter key. The key carries the shape, the routing rule, and any model remaps. Behind it sit your gateway accounts — OpenRouter can be one of them — each with your own provider key. Adding a gateway later is a dashboard row, and Opencode's config never changes.
flowchart LR
Opencode["Opencode"] --> Key["FreeRouter key: openai shape, rule, remaps"]
Key --> OR["OpenRouter (your key)"]
Key -.->|"a row you add later"| GW2["Second gateway (your key)"]
OR --> M["model"]
GW2 --> M2["model"]
Opencode speaks to one key. The dotted path is a dashboard edit, not a config edit.
Why a router earns its place behind an editor
Three properties matter in an editor: you can reach the model you want, a failure doesn't end the session, and you can tell what happened afterward. Each maps to a FreeRouter feature.
1. OpenRouter stays useful — as one target among several
If you already use OpenRouter in Opencode, nothing here asks you to leave it. Store its key as a provider — BYOK, so you keep the account and the bill — route 100% of the key there on day one, and Opencode behaves as before. The difference is positional: OpenRouter is now a row in a rule rather than the address in your config.
From there the option set widens without touching opencode.json: add a second gateway key, split traffic or hold it as failover-only, and compare providers on your own editor traffic in the Logs tab. That is the whole case for wrapping gateways instead of committing to one — the editor keeps a single address while the set behind it changes.
2. Model changes become remaps
A model remap rewrites one incoming model id to a fixed provider and upstream model before the rule runs. When a new coding model lands on only one of your gateways, point the id Opencode already sends at that destination. Opencode's config never learns the new name.
Each remap carries a percent (1–100), rolled per request, so percent: 20 sends a fifth of that model's traffic to the new destination while the rest follows the normal rule. Failed attempts fall back to the rule's targets. Compare in the logs, then take it to 100 or delete it.
3. Failures get a second target and a paper trail
A priority rule with two targets retries the next one on connection errors, 5xx, 429, an unknown model, or no response headers inside the first-byte budget. A 400 that would fail everywhere returns immediately instead of being retried around the list.
When a retry happens, X-FreeRouter-Failover names each skipped attempt as gateway:reason, X-FreeRouter-Provider names who served, and every proxied request logs a Router, Upstream, and Duration split you can quote with X-FreeRouter-Request-Id. That is retry behavior you configure rather than write.
4. Responses support rides along
Opencode's newer paths speak the Responses API. FreeRouter serves POST /v1/responses on openai and openrouter keys — natively where the gateway implements it, by translation everywhere else, with X-FreeRouter-Responses-Mode saying which. Same base URL, same key.
Chaining works too, with one condition: previous_response_id names a response stored by one upstream account, so FreeRouter returns its own opaque id (resp_fr_…) and pins the follow-up to the provider that holds the state. Only chain-capable gateways can anchor a chain — Ramp Router, Cloudflare AI Gateway, and LLM Gateway, verified against live captures — so route the key at one of those if your tool chains turns. The Conversations API's conversation field is still rejected; see Chaining.
Setting it up
Fifteen minutes, most of it in the dashboard. You need at least one gateway's API key and the ability to edit your Opencode config.
- Sign up. Create an account — Google or email plus an OTP. You land in a workspace.
- Add your gateway key as a provider. Providers → add the gateway you already use, paste its key. It's encrypted at rest and scoped to the workspace. Nothing stops you running the direct path and the FreeRouter path side by side while you cut over.
- Create a FreeRouter key with the
openaishape. API Keys → new key. The defaultopenaishape is what Opencode's OpenAI-compatible provider expects. - Point the routing rule at that one provider. Attach a rule with a single target: your provider key, 100%. A one-target rule is a legitimate rule — you are not configuring failover yet.
- Confirm the model id. List what your key can serve and pick an id from that output — don't copy one from a gateway's marketing page:
curl https://api.freerouter.com/v1/models \ -H "Authorization: Bearer $FREEROUTER_API_KEY" | head -c 2000
- Wire Opencode — dialog or file, below. Both end in the same place: a provider entry whose base URL is FreeRouter and whose model ids are the ones from step 5.
- Send a prompt and open Logs. Check
X-FreeRouter-Providernames your gateway, and look at the Router vs Upstream split on the log row.
Option A: the custom-provider dialog
Opencode's custom-provider form takes four things. Here is what each one means against FreeRouter:
- Base URL:
https://api.freerouter.com/v1. Opencode appends the endpoint paths itself. - API key: your
fr_live_key from the dashboard. Opencode sends it asAuthorization: Bearer, which is what FreeRouter expects. - Models: one row per model. The
model-idmust be an id from step 5 — that string is what FreeRouter routes on. Display Name is free text, e.g.Kimi K2.7 Code via FreeRouter. - Headers: leave empty. Only fill this in if you left API key blank and manage auth yourself.
Option B: opencode.json
Opencode documents custom providers under Providers → Custom provider. The flow is: run /connect, choose Other, enter a provider id (we use freerouter below), and store the credential — then describe that same id in opencode.json in your project directory, and pick the model with /models. The id in /connect must match the id in the file; opencode auth list shows what is stored.
Before the file, sanity-check the key with the request Opencode will send. Same body you'd send any OpenAI-compatible gateway:
export FREEROUTER_API_KEY=fr_live_…
curl https://api.freerouter.com/v1/chat/completions \
-H "Authorization: Bearer $FREEROUTER_API_KEY" \
-H "Content-Type: application/json" \
-i \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [{ "role": "user", "content": "Ping" }],
"temperature": 0.2
}'The body is an ordinary chat completion: { id, model, choices, usage }. The -i is there so you can read the routing headers — X-FreeRouter-Provider (who served it), X-FreeRouter-Attempts (how many targets were tried, 1 today), and X-FreeRouter-Request-Id (quote this when you ask us anything).
Then the file. Field for field against Opencode's custom-provider reference: npm selects the AI SDK package (@ai-sdk/openai-compatible for /v1/chat/completions; a model that uses /v1/responses takes @ai-sdk/openai, overridable per model), name is the display name, options.baseURL is the endpoint, options.apiKey reads from the environment so no live key is committed, and models maps each routed model id to its display name:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"freerouter": {
"npm": "@ai-sdk/openai-compatible",
"name": "FreeRouter",
"options": {
"baseURL": "https://api.freerouter.com/v1",
"apiKey": "{env:FREEROUTER_API_KEY}"
},
"models": {
"kimi-k2p7-code": {
"name": "Kimi K2.7 Code via FreeRouter"
}
}
}
}
}Two details worth knowing from the same reference: a per-model limit block (context / output token counts) tells Opencode how much context remains — stock providers pull those from models.dev automatically, custom ones don't — and options.headers takes custom headers if you ever need them. Neither is required to start.
flowchart TD
A["Sign up, land in workspace"] --> B["Add gateway key as provider"]
B --> C["Create fr_live_ key, openai shape"]
C --> D["Rule: 100 percent to that provider"]
D --> E["GET /v1/models, pick model id"]
E --> F["Dialog or opencode.json"]
F --> G["/models, prompt, check Logs"]
Seven steps, one config value in the editor. Everything after F is dashboard work.
What the setup buys later
A month later the change you want is small: a new coding model is fastest on a gateway you don't use yet. Add that gateway's key, add a remap from the id Opencode sends at percent: 20, watch the Logs split, take it to 100 or delete it. Opencode's config never changed.
The same holds for reliability: drop a second provider under the first in a priority rule and failover starts working for every model on the key, editor included. If the argument for putting the router in front of a single gateway first isn't obvious yet, the one-gateway post makes it at length.
Limits and failure modes
- One target means no failover. With a single gateway behind the key there is nothing to fall over to — add a second target before you need it.
- Billing stays with your providers. FreeRouter is BYOK: inference is charged to your gateway accounts, and routing itself is free during the preview. It doesn't make models cheaper.
- The hop costs something. Overhead is sub-1 ms on a simple route and 20–30 ms with remaps, translation, or failover drain — read your own number per request in the Router vs Upstream split.
- Chaining needs a chain-capable gateway.
previous_response_idis honored by pinning the follow-up to the originating provider — but only Ramp Router, Cloudflare, and LLM Gateway can anchor a chain. Point the key at one of those if your tool chains turns; otherwise resend the fullinputeach turn.conversationis rejected outright. - The Playground and Test button don't go through the inference API. They call providers from the dashboard, don't evaluate remaps, and are never written to Logs — verify editor traffic with real requests.
- FreeRouter doesn't pick your model. If the id in
opencode.jsonis wrong for the job, routing delivers the wrong answer faster. Check it againstGET /v1/modelsfirst.
Next steps
Create a key, point Opencode at it through the dialog or the file, send one prompt and read the log row. If the provider split looks right, add the second gateway — that is when the router starts paying.