--- name: openrouter-upgrade-migration description: 'Migrate to OpenRouter from direct provider APIs or upgrade between SDK/model versions. Triggers: ''openrouter migrate'', ''openrouter upgrade'', ''switch to openrouter'', ''migrate from openai to openrouter''. ' allowed-tools: Read, Write, Edit, Grep, Bash(python3:*), Bash(node:*), Bash(npm:*), Bash(pip:*) version: 1.20.0 license: MIT author: Jeremy Longshore tags: - saas - openrouter - migration - upgrade compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw --- # OpenRouter Upgrade & Migration ## Current State !`npm list openai 2>/dev/null | head -5` !`pip show openai 2>/dev/null | head -5` ## Overview Migrating to OpenRouter from a direct provider API (OpenAI, Anthropic) is minimal: change `base_url` and `api_key`, add two headers. The OpenAI SDK works natively with OpenRouter. This skill covers migrating from direct APIs, switching between models, upgrading SDK versions, and running comparison tests. ## Prerequisites - An existing direct OpenAI or Anthropic integration to migrate — the Current State block above checks your installed `openai` SDK via `npm list openai` / `pip show openai` - An OpenRouter API key (`sk-or-v1-...`) exported as `OPENROUTER_API_KEY` — see the `openrouter-install-auth` skill for setup - Python 3.8+ or Node.js 18+ with the OpenAI SDK (Anthropic SDK users switch to the OpenAI SDK as part of the migration) - The old provider key (`OPENAI_API_KEY` / `ANTHROPIC_API_KEY`) kept active during migration for comparison tests and quick rollback ## Instructions 1. Confirm your installed SDK versions from the Current State output at the top of this skill. 2. Apply the 3-line change per Migration from Direct OpenAI, Migration from Direct Anthropic, or TypeScript Migration: swap `base_url` to `https://openrouter.ai/api/v1`, switch to `OPENROUTER_API_KEY`, and add the `HTTP-Referer` / `X-Title` headers. Anthropic migrations also change response parsing to `.choices[0].message.content`. 3. Prefix every model ID with its provider per the Model ID Migration Map (e.g. `gpt-4o` → `openai/gpt-4o`). 4. Work through the Migration Checklist — config, code, testing, and operations items — before flipping traffic. 5. Run the Comparison Test Script on your critical prompts (`temperature=0`) to compare content, tokens, and latency against the old backend. 6. Roll out gradually with the Feature Flag Migration pattern (`USE_OPENROUTER` env var plus `get_model_id` mapping), moving 10% → 50% → 100%. 7. Watch for post-migration failures (401, `model_not_found`, response-format drift, +50–100ms latency) per the Error Handling table. ## Migration from Direct OpenAI ```python # BEFORE: Direct OpenAI from openai import OpenAI client = OpenAI(api_key=os.environ["OPENAI_API_KEY"]) response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "Hello"}], max_tokens=200, ) # AFTER: Via OpenRouter (3 lines changed) from openai import OpenAI client = OpenAI( base_url="https://openrouter.ai/api/v1", # ← Changed api_key=os.environ["OPENROUTER_API_KEY"], # ← Changed default_headers={ # ← Added "HTTP-Referer": "https://my-app.com", "X-Title": "my-app", }, ) response = client.chat.completions.create( model="openai/gpt-4o", # ← Add provider prefix messages=[{"role": "user", "content": "Hello"}], max_tokens=200, ) ``` ## Migration from Direct Anthropic ```python # BEFORE: Direct Anthropic SDK import anthropic client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"]) response = client.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=200, messages=[{"role": "user", "content": "Hello"}], ) content = response.content[0].text # AFTER: Via OpenRouter (using OpenAI SDK instead of Anthropic SDK) from openai import OpenAI client = OpenAI( base_url="https://openrouter.ai/api/v1", api_key=os.environ["OPENROUTER_API_KEY"], default_headers={ "HTTP-Referer": "https://my-app.com", "X-Title": "my-app", }, ) response = client.chat.completions.create( model="anthropic/claude-3.5-sonnet", # OpenRouter model ID messages=[{"role": "user", "content": "Hello"}], max_tokens=200, ) content = response.choices[0].message.content # OpenAI response format ``` ## TypeScript Migration ```typescript // BEFORE: Direct OpenAI import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); // AFTER: Via OpenRouter const client = new OpenAI({ baseURL: "https://openrouter.ai/api/v1", apiKey: process.env.OPENROUTER_API_KEY, defaultHeaders: { "HTTP-Referer": "https://my-app.com", "X-Title": "my-app", }, }); // Change model from "gpt-4o" to "openai/gpt-4o" ``` ## Migration Checklist ```python MIGRATION_CHECKLIST = { "config": [ "base_url changed to https://openrouter.ai/api/v1", "API key changed to OPENROUTER_API_KEY (sk-or-v1-...)", "HTTP-Referer and X-Title headers added", "Model IDs prefixed with provider/ (e.g., openai/gpt-4o)", ], "code": [ "All client initialization updated", "Model IDs updated in all routes/configs", "Error handling covers OpenRouter-specific codes (402, 408)", "Streaming still works with new endpoint", "Tool/function calling still works", ], "testing": [ "Same prompts produce comparable quality output", "Latency within acceptable range (expect +50-100ms)", "Token counts match expectations", "Cost tracking updated for OpenRouter pricing", "Fallback chain tested", ], "operations": [ "Credit balance sufficient for expected usage", "Per-key credit limits configured", "Monitoring updated to track OpenRouter metrics", "Alerting on new error codes (402, 408)", "Rollback plan documented", ], } ``` ## Model ID Migration Map | Direct Provider | OpenRouter ID | |----------------|---------------| | `gpt-4o` | `openai/gpt-4o` | | `gpt-4o-mini` | `openai/gpt-4o-mini` | | `o1` | `openai/o1` | | `claude-3-5-sonnet-20241022` | `anthropic/claude-3.5-sonnet` | | `claude-3-haiku-20240307` | `anthropic/claude-3-haiku` | | `gemini-2.0-flash` | `google/gemini-2.0-flash-001` | | `llama-3.1-8b-instruct` | `meta-llama/llama-3.1-8b-instruct` | ## Comparison Test Script ```python def compare_migration(prompt: str, old_model: str, new_model: str): """Run same prompt through old and new configurations to compare.""" import time # New: OpenRouter or_client = OpenAI( base_url="https://openrouter.ai/api/v1", api_key=os.environ["OPENROUTER_API_KEY"], default_headers={"HTTP-Referer": "https://my-app.com", "X-Title": "migration-test"}, ) start = time.monotonic() or_response = or_client.chat.completions.create( model=new_model, messages=[{"role": "user", "content": prompt}], max_tokens=200, temperature=0, ) or_latency = (time.monotonic() - start) * 1000 return { "openrouter": { "model": or_response.model, "content": or_response.choices[0].message.content[:100], "tokens": or_response.usage.prompt_tokens + or_response.usage.completion_tokens, "latency_ms": round(or_latency), }, } # Test result = compare_migration( "What is 2+2?", old_model="gpt-4o", new_model="openai/gpt-4o", ) print(json.dumps(result, indent=2)) ``` ## Feature Flag Migration ```python import os USE_OPENROUTER = os.environ.get("USE_OPENROUTER", "false").lower() == "true" def get_llm_client(): """Feature flag for gradual migration.""" if USE_OPENROUTER: return OpenAI( base_url="https://openrouter.ai/api/v1", api_key=os.environ["OPENROUTER_API_KEY"], default_headers={"HTTP-Referer": "https://my-app.com", "X-Title": "my-app"}, ) else: return OpenAI(api_key=os.environ["OPENAI_API_KEY"]) def get_model_id(model: str) -> str: """Map model IDs based on current backend.""" if USE_OPENROUTER and "/" not in model: MODEL_MAP = {"gpt-4o": "openai/gpt-4o", "gpt-4o-mini": "openai/gpt-4o-mini"} return MODEL_MAP.get(model, f"openai/{model}") return model ``` ## Output - Migrated client initialization code: 3 changed lines (`base_url`, `api_key`, headers) plus provider-prefixed model IDs across routes/configs - A comparison test JSON per prompt with the served `model`, a content preview, combined token count, and `latency_ms` - A four-category migration checklist (config / code / testing / operations) to track cutover readiness - A feature-flagged `get_llm_client()` that flips between direct OpenAI and OpenRouter via the `USE_OPENROUTER` env var ## Examples Verify a migrated model on the same prompt before flipping traffic: ```python result = compare_migration("What is 2+2?", old_model="gpt-4o", new_model="openai/gpt-4o") print(json.dumps(result, indent=2)) # { # "openrouter": { # "model": "openai/gpt-4o", # "content": "2 + 2 = 4", # "tokens": 21, # "latency_ms": 934 # } # } ``` Expect OpenRouter latency to run ~50-100ms above the direct API. More worked examples: `references/examples.md`. ## Error Handling | Error | Cause | Fix | |-------|-------|-----| | 401 after migration | Using old API key with new base_url | Update to OpenRouter API key (sk-or-v1-...) | | `model_not_found` | Missing provider prefix | Add `openai/` or `anthropic/` prefix to model ID | | Different response format | Switched from Anthropic SDK to OpenAI SDK | Update response parsing: `.choices[0].message.content` | | Higher latency | OpenRouter proxy overhead | Expected: +50-100ms; use streaming to mask it | ## Enterprise Considerations - Migration from direct provider to OpenRouter requires only 3 lines of code change - Use feature flags for gradual migration (10% -> 50% -> 100%) - Run comparison tests on critical prompts before full migration - OpenRouter adds ~50-100ms overhead; use streaming to mask perceived latency - Keep direct provider keys active during migration for quick rollback - Update monitoring dashboards for OpenRouter-specific metrics (generation_id, provider used) ## References - Examples | Errors - [Quickstart](https://openrouter.ai/docs/quickstart) | [OpenAI Compatibility](https://openrouter.ai/docs/frameworks)