Migrating from Anthropic to FluxRouter: The Engineering Guide

Switch from api.anthropic.com to FluxRouter in three lines: new base URL, new key, new model id. Same Anthropic SDK, same Claude Code. Plus the spend-tracking gotcha most people miss.

Cover Image for Migrating from Anthropic to FluxRouter: The Engineering Guide

By Sean Donahoe · Published July 17, 2026 · accurate as of this date

How do I migrate from Anthropic to FluxRouter?

Point your Anthropic SDK, or Claude Code, at https://api.fluxrouter.ai/anthropic instead of https://api.anthropic.com, swap your sk-ant-... key for a FluxRouter sk-... key from the dashboard, and change the model to flux-auto (or a flux-* id). The SDK, messages.create, streaming, tool use, everything stays. The catch: your spend tracking changes, and it's not the change you'd guess. More on that below.

That's the diff. Three values.

Here's what's actually worth your time: the one auth nuance that trips nobody up (good), the Claude Code re-point (the path most of you are actually on), and the spend-tracking section, which is the part I'd read twice before you touch production. If your code today assumes Anthropic hands you a dollar figure in the response, it doesn't, on Anthropic or on Flux, and the two paths get you that number differently.

The canonical reference is the live migration doc. This piece goes past what a docs page can: the Claude Code walkthrough, the spend-tracking trap, and a straight answer to "wait, is my Claude request even going to Claude."

The one auth nuance

Anthropic direct requires three headers on every call: x-api-key, anthropic-version (something like 2023-06-01), and content-type. Standard stuff, and your SDK already sends all three.

FluxRouter's /anthropic endpoint takes the key either way: as x-api-key: sk-... (the SDK's default, so if you're on the SDK there's nothing to change) or as Authorization: Bearer sk-... if you're hand-rolling requests. Your SDK keeps sending anthropic-version and Flux is fine with it. Nobody needs to touch this section, I just want you to stop worrying about it.

Migrate the SDK: Python and Node

One line changes. Here's the before and after.

Python, before (direct to Anthropic):

python
import anthropic

client = anthropic.Anthropic(
    api_key="sk-ant-...",
)

message = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Say hello in five words."}],
)
print(message.content)

Python, after (FluxRouter):

python
import anthropic

client = anthropic.Anthropic(
    base_url="https://api.fluxrouter.ai/anthropic",  # new
    api_key="sk-...",                                  # your FluxRouter key
)

message = client.messages.create(
    model="flux-auto",  # was: claude-sonnet-4-5
    max_tokens=1024,
    messages=[{"role": "user", "content": "Say hello in five words."}],
)
print(message.content)

Node, before:

javascript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  apiKey: "sk-ant-...",
});

const message = await client.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Say hello in five words." }],
});
console.log(message.content);

Node, after:

javascript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  baseURL: "https://api.fluxrouter.ai/anthropic", // new, note the casing
  apiKey: "sk-...", // your FluxRouter key
});

const message = await client.messages.create({
  model: "flux-auto", // was: claude-sonnet-4-5
  max_tokens: 1024,
  messages: [{ role: "user", content: "Say hello in five words." }],
});
console.log(message.content);

Node uses baseURL, Python uses base_url. Small thing, catches people every time.

Prefer environment variables over hardcoding? Set ANTHROPIC_BASE_URL and skip the constructor argument entirely. Same effect, cleaner diff in your PR.

Migrate Claude Code (the one most of you are here for)

Claude Code already speaks the Anthropic wire format, so it points at the exact same /anthropic endpoint as the SDK. No plugin, no wrapper.

Add this to ~/.claude/settings.json:

json
{
  "env": {
    "ANTHROPIC_BASE_URL": "https://api.fluxrouter.ai/anthropic",
    "ANTHROPIC_AUTH_TOKEN": "sk-...",
    "ANTHROPIC_MODEL": "flux-auto"
  }
}

ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY both work here; use whichever your setup already expects. If you'd rather not touch a config file, export the same three as shell variables before launching Claude Code and it picks them up the same way.

Two things to know before you do this, straight from the docs:

  • settings.json's env block wins over your shell exports. If you set both, the file wins. Don't spend twenty minutes debugging why your shell var isn't taking.
  • On a clean install, the first run after this config change sometimes needs a re-run to pick it up. Annoying, not dangerous, just close it and open it again if the first launch looks wrong.

Don't want to hand-edit a config file at all? Flux Desktop writes this for you, one click, and detects Claude Code if it's already installed. I've got the full walkthrough, screenshots included, in the Claude Code connect guide. Not repeating it here.

Spend tracking: the part that actually changes your code

This is the section to read twice.

On Anthropic direct, the response usage block is token counts. input_tokens, output_tokens, cache token fields if you're using caching. No dollar figure anywhere in the body. Per Anthropic's docs, response headers include request-id and anthropic-organization-id; none of them carry a price. You've always priced your own spend from token counts on this path. That part doesn't change.

Here's the part that will bite you if you assume it works like the OpenAI-compatible side of Flux: on the /anthropic path, the usage block is still token counts only. input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens. No cost_usd field. If you've read about Flux's OpenAI-path migration and expect a dollar figure sitting in the response body, it isn't there on this path. Better you hit that now than find out from a broken dashboard integration.

The dollar figure lives in a header instead: X-Flux-Cost-Usd, on non-streaming responses.

Anthropic directFlux /anthropic path
usage blockToken counts onlyToken counts only
Cost in the response bodyNoneNone
Cost anywhere on the responseNot on Anthropic directX-Flux-Cost-Usd header, non-streaming only
Streaming costNot on Anthropic directNo header either. Lands on your bill / usage page
Historical lookupYour own loggingDashboard usage view

