Run the Claude Agent SDK on Router One with the two Claude Code variables
The Claude Agent SDK embeds Claude Code's agent loop in Python and TypeScript applications: it spawns the bundled Claude Code engine, which reads files, runs commands, and calls tools on your machine while sending each model turn as an Anthropic Messages request. Point that engine at Router One with the same two variables Claude Code uses, passed through the SDK's env option or the shell, and every turn appears as a POST /v1/messages trace in Dashboard → Logs with tokens, cost, and latency. This guide installs the Python SDK, runs one text-only query with an exact Claude-family model ID, explains which options decide what reaches the gateway, and shows how max_turns and a per-key maxSpend bound a multi-turn run.
Install the Python SDK and set your credentials
Use Python 3.10 or newer and install claude-agent-sdk; the wheel bundles a native Claude Code binary, so no separate Claude Code install is needed. If pip installs the source distribution instead of a platform wheel (the official docs name ARM64 Windows), install Claude Code natively and the SDK finds it on PATH. The TypeScript package is @anthropic-ai/claude-agent-sdk on Node.js 18 or newer. The shell example below is for macOS/Linux; replace both placeholders with a Router One key and the exact ID of a current Claude-family model whose detail page lists POST /v1/messages. These ROUTER_ONE_* names belong to the example, which reads them and hands the engine ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN; exporting those two directly, as the Claude Code guide does, works the same way because the Python SDK merges env on top of the inherited environment. The SDK does not load .env files, so keep the same environment active when running the Python file.
python -m pip install claude-agent-sdk export ROUTER_ONE_API_KEY="sk-your-router-one-key" export ROUTER_ONE_MODEL_ID="<exact-model-id-from-/models>"
Configure Claude Agent SDK to use the Router One base URL
Save this as claude_agent_sdk_router_one.py and run python claude_agent_sdk_router_one.py. query() starts the bundled Claude Code engine as a subprocess and yields its messages. The env option is merged on top of the inherited environment: ANTHROPIC_BASE_URL is the host root https://api.router.one without /v1, because the engine appends /v1/messages itself, and ANTHROPIC_AUTH_TOKEN carries your Router One key as an Authorization: Bearer header, so every model turn is an Anthropic Messages request to the gateway. ANTHROPIC_API_KEY is not required and is deliberately absent. model= passes the catalog ID unchanged to the engine's --model flag; an unset model falls back to Claude Code's default alias, which resolves to a built-in default ID that may not be a catalog entry. max_turns=3 caps tool-use round trips. permission_mode is left unset and no can_use_tool callback is given, so a tool request would be denied rather than executed; the first run is model requests only. The loop prints every TextBlock from each AssistantMessage, then the ResultMessage subtype and num_turns.
import asyncio
import os
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
ResultMessage,
TextBlock,
query,
)
options = ClaudeAgentOptions(
model=os.environ["ROUTER_ONE_MODEL_ID"],
env={
"ANTHROPIC_BASE_URL": "https://api.router.one",
"ANTHROPIC_AUTH_TOKEN": os.environ["ROUTER_ONE_API_KEY"],
},
max_turns=3,
)
async def main() -> None:
prompt = "Reply with one short greeting."
async for message in query(prompt=prompt, options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
elif isinstance(message, ResultMessage):
print(f"{message.subtype}: {message.num_turns} turn(s)")
asyncio.run(main())Options that decide what reaches the gateway
Most ClaudeAgentOptions fields shape what the engine does locally. These are the ones that change the requests Router One receives; TypeScript uses the camelCase twins on its Options object, and one of them behaves differently between the two SDKs.
| Option (Python / TypeScript) | What it does | What to verify on Router One |
|---|---|---|
| env / env | Variables for the spawned Claude Code process. Python merges them on top of the inherited environment; TypeScript replaces the environment entirely, so spread process.env into it | ANTHROPIC_BASE_URL is the host root without /v1 and ANTHROPIC_AUTH_TOKEN is your Router One key; an env block in a Claude Code settings file overrides both (see the FAQ) |
| model / model | Alias or full model name handed to the engine as --model; unset means Claude Code's default, and aliases resolve to built-in default IDs | An exact ID from /models whose detail page lists POST /v1/messages; a GPT-family ID is rejected with 400 must be called via before any model runs |
| max_turns / maxTurns | Maximum tool-use round trips; the run ends with a ResultMessage of subtype error_max_turns | Each turn is at least one Messages request, so one query() produces several traces in Logs, more with subagents |
| max_budget_usd / maxBudgetUsd | Stops the query when the SDK's client-side cost estimate reaches the value (subtype error_max_budget_usd) | The estimate comes from a price table bundled with the SDK, not from your Router One rate; the cap that actually stops spend is maxSpend on the key |
| permission_mode / permissionMode with allowed_tools / allowedTools | Which tool calls run without prompting; default mode with no callback denies them | Nothing at the gateway: tools run on your machine, and Router One only records the Messages requests around them |
Budget one query and reconcile its turns in Logs
One query() call is a sequence of Messages requests: at least one per turn, more when the engine spawns subagents, plus the background requests the Claude Code engine documents. max_turns bounds tool-use round trips, not spend, and max_budget_usd compares a list-price estimate that the official docs say can drift and must not drive financial decisions. Give the agent its own Router One key with maxSpend, then read the run in Dashboard → Logs: filter by that key and the exact model, match traces to the run's time window and to num_turns on the ResultMessage, and keep the request_id of any failed trace. total_cost_usd on the result is the SDK's estimate; the cost the gateway records per trace is what you are charged. Router One serves and records the model requests only: tool execution, hooks, permission checks, sessions, and subagents run in the SDK on your machine or container.
Which model ID should Claude Agent SDK 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 Claude Agent SDK. 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 Claude Agent SDK 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 Claude Agent SDK call in your request trace
Send a simple text request from Claude Agent SDK, 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
Which model IDs can the SDK send through Router One, and what does 400 must be called via mean?
The engine speaks the Anthropic Messages format, so every turn goes to POST /v1/messages, which serves the currently listed Claude-family IDs; each model detail page under /models lists its endpoints. Pass one of those exact IDs in model, not a display name or an alias such as sonnet, because aliases resolve client-side to Anthropic's built-in default IDs. The endpoint may list other families too, but Claude Code's gateway documentation states that Anthropic does not support routing it to non-Claude models, so keep the SDK on a Claude-family ID. If you set a GPT-family ID, the gateway answers 400 invalid_request_error with model '<id>' must be called via … before any model is called; change the ID, not the base URL.
I already use Claude Code with a claude.ai login or an official API key. How do these variables interact?
Claude Code's gateway documentation says a gateway credential variable takes precedence over a saved claude.ai login: with ANTHROPIC_AUTH_TOKEN set, the login stays saved and unused for that process, and its usage limits and billing do not apply. Setting ANTHROPIC_BASE_URL alone does not replace the login; requests still go to the gateway but without your Router One key, so check this first if the first request fails with 401. ANTHROPIC_API_KEY is not required; a leftover value from an official-API setup is sent as a second header (x-api-key) next to the bearer token, so unset it or keep it out of env. One more source of conflict: an env block in a Claude Code settings file replaces values inherited from the shell, and default query() options load user, project, and local settings, so remove an old ANTHROPIC_BASE_URL entry there or pass setting_sources=[] for this agent.
Logs shows a request for a model I never set. Where does it come from?
Two engine features send their own requests. Claude Code documents that the model behind the haiku alias is also used for background functionality, such as summarizing conversations for resume, and that an alias resolves to a built-in default ID; through Router One that request carries the default ID, which may not be a catalog entry. Set ANTHROPIC_DEFAULT_HAIKU_MODEL in env to a currently listed Claude-family ID served on /v1/messages so those requests use a catalog model and are traced and charged like the rest. Subagents likewise send their own requests under the model assigned to them or the CLAUDE_CODE_SUBAGENT_MODEL default. If a trace for an unexpected ID failed, keep its request_id and error message, then pin the variable rather than changing the base URL.
Which models can Claude Agent SDK use through the gateway?
Choose a current catalog model that supports both the endpoint and the features Claude Agent SDK 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.