Moving your LLM traffic between gateways is dull work until a model ID silently breaks or a header gets ignored. This OpenRouter to n4n migration checklist lays out the exact order to repoint your OpenAI-compatible client, reconcile model strings, carry over routing intent, and confirm per-token metering without a midnight page. If your app already talks to OpenRouter via the OpenAI SDK, you are 80% done before you start.
Step 1: Inventory every OpenRouter-specific assumption
Grep your codebase for the base URL and any custom headers. OpenRouter users often hardcode https://openrouter.ai/api/v1 and set HTTP-Referer or X-Title. Those headers are ignored by other gateways, so they are dead weight.
grep -rn "openrouter.ai" ./src
grep -rn "HTTP-Referer\|X-Title\|OpenRouter" ./src
List the distinct model strings you send. Pay attention to provider prefixes (openai/gpt-4o, anthropic/claude-3.5-sonnet). Write them down; you will cross-check each in Step 3.
Capture where you read the usage object. OpenRouter returns it identically to OpenAI, but you may have wrapped it in a custom meter. That code stays, but the source of tokens changes. Note any retry logic that keys on OpenRouter-specific error codes (e.g., 429 with a custom body field). Standard 429 and 5xx handling transfers unchanged.
Step 2: Repoint the base URL and API key
Both gateways are OpenAI-compatible, so the only mandatory change is the base_url and credential. n4n.ai exposes a single OpenAI-compatible endpoint covering 240+ models, so you drop the OpenRouter host and point at it.
from openai import OpenAI
import os
client = OpenAI(
base_url="https://api.n4n.ai/v1",
api_key=os.environ["N4N_API_KEY"],
)
Store the key in the same secret store you used for OPENROUTER_API_KEY. If you previously passed default_headers with OpenRouter tags, delete that argument:
# Before
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
default_headers={"HTTP-Referer": "https://myapp.com", "X-Title": "MyApp"},
)
# After: headers removed
client = OpenAI(
base_url="https://api.n4n.ai/v1",
api_key=os.environ["N4N_API_KEY"],
)
Run a smoke test:
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "ping"}],
)
print(resp.choices[0].message.content)
If you get a completion, the network path works. The OpenRouter to n4n migration checklist now moves to model compatibility, which is where most silent breakages hide.
Step 3: Reconcile model identifiers
OpenRouter popularized slash-prefixed model names (openai/gpt-4o). The new gateway addresses models by their canonical IDs when they are unique, and uses qualified names only to disambiguate. Pull the live model list and diff against your inventory.
models = client.models.list()
available = {m.id for m in models}
required = {"openai/gpt-4o", "anthropic/claude-3.5-sonnet", "mistral/mixtral-8x7b"}
missing = required - available
print(missing)
If gpt-4o appears without prefix, change your calls to the unprefixed ID. If a provider collision exists, the gateway will document the qualified form; use it exactly. Do not guess—missing models fail at request time, not import time.
For fine-tuned or private models, confirm they are provisioned on the new account. Gateways do not share model access. The OpenRouter to n4n migration checklist requires you to treat every model string as a contract: lock them in a constants file rather than inline strings, so a future rename is a one-line diff.
Step 4: Port routing directives and cache hints
OpenRouter lets you hint routing via headers or route params. n4n.ai provides automatic fallback when a provider is rate-limited or degraded, but explicit client routing is still honored. Strip OpenRouter-only fields and use the standard extension pattern: pass routing intent inside the JSON body under routing if your client supports extra fields, or rely on the gateway default.
Provider cache-control hints are forwarded unchanged. If you use Anthropic models with prompt caching, keep the cache_control block on messages:
resp = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[
{"role": "system", "content": "You are a terse bot."},
{"role": "user", "content": "Long context..."},
{"role": "user", "content": "Summarize", "cache_control": {"type": "ephemeral"}}
],
)
That cache_control is passed through to the upstream provider. If you previously set X-OpenRouter-Cache or similar, remove it; it has no effect. Any fallback list you maintained client-side can be dropped—the gateway handles degradation transparently, but you can still force a preference if you need deterministic provider selection for compliance.
Step 5: Adapt usage metering and billing hooks
Per-token usage metering is standard in the response. Extract it the same way, but tag the source so finance can reconcile. As part of the OpenRouter to n4n migration checklist, verify that your meter captures the model field exactly as sent, because qualified names may differ.
usage = resp.usage
print({
"model": resp.model,
"prompt_tokens": usage.prompt_tokens,
"completion_tokens": usage.completion_tokens,
"total_tokens": usage.total_tokens,
})
If you aggregate cost, note that token counts are provider-reported; the gateway does not alter them. Wire the total_tokens into your existing meter keyed by model. Because n4n returns the same usage shape, your dashboard code needs no structural change. Add a quick assertion in tests that usage is not None for every successful call.
Step 6: Run a side-by-side validation
Before cutover, send a fixed prompt to both gateways from a script and compare latency, output, and usage. Use environment toggles:
import os
from openai import OpenAI
def make_client():
if os.environ["MODE"] == "old":
return OpenAI(base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"])
return OpenAI(base_url="https://api.n4n.ai/v1",
api_key=os.environ["N4N_API_KEY"])
client = make_client()
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Return the word 'ok'."}],
temperature=0,
)
assert "ok" in resp.choices[0].message.content.lower()
print("validation pass", resp.usage.total_tokens)
Run with MODE=old and MODE=new. Diff the total_tokens; they should match within provider rounding. If outputs diverge wildly, check system prompts and temperature—those are request-bound, not gateway-bound. Repeat the check for at least three models from your inventory, including one cross-provider call (e.g., Anthropic) to confirm cache hints survive.
Step 7: Cut over and monitor
Flip the environment variable in your deployment. No code change is needed if you centralized the client constructor. The final step of the OpenRouter to n4n migration checklist is cutover and observation. Watch two signals for the first hour:
- Error rate on
/v1/chat/completions(4xx vs 5xx). usage.total_tokensper route, compared to the old baseline.
If a model returns 404, it is a missing ID from Step 3, not a network issue. Fix the string and redeploy. If you see elevated 429s, the gateway’s automatic fallback should absorb provider limits, but verify your retries use exponential backoff and respect Retry-After.
Verifying success
You have completed the OpenRouter to n4n migration checklist when:
- All model IDs from inventory resolve against
client.models.list(). - A production-like request returns valid content and a populated
usageobject. - No OpenRouter-specific headers remain in client init.
- Side-by-side token counts match within 1% on three repeated calls per model.
- Traffic routes exclusively to the new base URL for 24 hours without elevated 5xx.
Keep the old credential disabled but not deleted for a week. That buffer turns a bad cutover into a config revert, not an incident.