Migrate
Move from OpenAI, Anthropic or another gateway.
Most migrations change the base URL and the API key. If you come from another gateway, you also change the model ids. You keep your SDK, because RouterPlus serves both common API formats:
- OpenAI-compatible:
POST https://demo-api.routerplus.com/v1/chat/completions, andPOST https://demo-api.routerplus.com/v1/responsesfor OpenAI's Responses API - Anthropic-compatible:
POST https://demo-api.routerplus.com/v1/messages
Every chat model works on both APIs. RouterPlus translates requests, streams and errors in both directions. Image models use POST /v1/images/generations. The translation rules are on Wire compatibility.
Every route accepts the key as Authorization: Bearer $TM_API_KEY or x-api-key: $TM_API_KEY.
To get a key, open Sign up, complete Clerk authentication and email verification, and save the first key shown after sign-in. Buy credits in Billing. Signup does not add free credit. If you have an account, sign in with the same verified email and create a key at API keys. There is no programmatic signup route.
From the OpenAI SDK
Change two lines. gpt-* model ids are the provider's own ids (gpt-4o-mini, gpt-4.1-mini, gpt-4o), so your model strings usually stay the same.
from openai import OpenAI
client = OpenAI(
- api_key=os.environ["OPENAI_API_KEY"],
+ base_url="https://demo-api.routerplus.com/v1",
+ api_key=os.environ["TM_API_KEY"],
) import OpenAI from "openai";
const client = new OpenAI({
- apiKey: process.env.OPENAI_API_KEY,
+ baseURL: "https://demo-api.routerplus.com/v1",
+ apiKey: process.env.TM_API_KEY,
});The same client can call Claude models:
client.chat.completions.create(
model="claude-sonnet-4-5", # Anthropic model, OpenAI format
stream=True,
messages=[{"role": "user", "content": "hello"}],
)Output limits:
- On the chat APIs, a request with no
max_tokensormax_completion_tokensgets the model's own maximum output. RouterPlus lowers it when needed so the input and output fit the model's context window. - On the Responses API, RouterPlus adds no limit, and OpenAI applies the model's own maximum.
- While such a request runs, the balance hold counts at most 32,768 output tokens. You pay for the real usage.
- A limit that you send (
max_tokenson the chat APIs,max_output_tokenson the Responses API) is held in full.
Code on OpenAI's Responses API (client.responses.create) works with the same base URL for OpenAI's models. Claude and the other chat models are a preview there. See POST /v1/responses.
Codex (the CLI, the desktop app and the IDE extension) calls the Responses API. Add a provider block to ~/.codex/config.toml:
model = "gpt-6-luna"
model_provider = "routerplus"
[model_providers.routerplus]
name = "RouterPlus"
base_url = "https://demo-api.routerplus.com/v1"
env_key = "TM_API_KEY"
wire_api = "responses"Then run codex with TM_API_KEY set. For the desktop app, which needs the key in the file, see Coding agents.
From the Anthropic SDK
Change the base URL and the key. The Anthropic-compatible API is at the gateway root. Do not add /v1 to base_url: the SDK appends /v1/messages.
from anthropic import Anthropic
client = Anthropic(
- api_key=os.environ["ANTHROPIC_API_KEY"],
+ base_url="https://demo-api.routerplus.com",
+ api_key=os.environ["TM_API_KEY"],
)Claude model ids are Anthropic's own (claude-sonnet-4-5, claude-haiku-4-5, dated snapshots included). This SDK can also call GPT models on the same /v1/messages endpoint.
Claude Code needs environment variables only:
ANTHROPIC_BASE_URL=https://dev2-api.routerplus.com ANTHROPIC_AUTH_TOKEN=$TM_API_KEY claude- RouterPlus forwards your
anthropic-betaheader when an Anthropic-format provider serves the request. - It keeps only allowlisted betas that do not change the price of a token:
prompt-caching,claude-code,interleaved-thinking, and the values Claude Code needs on the Claude 5 models. The full list is on Authentication. - The
x-tm-dropped-paramsheader names each dropped value. - Claude Code's
?beta=truequery string is accepted and routes normally. RouterPlus does not forward query strings to the provider.
For Claude Code's output limit, auto mode and advisor, see Coding agents.
From another gateway
If your code calls another multi-provider gateway in the OpenAI format, change the client as in From the OpenAI SDK. Then change the model ids. Usage comes in the last stream chunk with a cost field. The optional HTTP-Referer and X-Title attribution headers also work.
completion = client.chat.completions.create(
- model="anthropic/claude-sonnet-4.5",
+ model="claude-sonnet-4-5",
stream=True,
messages=[{"role": "user", "content": "hello"}],
)| Before | RouterPlus |
|---|---|
author/model slugs (anthropic/claude-sonnet-4.5) | Provider ids (claude-sonnet-4-5). The full list is at https://demo.routerplus.com/api/models.json |
Variant suffixes (model:variant) | None. An id with a suffix is not in the catalog and returns 404 |
usage.cost in credits | usage.cost in USD, computed with the ledger's integer math. It covers the full request debit, including attempts that failed over |
| A usage lookup by generation id | GET https://demo-api.routerplus.com/v1/generation?id=REQUEST_ID returns every attempt with its tokens, outcome and settled cost |
| Out of credits: HTTP 402 | HTTP 429 insufficient_quota, the response OpenAI's SDKs already handle |
A models fallback array with route: "fallback" | Supported: up to five models, tried in order. See Model fallback |
transforms | Dropped. The x-tm-dropped-params header names it. Remove it |
A provider object | Supported keys: order, only, ignore, allow_fallbacks, require_parameters, sort, max_price, quantizations, preferred_min_throughput, preferred_max_latency, zdr, data_collection, enforce_distillable_text, and the connection controls on Routing policies. sort takes "price", "throughput", "latency" or "quality". Other keys return a 400. See Provider routing |
RouterPlus never substitutes a model. An id that is not in the catalog returns a 404 that names the id you sent. Check every migrated model id against https://demo.routerplus.com/api/models.json before you ship.
Failover never switches providers in the middle of an answer. If a provider fails after output starts, you get the tokens streamed so far and one terminal error event. You decide whether to retry. See Wire compatibility.
Migrate with one prompt
Paste this into your coding agent:
Migrate this project to RouterPlus.
1. If TM_API_KEY is not set, have me open https://demo.routerplus.com/signup and complete
Clerk authentication and email verification in the browser. Existing customers
sign in at https://demo.routerplus.com/login and create a key at https://demo.routerplus.com/console/keys.
Save the key as TM_API_KEY (env var and .env; never commit it). Continue once
the key is available; there is no programmatic signup endpoint.
2. Find every LLM client in this codebase and repoint it:
- OpenAI SDK / any OpenAI-compatible client:
base_url -> "https://demo-api.routerplus.com/v1", api_key -> TM_API_KEY
- Anthropic SDK:
base_url -> "https://demo-api.routerplus.com", api key -> TM_API_KEY
- Claude Code:
run with ANTHROPIC_BASE_URL=https://demo-api.routerplus.com ANTHROPIC_AUTH_TOKEN=$TM_API_KEY
- Codex: add a [model_providers.routerplus] block to ~/.codex/config.toml with
base_url "https://demo-api.routerplus.com/v1", env_key "TM_API_KEY", wire_api "responses",
and set model_provider = "routerplus" and model = "gpt-6-luna".
- Clients of another gateway: also strip "author/" prefixes and ":variant"
suffixes from model ids, and remove the transforms request field.
Keep a "models" fallback list (at most five) with route "fallback".
Inside a "provider" object keep only: order, only, ignore, allow_fallbacks,
require_parameters, sort ("price", "throughput", "latency" or "quality"),
max_price (USD per million), quantizations, preferred_min_throughput,
preferred_max_latency, zdr, data_collection; remove the rest.
- Raw HTTP: change the host to https://demo-api.routerplus.com and the bearer/x-api-key to TM_API_KEY.
3. Check every model id against https://demo.routerplus.com/api/models.json and substitute the
closest listed id where needed. Never invent an id: unlisted models return 404.
4. Run one real streamed test call per repointed client and show me each response's
final usage object (it must contain "cost").
5. Keep the diff minimal. Do not refactor unrelated code.Verify the cutover
One streamed call tests auth, routing and billing:
curl -N https://dev2-api.routerplus.com/v1/chat/completions \
-H "Authorization: Bearer $TM_API_KEY" \
-H "content-type: application/json" \
-d '{"model":"claude-haiku-4-5","stream":true,"max_tokens":40,"messages":[{"role":"user","content":"Reply with exactly: MIGRATED"}]}'The last usage chunk must have cost (USD). Then check that the ledger recorded the call:
curl -s -H "Authorization: Bearer $TM_API_KEY" https://dev2-api.routerplus.com/v1/usageUse the response headers to confirm that traffic moved:
x-tm-providernames the deployment that served the request.x-tm-attemptscounts the dispatches.
Every error body has a stable error_type. The fixes are on Errors.
For agents: /docs/migration.md and /llms.txt