Migration action: read cost from X-Flux-Cost-Usd, not from the body, and pull it via your SDK's raw-response path.

python
raw = client.messages.with_raw_response.create(
    model="flux-auto",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Say hello in five words."}],
)
cost_usd = raw.headers.get("x-flux-cost-usd")
message = raw.parse()
print(cost_usd, message.content)

Streaming is the one place I want to be blunt: there is no cost header on a stream. It gets flushed before the total cost is known, so it can't be there, not "sometimes there," not "there if you ask nicely." The usage fields inside message_start and message_delta are still just token counts. The dollar figure for a streamed request shows up on your bill and in the usage dashboard, not on the wire. Build your real-time cost tracking around non-streaming calls, or accept that streamed-request cost is a lagging number you check later.

Non-streaming responses carry all five transparency headers: X-Flux-Model, X-Flux-Original-Model, X-Flux-Routed, X-Flux-Request-Id, X-Flux-Cost-Usd. Don't count on reading them off a stream. In live checks on July 17, 2026, the only documented transparency header present on a streamed response was X-Flux-Routed, and on a stream its value is not a reliable pin check. The cost header, the model headers, and the request id are not on the wire for a streamed request. When you need to know what served a call, confirm a pin, or capture the request id, make the call non-streaming or read it from the usage dashboard.

Billing itself runs through the dashboard: a usage view plus billing through Stripe. Self-serve plans are Free, Pay As You Go, Builder, and Scale, with an Enterprise tier above that.

Your Anthropic-shaped request might not come back from Claude

Send a request in Anthropic's exact wire format to flux-auto and the model that answers it doesn't have to be a Claude model. The router picks per request, and a non-streaming response tells you exactly what happened in its headers. X-Flux-Model shows what actually served it, X-Flux-Routed shows whether it changed your requested model at all. Check those two headers and you know exactly what ran, no guessing.

If you want Claude specifically, every time, that's what pinning is for: a flux-pinned-* id gets you a consistent backing model on every call, still metered through Flux. Send a pinned request non-streaming and check X-Flux-Routed: false in the response, that's your confirmation the pin held. Run this check on a non-streaming call, on a stream that header is not a reliable pin signal. The full, current list of ids your key can use is GET /v1/models, not a table in a blog post that goes stale the week after I write it.

bash
curl -s https://api.fluxrouter.ai/v1/models \
  -H "Authorization: Bearer $FLUX_API_KEY"

Run that once during migration. It confirms your auth works and shows you exactly what you're allowed to call.

Streaming and tool use: unchanged

Streaming stays the standard Anthropic SSE sequence: message_start, content_block_start, content_block_delta, content_block_stop, message_delta, message_stop. If your client parses that sequence today, it parses it tomorrow.

Tool use round-trips the same standard Anthropic shape, and it works under flux-auto regardless of which model ends up serving the request. Nothing Flux-specific in the schema.

The cutover test plan

Run these before you flip production traffic. Budget half an hour.

1. One non-streaming messages.create, read the headers.

bash
curl -si https://api.fluxrouter.ai/anthropic/v1/messages \
  -H "x-api-key: $FLUX_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model": "flux-auto", "max_tokens": 32, "messages": [{"role": "user", "content": "Reply with the word ok."}]}' \
  | grep -iE '^x-flux-(model|original-model|routed|request-id|cost-usd): '

Confirm all five show up, X-Flux-Cost-Usd included. This is your non-streaming call, so it should be there.

2. Confirm the usage block is token counts only. Print the response body and check. Don't build a billing pipeline on a cost_usd field that isn't in this body, ever, on this path.

3. One streaming request. Confirm the SSE sequence above and confirm, deliberately, that there's no cost header on it. That's expected, not a bug.

4. One tool-use round trip, with your actual production tool schema, not a toy one.

5. If you're pinning anything, confirm the pin. Send one non-streaming request on your flux-pinned-* id and check for X-Flux-Routed: false in the response.

6. GET /v1/models with the new key. Confirms auth and gives you the id catalog in one call.

7. Log X-Flux-Request-Id from day one. It's the support handle, and it rides your non-streaming responses. Start logging it before you need it, not after.

Green across all seven and the actual cutover is the three-line diff from the top.

Quick answers

Do I have to drop the Anthropic SDK? No. It's the exact SDK, three values change.

Does anthropic-version still matter? Your SDK already sends it. Flux accepts it. You don't touch this.

What about batch requests, prompt caching, extended thinking? The migrate doc confirms tool use, system prompts, streaming, and max_tokens carry over unchanged. I'm not going to tell you the rest of the feature set matches until I've verified each one myself, so consider that an open question for now rather than a yes.

Where do I go next? The migration doc is canonical, the Claude Code connect guide has the full one-click walkthrough, and if your integration is on the OpenAI-compatible path instead, that's a different migration entirely with its own cost-in-the-body behavior: see migrating from OpenAI or, if you're coming off OpenRouter, the OpenRouter guide. Don't mix the two paths up.

Related reading: this piece is a spoke of what an LLM gateway is, if you want the case for putting one in front of your stack in the first place. Then: why nobody can tell you what a request actually costs until you're reading a header instead of guessing, why a single provider is one bad day away from being your outage, and why the number that matters is cost per answer, not cost per token.

Right-size every prompt, see what each call costs, and pay only for what you use. That is the kind of thing we built Flux to handle.

One key. Pay only for what you use.