agentsclimarketplace

Openrouter openai compat

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/openrouter-pack/skills/openrouter-openai-compat

'Migrate from OpenAI to OpenRouter with minimal code changes. Use when switching to OpenRouter or maintaining dual compatibility. Triggers: ''openrouter openai compatible'', ''openrouter drop-in'', ''openai to openrouter'', ''openrouter migration''.From its SKILL.md

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill openrouter-openai-compat

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

9.4 KB, ~2.3k tokens by cl100k_base, as published. Nobody here has run it

OpenRouter OpenAI Compatibility

Overview

OpenRouter implements the OpenAI Chat Completions API specification (/v1/chat/completions). Existing OpenAI SDK code works with OpenRouter by changing two values: base_url and api_key. This gives you access to 400+ models from all providers through the same SDK interface.

Prerequisites

  • An existing OpenAI SDK integration to migrate — Python or TypeScript code calling chat.completions.create
  • An OpenRouter API key exported as OPENROUTER_API_KEY — see the openrouter-install-auth skill for setup
  • Python 3.8+ with the openai package, or Node.js 18+ with the openai npm package — the same SDK you already use, no new dependency
  • Optionally keep OPENAI_API_KEY exported too, so the Dual-Provider Pattern can switch back to direct OpenAI

Instructions

  1. Apply The Two-Line Migration: point base_url at https://openrouter.ai/api/v1 and swap api_key to OPENROUTER_API_KEY; optionally add the HTTP-Referer / X-Title headers for app attribution.
  2. Prefix every model string per Model ID Mapping — gpt-4o becomes openai/gpt-4o, o1 becomes openai/o1 — and try a non-OpenAI model (anthropic/claude-3.5-sonnet) through the same client.
  3. Confirm your feature usage against What Works Identically (streaming, tools, JSON mode, stop, n) and adjust per What Differs — remove the organization param, plan around limited embeddings, and check logprobs support per model via /api/v1/models.
  4. Layer in OpenRouter-Only Features through extra_body: ordered fallback model lists with "route": "fallback", provider preferences with sort: "price", or the plugins: [{"id": "web"}] web-search plugin.
  5. Keep the migration reversible with the Dual-Provider Pattern — create_client() switches between direct OpenAI and OpenRouter off the LLM_PROVIDER environment variable.

The Two-Line Migration

Python (Before)

from openai import OpenAI

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])  # OpenAI direct
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
)

Python (After)

from openai import OpenAI

client = OpenAI(
    base_url="https://openrouter.ai/api/v1",              # Changed
    api_key=os.environ["OPENROUTER_API_KEY"],              # Changed
    default_headers={
        "HTTP-Referer": "https://your-app.com",            # Added (optional)
        "X-Title": "Your App",                             # Added (optional)
    },
)
response = client.chat.completions.create(
    model="openai/gpt-4o",  # Prefix with provider namespace
    messages=[{"role": "user", "content": "Hello"}],
)

TypeScript (After)

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://openrouter.ai/api/v1",
  apiKey: process.env.OPENROUTER_API_KEY,
  defaultHeaders: { "HTTP-Referer": "https://your-app.com", "X-Title": "Your App" },
});

const res = await client.chat.completions.create({
  model: "openai/gpt-4o",
  messages: [{ role: "user", content: "Hello" }],
});

Model ID Mapping

OpenAI DirectOpenRouter ID
gpt-4oopenai/gpt-4o
gpt-4o-miniopenai/gpt-4o-mini
gpt-4-turboopenai/gpt-4-turbo
o1openai/o1
o1-miniopenai/o1-mini

You also gain access to non-OpenAI models through the same SDK:

# Same client, any provider
response = client.chat.completions.create(
    model="anthropic/claude-3.5-sonnet",  # Anthropic
    messages=[{"role": "user", "content": "Hello"}],
)

response = client.chat.completions.create(
    model="google/gemini-2.0-flash",  # Google
    messages=[{"role": "user", "content": "Hello"}],
)

What Works Identically

