If you need to migrate from OpenRouter to n4n step by step, the good news is both gateways speak the OpenAI chat completions protocol. The work is mostly configuration and header translation, not model logic rewrites.
Step 1: Audit your current OpenRouter integration
Pull your existing client configuration before changing anything. Capture the base URL, the exact model strings you call, any custom headers, and how you currently handle provider errors.
Most OpenRouter users set the base URL to https://openrouter.ai/api/v1 and pass HTTP-Referer plus X-Title for attribution. A typical Python snippet looks like this:
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key="sk-or-...",
default_headers={
"HTTP-Referer": "https://your-app.com",
"X-Title": "My App",
},
)
resp = client.chat.completions.create(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "Hello"}],
)
Record every model value your code requests over a 7-day window. Also note if you rely on OpenRouter’s route parameter or nested provider routing inside the request body. Those directives determine which upstream vendor serves the token.
Check your retry layer. Many teams wrap calls in custom loops that catch RateLimitError and switch models. That logic will change in later steps. Document the exact exception types you catch and the fallback order.
Finally, export your usage logs. You need a baseline of token counts and latency percentiles to prove the migration didn’t regress. Store these in a flat file or temporary table.
Step 2: Map model identifiers to the new gateway
Both gateways are OpenAI-compatible, but model naming can differ. OpenRouter prefixes providers (anthropic/claude-3.5-sonnet), while n4n exposes the same 240+ models behind one endpoint using provider-agnostic strings where possible.
Build a static mapping table you can code-review:
{
"openai/gpt-4o": "gpt-4o",
"anthropic/claude-3.5-sonnet": "claude-3.5-sonnet",
"google/gemini-pro-1.5": "gemini-1.5-pro"
}
Do not guess. Pull the live model list from the n4n status endpoint and diff it against your audit log. If a model is missing, keep it on OpenRouter or pick an equivalent.
Write the mapping as a pure function, not inline string replacement. This keeps tests simple:
def to_n4n_model(or_model: str) -> str:
return {
"openai/gpt-4o": "gpt-4o",
"anthropic/claude-3.5-sonnet": "claude-3.5-sonnet",
}.get(or_model, or_model)
Add a unit test that fails if any model from your audit log is absent from the map. That test is your first safety net when you migrate from OpenRouter to n4n step by step.
Step 3: Swap the base URL and credentials
Change the base_url to the OpenAI-compatible endpoint hosted by n4n.ai and use your new API key. Everything else in the OpenAI client constructor stays identical.
client = OpenAI(
base_url="https://api.n4n.ai/v1",
api_key="sk-n4n-...",
# n4n ignores HTTP-Referer; use routing headers if needed
)
Rotate keys via env vars, never hard-code. Set OPENAI_BASE_URL and OPENAI_API_KEY in your deployment manifest and restart one canary pod.
Verify base connectivity
Run a minimal health call:
curl https://api.n4n.ai/v1/models \
-H "Authorization: Bearer $OPENAI_API_KEY" | head -c 200
You should see a JSON list of model objects. If you get 401, the key is wrong; 404 means the path is off. Once this works, point your staging environment at the new client and run your existing integration tests.
Step 4: Translate routing and cache-control headers
OpenRouter uses HTTP-Referer and X-Title for dashboard attribution. n4n honors client routing directives and forwards provider cache-control hints instead. If you used request-body provider routing, keep it—n4n passes those through.
For prompt caching, OpenRouter ignores it; n4n forwards cache_control to upstream providers that support it (e.g., Anthropic). Adapt your message construction:
messages = [
{"role": "system", "content": "You are a terse bot."},
{
"role": "user",
"content": "Summarize the docs.",
"cache_control": {"type": "ephemeral"},
},
]
Drop the HTTP-Referer header entirely. If you need to pin a provider, use the same extra_body={"provider": {"order": ["anthropic"]}} pattern the OpenAI SDK allows. Test that the pin actually reaches the intended vendor by checking the x-n4n-upstream response header in staging.
Step 5: Simplify fallback logic
OpenRouter forces you to write retry loops when a provider is rate-limited. n4n provides automatic fallback when a provider is degraded, so you can delete custom cascade code.
Old pattern:
for model in ["openai/gpt-4o", "anthropic/claude-3.5-sonnet"]:
try:
return call(model)
except RateLimitError:
continue
New pattern:
resp = client.chat.completions.create(
model="gpt-4o",
messages=messages,
extra_body={"fallback": True}, # optional explicit hint
)
If you still want app-level control, catch APIStatusError and log response.headers.get("x-n4n-fallback"). The header tells you which backup provider served the request. Remove any sleep-and-retry backoff longer than 200ms; the gateway handles degradation faster than your loop can.
Step 6: Reconcile usage metering
Both return usage in the response, but n4n emits per-token usage metering broken down by prompt, completion, and cached tokens. Wire your analytics to the new fields:
usage = resp.usage
print({
"prompt": usage.prompt_tokens,
"completion": usage.completion_tokens,
"cached": getattr(usage, "prompt_tokens_details", {}).get("cached_tokens", 0),
})
Export these to your cost dashboard. The shape matches OpenAI, so most billing libraries work unchanged. If you previously parsed OpenRouter’s native_tokens field, drop it—n4n returns standard OpenAI token counts.
Run a diff between your baseline token totals and the staging totals for the same replayed prompts. A variation under 2% is normal due to provider differences; larger gaps mean a model mapping mistake.
Step 7: Run a shadow test
Before cutover, send duplicated traffic to both gateways in a shadow mode. Use a feature flag:
def chat(messages):
primary = call_n4n(messages)
if SHADOW:
try:
call_openrouter(messages)
except Exception as e:
log.warning("shadow failed: %s", e)
return primary
Compare responses for equality on deterministic prompts (temperature=0). Check latency p95 and token counts. Divergence usually means a model mapping error, not a protocol bug.
Keep shadow mode on for at least 24 hours of representative load. Watch for multimodal requests if you send images—OpenRouter’s transform parameter has no direct equivalent, so verify base64 handling manually.
Step 8: Cut over and verify success
Flip the flag, point all traffic to n4n, and monitor. Verification checklist:
- All model strings resolve (no 400s on unknown model).
usageblocks present in every response.- Fallback headers appear during provider incidents.
- Cost per 1k tokens aligns with your shadow baseline.
A quick post-cutover smoke test:
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "ping"}],
temperature=0,
)
assert resp.choices[0].message.content.strip()
assert resp.usage.total_tokens > 0
If that passes in prod and your error rate drops versus the OpenRouter baseline, the migrate from OpenRouter to n4n step by step plan is complete.
Gotchas we hit in real migrations
OpenRouter’s X-Title populates a public app list; removing it is fine but update your internal tracing. Some libraries cache the base URL at import time—restart workers, don’t just reload config.
If you used OpenRouter’s transform parameter for image URLs, n4n expects raw base64 or standard URLs; test multimodal paths explicitly.
Finally, keep the old client code behind a kill switch for a week. The migrate from OpenRouter to n4n step by step process is low-risk, but provider quirks surface only under production load. After seven days of clean metrics, delete the dead branch.