How do I migrate from OpenRouter to FluxRouter?
Point your OpenAI SDK at https://api.fluxrouter.ai/v1 instead of https://openrouter.ai/api/v1, swap your sk-or-... key for a FluxRouter sk-... key from the dashboard, and change your model string to flux-auto (or a flux-pinned-* id when you need a specific model). Endpoints, request JSON, streaming, and tool calls stay the same. Then run the eight-step cutover checklist below before you flip production traffic.
That's the whole migration. Three lines.
The reason to keep reading isn't the swap, it's everything around the swap: which model ids map to what, exactly what to test before cutover, and how your spend tracking changes (it does change, and if your code reads cost out of the response body today, you'll want the section below).
The canonical reference is the live migration doc. This guide goes deeper on the parts a docs page keeps short: the mapping table, the test plan, the spend-tracking diff.
Don't want to touch config files? Two easy buttons
Everything below this section is for engineers who want to see every moving part. You might not need any of it.
If your OpenRouter usage lives in your AI tools (Claude Code, Cursor, Cline and friends), Flux Desktop does the whole thing. Download it, sign in through your browser, and it detects the AI tools you already have installed and rewrites their configs to route through FluxRouter. It only touches the relevant block of each config, keeps a byte-for-byte copy of the original, and can restore it exactly as it was. That's your rollback, built in. Your key is created during sign-in and stored in your OS keychain, so you never paste one by hand. Around 20 tools are supported, and the docs call it best on macOS today.
If it lives in your application code, hand this page to an AI. Paste this into Claude, ChatGPT, or your coding agent:
Migrate my project from OpenRouter to FluxRouter. Before changing anything,
back up every file you touch (.env, config files) with a .bak copy so we can
roll back. Then: swap the base URL to https://api.fluxrouter.ai/v1, replace
the OpenRouter key with my FluxRouter key (env var FLUX_API_KEY), change the
model to flux-auto, and run one non-streaming test, one streaming test, and
one tool-call test. Keep my OpenRouter key active and do not delete anything.
Show me the diff before applying it.
That prompt carries its own rollback. The rest of this guide is the manual version of exactly what that prompt does, plus the verification an engineer would want.
The three-line diff
Both gateways speak the OpenAI API, so your SDK doesn't change and neither does your request JSON. Here's the same request on both, curl first.
Before, on OpenRouter (per OpenRouter's docs as of July 2026, chat lives at /api/v1/chat/completions):
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4.5",
"messages": [{"role": "user", "content": "Say hello in five words."}]
}'
After, on FluxRouter:
curl https://api.fluxrouter.ai/v1/chat/completions \
-H "Authorization: Bearer $FLUX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "flux-auto",
"messages": [{"role": "user", "content": "Say hello in five words."}]
}'
Base URL, key, model string. Nothing else moved. The auth header shape is identical on both sides: Authorization: Bearer <key>. Your FluxRouter key looks like sk-... and comes from the dashboard.
One honest note before you copy-paste that: the example swaps a pinned frontier model for flux-auto, which is dynamic. Different requests can land on different models. If you pinned Sonnet on OpenRouter because your prompts are tuned to Sonnet, the behavior-preserving move is a flux-pinned-* id (mapping table below), and you graduate to flux-auto once you've seen it work on your traffic.
Python, OpenAI SDK:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.fluxrouter.ai/v1", # was: https://openrouter.ai/api/v1
api_key=os.environ["FLUX_API_KEY"], # was: OPENROUTER_API_KEY
)
resp = client.chat.completions.create(
model="flux-auto", # was: a vendor/model slug
messages=[{"role": "user", "content": "Say hello in five words."}],
)
print(resp.choices[0].message.content)
Node, OpenAI SDK:
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.fluxrouter.ai/v1", // was: https://openrouter.ai/api/v1
apiKey: process.env.FLUX_API_KEY, // was: OPENROUTER_API_KEY
});
const resp = await client.chat.completions.create({
model: "flux-auto", // was: a vendor/model slug
messages: [{ role: "user", content: "Say hello in five words." }],
});
console.log(resp.choices[0].message.content);
Two cleanup notes while you're in there:
- If you're on the Anthropic SDK instead of the OpenAI one, that path works too: set
base_urltohttps://api.fluxrouter.ai/anthropicand keep your Anthropic-SDK code as is. - Per OpenRouter's docs as of July 2026, OpenRouter supports optional attribution headers:
HTTP-RefererandX-OpenRouter-Title. FluxRouter has no equivalent. Grep for both, delete both.
Model names: the real work of the migration
This is the one place the two products think differently, so it's worth two minutes.
On OpenRouter, per their docs as of July 2026, you name a vendor/model slug on every request (anthropic/claude-sonnet-4.5, dynamic aliases like ~openai/gpt-latest, and so on), and you pull the catalog from GET /api/v1/models. And credit where it's due: OpenRouter has an auto option too, openrouter/auto, so if someone sold you the switch as "over there you pick every model by hand," that pitch is out of date. Both products will route for you.
The difference is the shape of the id scheme. FluxRouter gives you three layers:
flux-autoroutes each request to a well-suited model. What a given request bills is the pricing page's job to spell out, not this article's; read it before cutover.- Tier aliases (
flux-fast,flux-standard,flux-reasoning,flux-image) express intent, speed versus capability, without naming a specific model. flux-pinned-*ids give you a specific backing model every time, still metered through Flux.
The mapping, then:
| If on OpenRouter you were... | On FluxRouter use | Why |
|---|---|---|
Using openrouter/auto, or hand-picking a slug per request | flux-auto | The router picks per request |
| Reaching for the cheapest/fastest thing that works | flux-fast | Tier alias: speed intent, not a specific model |
| Using a sensible general-purpose default | flux-standard | Tier alias |
| Calling reasoning-class models | flux-reasoning | Tier alias |
| Calling image models | flux-image | Tier alias |
Pinned to a Claude Sonnet slug (e.g. anthropic/claude-sonnet-4.5) | flux-pinned-claude-sonnet | A consistent pinned Sonnet, possibly newer than the 4.5 you left; see the note below the table |
| Using Perplexity Sonar for search-grounded answers | flux-pinned-sonar, flux-pinned-sonar-pro, or flux-pinned-sonar-reasoning-pro | Search-grounded: answers from live web results |
| Pinned to anything else specific | Check GET /v1/models | The live list is the authority, not this table |
That last row matters more than it looks. There's a full catalog of pinned ids and I'm deliberately not printing it here, because a table in a blog post goes stale and the API doesn't:
curl -s https://api.fluxrouter.ai/v1/models \
-H "Authorization: Bearer $FLUX_API_KEY"
That's the authoritative, always-current list of ids your key can use. Run it once during migration and grep for the vendors you care about.
About that Sonnet row, because it's the one that bites the exact person who pins: flux-pinned-claude-sonnet is unversioned. It gives you the same backing model on every request, and that backing model can be a newer Sonnet than the one you were pinned to on OpenRouter. An upgrade is still a change. Check the id list from /v1/models, read X-Flux-Model on your first pinned request to see the exact version serving you, and re-run whatever evals you trust before calling that workload migrated.
One habit worth keeping from your OpenRouter days: trust, then check. FluxRouter stamps routing headers on every response. X-Flux-Routed: false is the router telling you it didn't reroute your request; treat it as the router's own signal, and pair it with X-Flux-Model when you want the name of the model that served. More on those headers in a second.
The default posture I'd migrate to: pin where you have a hard requirement, flux-auto everywhere else.
What doesn't change (and the two edge cases)
The endpoints your OpenRouter code already calls are here under the same paths: /v1/chat/completions and /v1/models. FluxRouter also serves /v1/responses, the OpenAI Responses API shape; that one is a FluxRouter surface rather than part of your migration diff, but if you have Responses-shaped code elsewhere you can consolidate it onto the same gateway. Request and response JSON are unchanged, structured outputs included.
Streaming. Set "stream": true and you get SSE (text/event-stream) with OpenAI-shape chat.completion.chunk chunks, terminated by data: [DONE]. Same contract as OpenAI and OpenRouter. One edge case: per OpenRouter's docs as of July 2026, their streams occasionally include SSE comment payloads your client is supposed to ignore. If your parser special-cases those, the code is harmless on FluxRouter, just no longer needed.
Tool calls. The standard OpenAI round trip: tools in the request, tool_calls in the response, results back as role: "tool". Nothing Flux-specific beyond the base URL and model id. One caveat straight from our own tools guide: tool support belongs to the backing model. flux-auto commonly lands on tool-capable models, but that's not a guarantee, so if tools are load-bearing in your product, pin a tool-capable model and test against the pin. Both gateways handle tool translation for non-OpenAI providers behind the scenes (OpenRouter describes passing tools as-is to OpenAI-interface providers and transforming for others, per their docs as of July 2026). The translation part, at least, is not your problem.
Spend tracking: the part that actually changes your code
Here's the one migration item that isn't a find-and-replace.
Per OpenRouter's docs as of July 2026: the response usage object carries prompt_tokens, completion_tokens, total_tokens, and can include a cost field; token counts come from each model's native tokenizer; streaming puts usage in the final chunk; and historical stats live behind GET /api/v1/generation?id=....
FluxRouter puts cost on the response too, but in the headers, not the body. Four transparency headers ride every response, and a fifth rides non-streaming responses only:
| Header | What it tells you |
|---|---|
X-Flux-Model | The model that served the request |
X-Flux-Original-Model | What you asked for |
X-Flux-Routed | The router's signal on whether it changed the model (false = no reroute) |
X-Flux-Request-Id | The support handle. Log it. |
X-Flux-Cost-Usd | Per-request cost in USD, on non-streaming responses only |
One caveat on that cost header, stated plainly because this is a migration guide and your spend dashboards will care: search-grounded and other capability-priced models may bill capability units separately from token cost. The dashboard and invoice are the system of record; the header is the fast signal.
Side by side:
| OpenRouter (per their docs, July 2026) | FluxRouter | |
|---|---|---|
| Per-request cost, non-streaming | Optional cost field in the body's usage object | usage.cost_usd in the body, plus the X-Flux-Cost-Usd header carrying the same number |
| Per-request cost, streaming | Usage in the final SSE chunk | Final SSE chunk too: set stream_options: {"include_usage": true} and the closing usage object carries cost_usd and currency |
| Historical lookup | GET /api/v1/generation?id=... | Dashboard at /home/usage |
| Which model actually answered | model field in the response body | X-Flux-Model + X-Flux-Original-Model headers |
| Support handle per request | id field in the response body | X-Flux-Request-Id header |
The migration action: if your code reads response.usage.cost from OpenRouter, the near-1:1 swap is response.usage.cost_usd on FluxRouter, in both streaming and non-streaming responses. Prefer headers? X-Flux-Cost-Usd carries the same number on non-streaming calls; in the Python SDK that means with_raw_response:
raw = client.chat.completions.with_raw_response.create(
model="flux-auto",
messages=[{"role": "user", "content": "Say hello in five words."}],
)
cost_usd = raw.headers.get("x-flux-cost-usd")
resp = raw.parse() # the normal completion object
print(cost_usd, resp.choices[0].message.content)
Same move in Node, where the SDK's .withResponse() hands you the raw fetch Response alongside the parsed object:
const { data: resp, response } = await client.chat.completions
.create({
model: "flux-auto",
messages: [{ role: "user", content: "Say hello in five words." }],
})
.withResponse();
console.log(
response.headers.get("x-flux-cost-usd"),
resp.choices[0].message.content
);
For the monthly view: usage lives at /home/usage in the dashboard, invoices and payment at /home/billing (billing runs through Stripe). Self-serve plans run from Free through Pay As You Go, Builder, and Scale, with an Enterprise tier above that on the pricing page.
The cutover test plan
Don't flip production on vibes. This is the checklist I'd run against FluxRouter with the new key before real traffic touches it. Budget half an hour.
1. List models with the new key.
curl -s https://api.fluxrouter.ai/v1/models \
-H "Authorization: Bearer $FLUX_API_KEY"
One call, two answers: your auth works, and you're looking at the id catalog you'll actually be allowed to use.
2. One non-streaming chat completion, and read the headers.
curl -si https://api.fluxrouter.ai/v1/chat/completions \
-H "Authorization: Bearer $FLUX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "flux-auto", "messages": [{"role": "user", "content": "Reply with the word ok."}]}' \
| grep -i "^x-flux-"
You should see all five here: X-Flux-Model, X-Flux-Original-Model, X-Flux-Routed, X-Flux-Request-Id, X-Flux-Cost-Usd. All five show up on this call because it's non-streaming; streaming responses carry the first four and no cost header. If your pipeline is going to depend on any of them, this is where you confirm they're really there.
3. One streaming request. Confirm your chunk parsing holds and you see the data: [DONE] terminator. And if your OpenRouter spend tracking read usage out of the final SSE chunk, check what FluxRouter's final chunk actually carries on this call (try stream_options: {"include_usage": true}) before assuming parity. Don't take my word for it either way; this one you verify with your own eyes.
curl -sN https://api.fluxrouter.ai/v1/chat/completions \
-H "Authorization: Bearer $FLUX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "flux-auto", "stream": true, "messages": [{"role": "user", "content": "Count to five."}]}'
4. One tool-call round trip. Send a request with a tools array, get tool_calls back, return a role: "tool" message, get the final answer. Use your real production tool schema, not a toy one. And if tools are mandatory in your product, run this against your tool-capable pin, not just flux-auto; tool support belongs to the backing model.
5. Contract-test whatever makes your workload yours. One real structured-outputs request if you use them. One /v1/responses call if you're on the Responses shape. One Anthropic-SDK call through /anthropic if that's your path. Production schemas, not toys. The smoke tests above prove the pipe; they say nothing about your contract.
6. If you pin, check the pin. Send one request with your pinned id and read two headers back: X-Flux-Routed: false, the router's signal that it didn't reroute you, and X-Flux-Model, the name of what actually served. For an unversioned pin like flux-pinned-claude-sonnet, that second header is how you learn the exact backing version.
7. Log X-Flux-Request-Id from day one. It's the handle support will ask for. The worst time to start logging it is after the incident.
8. Canary, with the old key still warm. Before real traffic, cap the new key: on the API keys page in the dashboard you can set a monthly spend limit in USD, a lifetime limit, and a monthly token cap per key (blank means unlimited, so don't leave it blank on a cutover). Then don't flip 100% at once. Send a slice of production traffic, watch errors, latency, and cost against your OpenRouter baseline, and keep your OpenRouter key live until you've seen a full day green. That key is your rollback plan, and it costs nothing sitting in the drawer.
Run all eight green and the cutover itself is the three-line diff from the top of this page.
Quick answers
Can I still call one specific vendor model, like I did on OpenRouter? Yes. Pin it with a flux-pinned-* id, and pull the current list from GET /v1/models.
My integration uses the Anthropic SDK, not the OpenAI one. Am I stuck? No. Point it at https://api.fluxrouter.ai/anthropic and carry on.
Where do I go next? The migration doc is the canonical reference, the quickstart gets you a key, and the models page explains the id scheme in full.
Receipts
Where every claim in this piece comes from. FluxRouter claims are from FluxRouter's published docs; OpenRouter claims are from OpenRouter's public docs as fetched July 16, 2026, and are attributed inline.
FluxRouter (published docs):
- Base URL, key format, three-line migration, auth header, drop-in OpenAI SDK support: Migrate from OpenRouter and Quickstart
- Anthropic SDK passthrough at
/anthropic: Quickstart flux-auto, tier aliases,flux-pinned-*ids, Sonar search grounding,/v1/modelsas the authoritative catalog: Models- The five
X-Flux-*headers,X-Flux-Routedas the router's no-reroute signal,X-Flux-Cost-Usdon non-streaming, streaming cost on the bill, request-id as support handle: Transparency headers - Streaming contract (SSE,
chat.completion.chunk,data: [DONE]): Streaming guide - Tool calling flow: Tools guide
- Dashboard usage/billing pages, Stripe billing, plan names, spend ceiling: Usage and invoices
OpenRouter (their public docs, fetched 2026-07-16; recheck before relying on them):
- Base URL and chat endpoint, Bearer auth,
vendor/modelslugs and model catalog: OpenRouter quickstart - Canonical dotted Sonnet slug (
anthropic/claude-sonnet-4.5): OpenRouter model page, fetched 2026-07-16 openrouter/autoauto-router: OpenRouter routing docs- Attribution headers (
HTTP-Referer,X-OpenRouter-Title), SSE comment payloads, tool translation behavior,usage/costaccounting and the generation endpoint: OpenRouter API reference
If anything above disagrees with what curl tells you today, trust the curl and tell us.
