# Migrate

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`, and
  `POST https://demo-api.routerplus.com/v1/responses` for 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](/docs/api-images). The translation rules are on [Wire compatibility](/docs/compat).

Every route accepts the key as `Authorization: Bearer $TM_API_KEY` or `x-api-key: $TM_API_KEY`.

> [!NOTE]
> To get a key, open [Sign up](https://demo.routerplus.com/signup), complete Clerk authentication
> and email verification, and save the first key shown after sign-in. Buy credits
> in [Billing](https://demo.routerplus.com/console/billing). Signup does not add free credit. If you have an account,
> [sign in](https://demo.routerplus.com/login) with the same verified email and create a key at
> [API keys](https://demo.routerplus.com/console/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.

```diff
 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"],
 )
```

```diff
 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:

```python
client.chat.completions.create(
    model="claude-sonnet-4-5",  # Anthropic model, OpenAI format
    stream=True,
    messages=[{"role": "user", "content": "hello"}],
)
```

> [!NOTE]
> Output limits:
> - On the chat APIs, a request with no `max_tokens` or `max_completion_tokens` gets 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_tokens` on the chat APIs, `max_output_tokens` on 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](/docs/api-responses).

**Codex** (the CLI, the desktop app and the IDE extension) calls the Responses API. Add a provider block to `~/.codex/config.toml`:

```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](/docs/install#codex).

## 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`.

```diff
 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:

```bash
ANTHROPIC_BASE_URL=https://demo-api.routerplus.com ANTHROPIC_AUTH_TOKEN=$TM_API_KEY claude
```

- RouterPlus forwards your `anthropic-beta` header 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](/docs/authentication).
- The `x-tm-dropped-params` header names each dropped value.
- Claude Code's `?beta=true` query 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](/docs/install#output-limit-auto-mode-and-the-advisor).

## From another gateway

If your code calls another multi-provider gateway in the OpenAI format, change the client as in [From the OpenAI SDK](#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.

```diff
 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](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](/docs/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](/docs/routing-policies). `sort` takes `"price"`, `"throughput"`, `"latency"` or `"quality"`. Other keys return a 400. See [Provider routing](/docs/provider-routing) |

> [!WARNING]
> 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](/docs/compat).

## Migrate with one prompt

Paste this into your coding agent:

```text
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:

```bash
curl -N https://demo-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:

```bash
curl -s -H "Authorization: Bearer $TM_API_KEY" https://demo-api.routerplus.com/v1/usage
```

Use the response headers to confirm that traffic moved:

- `x-tm-provider` names the deployment that served the request.
- `x-tm-attempts` counts the dispatches.

Every error body has a stable `error_type`. The fixes are on [Errors](/docs/errors).