FeatureStatusNotes
chat.completions.createFully supportedMain endpoint, all parameters
stream: trueFully supportedSSE format identical to OpenAI
tools / tool_choiceSupportedOpenRouter transforms for non-OpenAI providers
response_format: { type: "json_object" }SupportedBasic JSON mode
response_format: { type: "json_schema" }SupportedStrict schema mode
temperature, top_p, max_tokensSupportedStandard parameters
stop sequencesSupportedArray of stop strings
n (multiple completions)SupportedMultiple choices

What Differs

FeatureDifferenceWorkaround
Model IDsPrefixed with provider/Update model strings
organization paramNot usedRemove from client init
EmbeddingsLimited supportUse direct provider or dedicated embedding service
Fine-tuned modelsNot directly accessibleUse provider's fine-tuned model ID if hosted
logprobsModel-dependentCheck model capabilities via /api/v1/models
Responses APIBeta supportUse /api/v1/responses endpoint

OpenRouter-Only Features

These are available through the same SDK but are unique to OpenRouter:

# Model fallbacks (try models in order)
response = client.chat.completions.create(
    model="anthropic/claude-3.5-sonnet",
    messages=[{"role": "user", "content": "Hello"}],
    extra_body={
        "models": [
            "anthropic/claude-3.5-sonnet",
            "openai/gpt-4o",
            "google/gemini-2.0-flash",
        ],
        "route": "fallback",
    },
)

# Provider preferences
response = client.chat.completions.create(
    model="anthropic/claude-3.5-sonnet",
    messages=[{"role": "user", "content": "Hello"}],
    extra_body={
        "provider": {
            "order": ["anthropic"],             # Prefer Anthropic direct
            "allow_fallbacks": True,
            "sort": "price",                    # Cheapest first
        },
    },
)

# Plugins (web search, response healing)
response = client.chat.completions.create(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "What happened today?"}],
    extra_body={
        "plugins": [{"id": "web"}],  # Enable real-time web search
    },
)

Dual-Provider Pattern

import os
from openai import OpenAI

def create_client(provider: str = "openrouter") -> OpenAI:
    if provider == "openai":
        return OpenAI(api_key=os.environ["OPENAI_API_KEY"])
    return OpenAI(
        base_url="https://openrouter.ai/api/v1",
        api_key=os.environ["OPENROUTER_API_KEY"],
        default_headers={"HTTP-Referer": "https://your-app.com"},
    )

# Switch providers without changing application code
client = create_client(os.environ.get("LLM_PROVIDER", "openrouter"))

Output

  • Standard OpenAI-SDK ChatCompletion objects — choices[0].message.content, usage token counts, and model reporting the provider-prefixed ID that actually served the request
  • The identical code path returning completions from non-OpenAI models (Claude, Gemini) with only the model string changed
  • A provider-switchable client from create_client() — flipping LLM_PROVIDER moves traffic between direct OpenAI and OpenRouter with zero application-code changes

Examples

After the two-line change, the untouched OpenAI SDK call round-trips through OpenRouter:

client = OpenAI(base_url="https://openrouter.ai/api/v1",
                api_key=os.environ["OPENROUTER_API_KEY"])
response = client.chat.completions.create(
    model="openai/gpt-3.5-turbo",
    messages=[{"role": "user", "content": "What is the capital of France?"}],
    max_tokens=100,
)
print(response.choices[0].message.content)  # The capital of France is Paris.
print(response.model)                        # openai/gpt-3.5-turbo

Swap the model string to anthropic/claude-3.5-sonnet and the same code returns Claude's answer — that swap is the entire multi-provider story. More worked examples: references/examples.md.

Error Handling

IssueCauseFix
400 unsupported parameterModel doesn't support a parameterConditionally set params based on model capabilities
Different response qualityNon-OpenAI model handles prompt differentlyAdjust prompts per model family; test before switching
Missing organizationOpenRouter ignores org-level authRemove organization from client init

Enterprise Considerations

  • Use environment variables to switch between direct OpenAI and OpenRouter without code changes
  • Test your full prompt suite across providers before migrating production traffic
  • Monitor response quality and latency after migration; some prompts may need tuning
  • OpenRouter normalizes the API across providers, but subtle behavioral differences exist between model families
  • Use extra_body for OpenRouter-specific features (provider preferences, plugins, fallbacks)

References

What ships with it: 5 files

7.0 KB alongside SKILL.md

Keep looking

Skills are one crate of 326,144. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.