By Sean Donahoe · Published July 17, 2026 · accurate as of this date
How do I migrate from OpenAI to FluxRouter?
Change three things: the base URL to https://api.fluxrouter.ai/v1, your API key to a FluxRouter sk-... key from the dashboard, and the model string to flux-auto. Everything else, your SDK, your method calls, your endpoints, your request and response JSON, stays exactly as it was. Run the cutover checklist below before you point production traffic at it.
That's the whole migration. Genuinely three lines.
If your code already talks to OpenAI, it already talks to Flux. Same SDK, same shapes. Still weighing whether a gateway belongs in your stack at all? Start with what an LLM gateway is; this piece assumes you've made that call. The canonical reference is the live migration doc. This piece goes further than that page does: the cutover checklist, the spend-tracking change your code will actually notice, and where the model-id mapping goes.
The three-line swap
Both sides speak the OpenAI API, so nothing about your SDK moves. Here's Python, before and after, lifted straight from the migration doc.
Before, on OpenAI:
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["OPENAI_API_KEY"],
)
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Say hello in five words."}],
)
print(resp.choices[0].message.content)
After, on FluxRouter:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.fluxrouter.ai/v1",
api_key=os.environ["FLUX_API_KEY"],
)
resp = client.chat.completions.create(
model="flux-auto",
messages=[{"role": "user", "content": "Say hello in five words."}],
)
print(resp.choices[0].message.content)
base_url, api_key, model. Nothing else in that block changed. (The doc's before-example uses gpt-4o. If your production code is already on whatever OpenAI ships next, that's fine, swap whichever model string you're actually running.)
Node, same three lines:
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.fluxrouter.ai/v1", // added
apiKey: process.env.FLUX_API_KEY, // was: OPENAI_API_KEY
});
const resp = await client.chat.completions.create({
model: "flux-auto", // was: a gpt-* id
messages: [{ role: "user", content: "Say hello in five words." }],
});
console.log(resp.choices[0].message.content);
Your key comes from the Flux dashboard and looks like sk-.... The auth header shape is identical to what you're already sending: Authorization: Bearer sk-....
What stays the same
This is the reassuring part, and it's most of the migration.
- The OpenAI SDK itself.
chat.completions.create,responses.create, whatever methods you're calling now. - The endpoints:
/v1/chat/completions,/v1/responses,/v1/models. - Request and response JSON, including
stream: true, tool calls, and structured outputs. - Every parameter you tuned:
temperature,max_tokens, your message arrays, your prompts.
Tool calls in particular need zero rework. Standard tools in the request, tool_calls back, results returned as role: "tool". It's the same round trip you already built, just against a different base URL.
The one real decision: the model field
Here's the actual work in this migration, and it's small. Your gpt-* ids aren't Flux ids. You have to pick something to put in the model field, and you've got three honest options.
| Option | What it does | When to use it |
|---|---|---|
flux-auto | Routes each request to a sensible model for the prompt | Recommended default, you stop hardcoding one model |
Tier alias (flux-fast, flux-standard, flux-reasoning) | Expresses intent, speed or depth, without naming one specific model | You want consistent behavior without picking a vendor |
flux-pinned-* id | Same backing model on every call | You need a specific model every time |
Pin ids confirmed live as of this session include flux-pinned-claude-sonnet, flux-pinned-claude-haiku, flux-pinned-claude-opus, flux-pinned-sonar, and flux-pinned-sonar-reasoning-pro. Don't copy that list into a spreadsheet, though. The catalog changes, and the id you actually want might not be on it. Pull the real thing:
curl -s https://api.fluxrouter.ai/v1/models \
-H "Authorization: Bearer $FLUX_API_KEY"
That call is the authoritative list, always. If a table in a blog post disagrees with it, the table's wrong and the terminal's right.
Spend tracking: the upgrade OpenAI users notice
This is the part your code will actually need to change, not just your config.
OpenAI's usage object, per its public API reference, reports token counts: prompt tokens, completion tokens, total. It doesn't hand you a dollar figure. On the FluxRouter /v1 path, the same request comes back with a per-request USD cost sitting right there, twice.
In the body:
"usage": {
"prompt_tokens": 19,
"completion_tokens": 12,
"total_tokens": 31,
"cost_usd": 0.000084,
"currency": "USD"
}
And in a header on the same response, X-Flux-Cost-Usd, carrying the identical number. If your code reads the body already, you don't need to touch the header at all. usage.cost_usd is just there.
Streaming works the same way, one step later. Set stream_options: {"include_usage": true} when you open the stream and the final chunk's usage carries the same cost_usd (live-probed on the /v1 path, July 17, 2026). The X-Flux-Cost-Usd header is non-streaming only, so on a stream you read cost from that final chunk, not the headers:
stream = client.chat.completions.create(
model="flux-auto",
messages=[{"role": "user", "content": "..."}],
stream=True,
stream_options={"include_usage": True},
)
for chunk in stream:
if chunk.usage is not None:
print(chunk.usage.cost_usd)
Every response also carries four more headers worth knowing: X-Flux-Model (what actually answered), X-Flux-Original-Model (what you asked for), X-Flux-Routed (whether it swapped your model), and X-Flux-Request-Id (your support handle, log it from day one). Read these on a non-streaming call. On a stream they aren't on the wire, so cost rides that final usage chunk instead, exactly as above, and anything you need from these four you capture on a non-streaming request. One line pulls exactly those five and nothing else:
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":"hello"}]}' \
| grep -iE '^x-flux-(model|original-model|routed|request-id|cost-usd): '
I go deeper on all five of those in the transparency headers piece if you want the full map of where cost lives on every path.
The cutover checklist
Run this against the new key before you flip real traffic. Half an hour, tops.
- List models with the new key.
GET /v1/models, confirms auth and shows you the id catalog you can actually use. - One non-streaming call on
flux-auto. Read the fiveX-Flux-*headers andusage.cost_usd. If your pipeline depends on any of them, confirm they're really there. - One streaming call. Verify your chunk parsing, watch for
[DONE], and setinclude_usageso the final chunk carries cost. - One tool-call round trip. Standard
tools/tool_calls/role: "tool", on your real schema, not a toy. - If you pin, check the pin.
X-Flux-Routedshould readfalseandX-Flux-Modelshould matchX-Flux-Original-Modelon that request. - Log
X-Flux-Request-Idfrom day one. It's the string support asks for. The worst time to start logging it is mid-incident. - Confirm your spend ceiling in the dashboard. It's a real, named monthly account cap that protects you from runaway cost. Know where it sits before real traffic hits the new key.
FAQ
Do I keep the OpenAI SDK? Yes. Base URL, key, model, that's it.
I'm on the Anthropic SDK, not OpenAI's. Different path, https://api.fluxrouter.ai/anthropic, and your Anthropic-SDK code carries over unchanged. It's got its own piece: migrating from Anthropic, where the spend-tracking behaves differently than it does here.
Which models can I actually reach? Whatever GET /v1/models returns for your key, right now. That's the only list that doesn't go stale.
Grab a key from the quickstart and run step one above against it. If you migrated off OpenRouter instead, or you're running both, the OpenRouter migration guide walks the same swap from that side.
