Connect Qwen Code to Router One with one .qwen/.env file or a modelProviders entry
Qwen Code is the Qwen team's open-source terminal coding agent: it reads and edits files, runs shell commands and loops through tool calls, and since v0.1 it has developed independently of the Gemini CLI it was based on. Its openai auth type sends Chat Completions through the official OpenAI Node.js SDK, so pointing it at Router One reaches every chat model served on /v1/chat/completions, the GPT, Claude, Gemini, Grok and DeepSeek families on one key, with a cost and latency trace per model turn in Dashboard → Logs, while the agent loop, tool execution, approvals and sessions stay in Qwen Code. The gateway is reachable from Mainland China without a VPN. This guide covers the three-variable .qwen/.env form, the modelProviders entry that fills the /model picker, which value applies when both exist, and how to budget a session.
Install Qwen Code and create a dedicated key
The npm install needs Node.js 22 or newer; the README also lists a standalone install script for Linux, macOS and Windows and a Homebrew formula. Install, open a terminal in the project and start qwen. The first run opens the /auth menu, whose Custom Provider option covers OpenAI-compatible endpoints, but the two files in this guide configure the same thing without the menu; /doctor shows the authentication and environment checks at any time. Before that, create a Router One key for Qwen Code alone with a maxSpend cap: an agent session sends one request per model turn and per tool-result continuation, so the cap is the hard stop. The key goes into .qwen/.env below, never into settings.json.
npm install -g @qwen-code/qwen-code@latest cd /path/to/your-project qwen
Configure Qwen Code to use the Router One base URL
Create .qwen/.env in the project root with the three variables below; it is the file the Qwen Code docs prefer for project-local secrets, and it must stay out of git. OPENAI_API_KEY is the Router One key, and the authentication docs state that exporting OPENAI_API_KEY is what selects OpenAI-compatible auth from environment variables alone; provider-specific variable names do not. OPENAI_BASE_URL takes https://api.router.one/v1 with /v1: the openai auth type builds an official OpenAI Node.js SDK client from this value, and the SDK posts to /chat/completions relative to it, so each model turn is one POST /v1/chat/completions. OPENAI_MODEL (alias QWEN_MODEL) is the exact catalog ID and becomes the model field of every request; --model <id> overrides it for one session. Qwen Code loads only the first .env file it finds, .qwen/.env then .env walking up from the current directory, then ~/.qwen/.env and ~/.env, and it never overwrites a variable already exported in the shell, so a stale OPENAI_BASE_URL in ~/.zshrc wins over this file. This form creates what the docs call a Runtime Model: one model and no picker entries; if qwen still opens the /auth menu, add security.auth.selectedType as in the next section. Start qwen, run /doctor, send one short prompt, and match its trace in Logs. The file, with the key and the model ID replaced:
# <project>/.qwen/.env — the first .env file Qwen Code finds; keep it out of git OPENAI_API_KEY=sk-your-router-one-key OPENAI_BASE_URL=https://api.router.one/v1 OPENAI_MODEL=<exact-model-id-from-/models>
Register Router One in modelProviders for the /model picker
To switch between several catalog models inside a session, declare them in ~/.qwen/settings.json, the user scope the docs recommend, because project settings replace the whole modelProviders object instead of merging it. The key openai is the auth type and fixes the transport; a misspelled key such as openai-custom is skipped with a warning and its models never appear in /model. Each entry needs id, the exact catalog ID sent as the model field; name is only the picker label; envKey names the environment variable that holds the key, since the runtime reads process.env[envKey] and never stores the credential, so put ROUTER_ONE_API_KEY=sk-your-router-one-key in .qwen/.env or ~/.qwen/.env, or export it; baseUrl is the same /v1 URL, and within one auth type entries are identified by id plus baseUrl, so a duplicate pair is skipped. generationConfig is optional and sealed: when a provider model is selected its values apply atomically and top-level model.generationConfig is ignored; in the source, timeout defaults to 120000 ms and maxRetries to 3. security.auth.selectedType set to openai starts qwen without the /auth menu, and model.name must equal one of the ids. A running session picks up edits to modelProviders (reopen /model), and /model and /auth write model.name and selectedType back to the scope that defines modelProviders. When a provider entry is selected, its baseUrl and envKey outrank --openai-base-url, OPENAI_BASE_URL and OPENAI_API_KEY.
{
"modelProviders": {
"openai": [
{
"id": "<exact-model-id-from-/models>",
"name": "<label shown in /model>",
"envKey": "ROUTER_ONE_API_KEY",
"baseUrl": "https://api.router.one/v1",
"generationConfig": { "timeout": 120000, "maxRetries": 3 }
},
{
"id": "<another-model-id-from-/models>",
"envKey": "ROUTER_ONE_API_KEY",
"baseUrl": "https://api.router.one/v1"
}
]
},
"security": { "auth": { "selectedType": "openai" } },
"model": { "name": "<exact-model-id-from-/models>" }
}Which value goes where, and how to verify it
Everything Router One needs is three variables or one provider entry; the rows below give each value and the check that proves it took effect. The quickest view of the actual request is Qwen Code's own log: model.enableOpenAILogging, or the --openai-logging flag, writes each request and response as JSON under logs/openai in the working directory.
| Qwen Code field | Value | What to verify |
|---|---|---|
| .qwen/.env → OPENAI_BASE_URL, or modelProviders.openai[].baseUrl | https://api.router.one/v1 | A 404 not_found whose message asks for a base URL ending in /v1 means /v1 is missing; with logging on, the JSON under logs/openai shows the exact URL |
| .qwen/.env → OPENAI_API_KEY, or the variable named by envKey | A Router One key created for Qwen Code, with maxSpend | The first request appears in Logs under that key; a 401 means another value won, since a shell export beats .env and only the first .env file found is loaded |
| .qwen/.env → OPENAI_MODEL (alias QWEN_MODEL), --model, or modelProviders.openai[].id and model.name | The exact catalog ID | The model in each Logs trace matches /models character for character; model.name must equal one of the ids |
| modelProviders key | openai | Entries appear in /model grouped under openai; an unknown key is skipped with a warning and its models never appear |
| security.auth.selectedType | openai | qwen starts without the /auth menu; /doctor reports the authentication checks; /model and /auth write this field back |
| modelProviders → openai-responses (optional) | Only IDs whose model page lists POST /v1/responses; the transport strips a trailing /v1 from baseUrl and appends /v1/responses itself | The model page lists POST /v1/responses; any other ID gets 400 must be called via before a model runs |
| model.enableOpenAILogging, or --openai-logging | true while debugging | JSON files under logs/openai show the exact URL, model and body of each request next to its Logs trace |
Budget a Qwen Code session and reconcile it with Logs
One prompt in Qwen Code is rarely one request. Each model turn is a request, every tool result the agent feeds back is another turn, model.maxToolCallsPerTurn caps a turn at 100 tool calls by default, adaptively, with a hard backstop of 1000, and side models chosen with /model --fast, --compaction or --vision send their own requests. The openai transport also passes timeout (default 120000 ms) and maxRetries (default 3) to the OpenAI SDK, and every attempt that reaches the gateway is its own request with its own request_id and charge. Give Qwen Code a dedicated key with maxSpend: a session that hits the cap gets HTTP 402 and stops, while the wallet and other keys are untouched. Client-side, model.maxSessionTurns and model.sessionTokenLimit bound the conversation, and a stream the client cancels is recorded as HTTP 499 client_cancelled and billed only for the usage reported. Reconcile with /stats model, which shows Qwen Code's per-model token breakdown and its own cost estimate; Dashboard → Logs is the charge, per request, with model, tokens, cost, latency, status and request_id, and /stats export writes the client side to CSV or JSON. Router One records model-call metadata only; tools, approvals, sessions and subagents run in Qwen Code.
Which model ID should Qwen Code send?
Copy the exact model ID from /models, preserving case, hyphens, and version suffixes; do not substitute a display name. Open its detail page and match the supported API endpoints, context window, and capabilities such as tool calling to the provider and features selected in Qwen Code. A catalog listing does not mean the client can use every feature of that model. Give each tool a dedicated API key with a maxSpend cap.
Which API protocol is Qwen Code using?
OpenAI-compatible describes an interface format; it does not make Chat Completions (/v1/chat/completions), Responses (/v1/responses), and Anthropic Messages (/v1/messages) interchangeable. Check the installed client version, provider configuration, and actual request path against the model detail page and API compatibility fact sheet. A successful plain-text chat does not establish support for hosted tools, conversation state, or file-editing features.
Verify the Qwen Code call in your request trace
Send a simple text request from Qwen Code, then match its trace in Dashboard → Logs by time, model, and request_id: tokens, cost, latency, and status. Next, test streaming, tool calls, and multi-turn history separately. For failures, retain the actual request path, full error message, and request_id. If there is no matching log, check client configuration and connectivity before attributing the error to the gateway or upstream.
FAQ
Qwen Code starts, but the first request fails with 404 or 401. Which base URL and key are actually in use?
The value that applies is decided per field, highest layer first: what /auth was given, then the selected modelProviders entry (its baseUrl and env[envKey]), then the CLI flags --openai-base-url and --openai-api-key, then OPENAI_BASE_URL and OPENAI_API_KEY from the environment, where a shell export beats .env and only the first .env file found is loaded, and last the deprecated security.auth.baseUrl and security.auth.apiKey. For a 404, read the error message in Logs and the request URL in Qwen Code's logs/openai JSON: the openai auth type posts to /chat/completions under the base URL, so a base URL without /v1 produces a 404 not_found whose message asks for a base URL ending in /v1; a 404 on the correct path points at the model ID or a missing resource, so check the ID against /models. For a 401, the key that reached the gateway was not a valid Router One key: check which variable envKey names, since it holds a variable name and not the key, and whether a shell export or an earlier .env file supplied another value. Turn on model.enableOpenAILogging to see the exact URL and model of each request, and keep the request_id from Logs when reporting.
Should the Router One entry use the openai or the openai-responses auth type?
Use openai. It sends Chat Completions through the OpenAI Node.js SDK, and /v1/chat/completions serves every chat model in the catalog, so one entry shape works for any current ID. openai-responses is a separate auth type with its own transport, direct HTTP/SSE calls to /v1/responses with no SDK, built to replay reasoning across turns via reasoning.encrypted_content and controlled with reasoning.effort rather than extra_body.enable_thinking. Its pipeline strips a trailing /v1 from baseUrl and appends /v1/responses itself, so https://api.router.one/v1 and https://api.router.one resolve to the same URL. The gateway serves /v1/responses natively only for the currently listed GPT-family and DeepSeek IDs; any other ID sent there gets HTTP 400 invalid_request_error with model '<id>' must be called via … before any model runs. Keep the two auth types as separate keys in modelProviders, and put a model under openai-responses only when its detail page lists POST /v1/responses.
Which models can Qwen Code use through the gateway?
Choose a current catalog model that supports both the endpoint and the features Qwen Code uses. Check /models and the model detail page for the exact ID, current rates, and capabilities; a family name such as GPT or Claude is not a compatibility guarantee. Seeing a model in the picker confirms discovery, so verify an actual request too.
Models are listed, but requests fail with 400 or 404. What should I check?
Record the actual request path and error message, then check the exact model ID. A 400 can indicate invalid parameters, unsupported tools, or a model/endpoint mismatch; a 404 can indicate an incorrect path or missing resource, so it does not by itself establish that a model was retired. If the error says must be called via, use the named endpoint or select a model supported on the current endpoint. Do not add or remove /v1 or /chat/completions across all clients indiscriminately.
Does this work from Mainland China?
Yes. The gateway is reachable from Mainland China without a VPN, and the configuration is identical to the global setup.
How do I debug a 401/402/403/429?
Match the request and error message in Dashboard → Logs. For 401, check whether the key was sent and is valid; for 402, check wallet balance and maxSpend; for 403, check key permissions and access restrictions. For 429, distinguish request/token limits from upstream throttling using the error details. Keep the request_id and follow the error-codes reference.