
What changes when you switch
Two config values: the base URL becomes
https://api.nrouter.ai/v1and the key becomes yourNROUTER_API_KEY. Your SDK, request shape, streaming and tool calls are unchanged. The real migration work is elsewhere — model-slug mapping, any vendor-specific fields you added to the request body, and an error and cost contract your client needs to read slightly differently.
Gateway migrations feel like projects because people picture rewriting an integration. You are not rewriting anything. Both sides speak the OpenAI-compatible surface, which is precisely what that standard is for: the client library, the message array, the streaming protocol and the tool-call schema are the same objects on both ends of the swap.
That makes the mechanical part a fifteen-minute change and moves all the risk into three places nobody looks at until cutover day. This post is those three places, plus the verification pass that catches them in staging instead of in production. If you are still deciding whether to move, the feature-by-feature case lives in the OpenRouter alternative write-up — this post assumes the decision is made and gets you across.
The job it does for you
Before: your application is bound to one gateway's account model. Your spend controls are whatever that gateway exposes, your cost figures are whatever it reports, and your blast radius is however many services share the key. When you want a per-service budget or a per-team spend split, you build it in application code — which means an infinite loop in a background worker is bounded by a bug-free if statement you wrote yourself.
After: the same application code calls the same endpoints, but each service holds its own key with its own rate ceiling and its own spend cap enforced before the request leaves the gateway. Spend attributes to a team automatically because the key carries the identity. The fee is a visible line at purchase rather than a percentage folded into a per-token rate, so your cost reports are the provider's numbers.
None of that requires a code change. It requires the two-line swap below and then a configuration pass in the dashboard, which is the point of doing it on a Tuesday afternoon rather than scheduling a quarter for it.
The two lines that change
from openai import OpenAI
client = OpenAI(
- base_url="https://openrouter.ai/api/v1",
- api_key=os.environ["OPENROUTER_API_KEY"],
+ base_url="https://api.nrouter.ai/v1",
+ api_key=os.environ["NROUTER_API_KEY"], # sk-nrouter-...
)
resp = client.chat.completions.create(
model=MODEL, # ← the one value you must re-map
messages=messages,
stream=True,
)If your keys and base URLs are already centralised in configuration — an environment variable, a secrets manager entry, a settings module — this is genuinely one edit. If they are scattered across a dozen call sites, the migration's real cost is the refactor you were going to have to do anyway, and this is a good excuse for it.
The same swap works from every SDK and framework that accepts a custom base URL. The Python OpenAI SDK guide, the Node SDK guide and the framework pages for LangChain, LlamaIndex, CrewAI and the Vercel AI SDK each show the exact constructor argument.
What maps one for one
Everything in this table needs no attention at all. It is here so you can stop worrying about it and spend your review time on the next section.
| Surface | Maps? | Notes |
|---|---|---|
chat.completions.create request shape | Yes | Same messages, temperature, top_p, max_completion_tokens, stop |
| Streaming | Yes | Server-sent events, same chunk shape, same terminator |
| Tool / function calling | Yes | Same tools and tool_choice schema, same tool_calls in the response |
| JSON / structured output | Yes | Same response_format request field |
| Embeddings | Yes | Same endpoint and response envelope — see the embeddings reference |
| Official OpenAI SDKs | Yes | Python, Node, Go, Java, Ruby, PHP — no version pin change |
| Frameworks with a base-URL override | Yes | LangChain, LlamaIndex, CrewAI, AutoGen, the Vercel AI SDK |
| Bearer auth header | Yes | Authorization: Bearer <key>, only the key value differs |
The reason this list is so long is structural rather than generous: an OpenAI-compatible gateway that broke any of these rows would not be OpenAI-compatible. The full request and response contract is in the chat completions reference, and if you want the conceptual version first, what is an LLM gateway covers what the layer is doing on your behalf.
What does not map
This is the section worth reading twice. Four things carry over badly, and all four are catchable in staging.
1. Model slugs. This is the one that bites. Aggregators namespace models their own way, and a slug your code hardcodes may not exist here under that exact string. Pull the live catalog from the models page or the models endpoint and build a mapping table before cutover. A wrong slug is not a subtle failure — it is an error on the first call — but you want it on a staging run, not in production traffic.
2. Vendor-specific request extensions. Any non-standard field you added to the request body or headers to steer one gateway's routing, ranking or provider preference is that gateway's extension, not part of the OpenAI-compatible contract. Those fields do not carry across. Grep your codebase for anything in the request that is not in the OpenAI schema and decide, per field, whether the behaviour it bought you is available here as configuration instead.
3. There is no BYOK. If you attached your own provider accounts to route through, that pattern has no equivalent: nRouter manages the provider credentials and you bring credits, so there is no place to paste a provider key. This is a deliberate design decision with real trade-offs, argued in why we do not do BYOK. If your commercial arrangement depends on billing directly to your own provider account, this is the point at which the migration stops making sense for you, and it is better to find that out now.
4. The cost and error contract reads differently. Two specifics your client code must handle:
| Situation | What you get | What your client should do |
|---|---|---|
| Cost is known | x-nr-request-cost present, x-nr-cost-status: exact | Record it |
| Cost is not knowable | x-nr-request-cost absent, x-nr-cost-status: unpriced | Count the call; never substitute 0 |
| Org, team or user budget hit | 402 with code budget_exceeded | Stop. Do not retry |
| A single key's budget hit | 429 with code key_budget_exceeded | Stop. Do not retry — despite the 429 |
| Rate limit hit | 429 with code rate_limit_exceeded | Retry with backoff |
The absent-cost-header rule catches people out most often, because a client written against a gateway that zero-fills will silently record a stream of free calls. The reasoning is in cost honesty. The 429 row catches the rest: a client that treats every 429 as retryable will hammer a spend ceiling forever. Handling 429 and 402 has the branch to write, and budgets vs rate limits explains why a budget block and a throttle share a status code.
The afternoon, step by step
- Open an account and buy credits. The minimum purchase is $5, and the platform fee is added on top at checkout rather than skimmed from each call. The quick start gets you to a first response.
- Mint one key per service, not one key for everything. This is the moment to fix blast radius, because you are touching the config anyway. Give each service its own key, rate ceiling and spend cap — the reasoning is in Virtual Keys vs Master Key: Scoping a Key Per Job, and the mechanics are in the key management guide.
- Build the model map. List every slug your code can emit, resolve each against the live catalog, and record the mapping in configuration rather than in code. Anything unresolvable is a decision to make now, not a 4xx to discover later.
- Swap the two config values in staging. Base URL and key only. Do not take any other change in the same commit — you want a clean bisect if something moves.
- Run your existing test traffic. Whatever you already have: contract tests, a replay of yesterday's requests, a smoke suite. The point is to compare against a known-good baseline, not to write new tests.
- Configure the controls you came for. Per-key budgets, per-team scopes, guardrails, fallback chains. These are dashboard settings, not code — see budget controls and guardrails.
- Cut over one service, not all of them. Pick the one with the most traffic and the least revenue impact. Watch it for a day.
- Cut over the rest, keeping the old key live for 24 hours. The rollback is the same one line in reverse.
Most teams spend more calendar time scheduling the change window than making the change. The step that actually takes work is step three.
Verify the cutover, and how to roll it back
Six checks, each of which has caught a real problem for someone:
- Golden-request diff. Send a fixed set of prompts through both gateways and diff the response bodies field by field. Shape differences show up here or they show up in a customer report.
- Streaming end to end. Confirm the first chunk arrives, chunks accumulate to the same text, and the stream terminates cleanly. Buffered testing hides streaming bugs completely.
- Tool calls round-trip. Confirm
tool_callscome back in the schema your executor parses, and that a tool result posted back produces a sane continuation. - Both cost headers, on every response. Log
x-nr-request-costandx-nr-cost-statusfor a full day and confirm theunpricedbranch of your code is exercised at least once. Then reconcile the total against the ledger using the spend ledger guide. - Force each ceiling deliberately. Set a tiny budget on a throwaway key and confirm you get
429withkey_budget_exceeded— then confirm your client stops rather than retries. Repeat at org scope for the402. - Latency, at the percentile that matters. Compare p50, p95 and p99 against your pre-migration baseline rather than an average, which hides exactly the tail your users feel. Measuring real LLM latency has the method.
Rollback is the same edit in reverse: point the base URL and key back, redeploy, done. Keep the old key funded and live for the first 24 hours and the whole change stays a config flip in both directions. A migration you can reverse in one line is a migration you can attempt on a Tuesday.
What it costs
Compare the fee you pay today against this one by its shape before its size: nRouter's per-call markup is 0% and the platform fee — on pay as you go 4% of your credits (so $100 of credits costs $104.00), 0% on Pro's $50/mo or $500/yr — is charged on top at purchase, so after cutover the per-token figure you reconcile against a provider's rate card is the provider's own, which is not true of any percentage folded into the price of a token. The full numbers are on pricing; the fee-on-top model and the arithmetic behind it are in markup-free LLM credits.
Every governance capability — guardrails, A/B tests, prompt management, evals, per-team budgets, audit logging, fallback chains — is on every plan. What a plan changes is the platform fee and the rate limits, and nothing else.
There is no free tier to migrate into. The account starts with a card and a real $5 minimum charge with the fee on top.
What OpenRouter still does better
A migration post that claims the other product has no advantages is not worth trusting, so here are two that are real and one that is a matter of fit.
Catalog breadth. OpenRouter's published model list is very large and includes a long tail of open-weight and community-hosted models that a curated catalog will not carry. If your product depends on a niche fine-tune being reachable through the same endpoint, check our models page against your list before you commit to anything — that comparison is the whole decision.
Bring your own provider keys. OpenRouter documents attaching your own provider accounts. If you have negotiated committed-spend pricing directly with a provider, or your compliance posture requires the inference to bill to your own account, that is a genuine capability we do not offer and are not planning to.
Public model rankings. Their usage-derived leaderboards are a useful, freely available signal for model selection, independent of whether you route through them.
OpenRouter is a trademark of its owner. nRouter is not affiliated with or endorsed by them. All claims are sourced from their public pricing or documentation on the dates linked below; if any have changed, email hello@nrouter.ai and we will update.
Limits and what this migration will not do
- It does not make your prompts portable. Prompts tuned against one model family behave differently on another. Switching gateways does not change models; switching models is a separate project with its own evaluation pass.
- It does not preserve your historical usage data. Your spend history stays where it was made. Plan for a reporting seam at the cutover date rather than a continuous series.
- It does not remove the model-mapping work. No gateway can normalise every other gateway's slug namespace, and a mapping that guesses would be worse than one you wrote.
- It will not give you BYOK later. This is a design position, not a gap awaiting a release.
- A dual-run costs double. If you shadow traffic through both gateways to compare, you pay both bills for that window. Budget for it or keep the comparison window short.
- It cannot fix a client that retries a spend ceiling. If your error handling retries every 429, moving gateways relocates that bug rather than solving it. Fix the branch first.
Try it
Open an account at signup and load the $5 minimum — charged with the platform fee on top. Mint one key for a single non-critical service, map its model slugs against the live catalog, and swap the two config values in staging. If you want to sanity-check a model before touching any code at all, run the prompt in the playground first. The whole exercise is an afternoon, and it reverses in one line.
See also
- OpenRouter alternative: every enterprise LLM-gateway feature, free for life — the feature-by-feature comparison this post deliberately does not repeat.
- Handling 429 and 402 errors from an LLM gateway — the client branch that distinguishes a throttle from a spend ceiling, which is the one code change a migration usually needs.
- Virtual Keys vs Master Key: Scoping a Key Per Job — why the cutover is the right moment to split one key into one per service.
- Markup-free LLM credits: the fee is on top, never in the rate — what changes about your cost reports once no per-call cut is folded into the rate.
- Why we do not do BYOK — the trade-off behind the one capability that genuinely does not carry across.
- Provider fallback chains: surviving an OpenAI outage — the resilience configuration worth setting up while you are already in the dashboard.
- Models — the live catalog to map your slugs against before you cut over.
Sources
Verified 2026-08-23. Vendor documentation changes; if any link below has moved or the behaviour has changed, email hello@nrouter.ai and we will correct this post.
- OpenRouter model catalog: openrouter.ai/models — the published list behind the catalog-breadth claim above.
- OpenRouter documentation: openrouter.ai/docs/quickstart — the base URL and OpenAI-compatible surface referenced in the diff.
- OpenRouter API reference: openrouter.ai/docs/api-reference/overview — the request contract the compatibility table is checked against.
- nRouter plans and fees: nrouter.ai/pricing — the 4% Pay as you go fee, Pro at $50/mo or $500/yr, and the $5 minimum purchase.
- OpenAI SDK base-URL configuration: platform.openai.com/docs/api-reference — the client argument the two-line swap sets.
- Anthropic pricing: anthropic.com/pricing — a provider rate card to cross-check a settled cost against after cutover.
OpenRouter, OpenAI and Anthropic are trademarks of their respective owners. nRouter is not affiliated with or endorsed by them. All claims above are sourced from their public pricing or documentation on the date shown.


