Change your base URL and swap in an OrcaRouter key. Your models, your SDK and the rest of your code stay exactly as they are — openrouter/ router ids are the one rename.
OrcaRouter speaks the OpenAI, Anthropic and Gemini APIs natively, so you keep the SDK you already use. In each snippet, lines starting with - are what you remove and lines starting with + are what you add.
client = OpenAI(
- base_url="https://openrouter.ai/api/v1",
+ base_url="https://api.orcarouter.ai/v1",
api_key=ORCAROUTER_API_KEY,
)That's the only code change. Swap in an OrcaRouter key as well — your model names, messages and streaming all stay the same, unless you call an openrouter/ router id.
client = Anthropic(
+ base_url="https://api.orcarouter.ai",
api_key=ORCAROUTER_API_KEY,
)We speak Anthropic's API directly, which OpenRouter doesn't. If you have been calling Claude through the OpenAI SDK to fit OpenRouter, you can stop.
const ai = new GoogleGenAI({
apiKey: ORCAROUTER_API_KEY,
+ httpOptions: { baseUrl: "https://api.orcarouter.ai" },
});We speak Gemini's API directly too, so the Google SDK works unchanged.
Imports, client, and one real call. Runnable as it stands, once ORCAROUTER_API_KEY is set in your environment.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.orcarouter.ai/v1",
api_key=os.environ["ORCAROUTER_API_KEY"],
)
response = client.chat.completions.create(
model="anthropic/claude-sonnet-4",
messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content)import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.orcarouter.ai/v1",
apiKey: process.env.ORCAROUTER_API_KEY,
});
const response = await client.chat.completions.create({
model: "anthropic/claude-sonnet-4",
messages: [{ role: "user", content: "Hello" }],
});
console.log(response.choices[0].message.content);curl https://api.orcarouter.ai/v1/chat/completions \
-H "Authorization: Bearer $ORCAROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4",
"messages": [{"role": "user", "content": "Hello"}]
}'import os
from anthropic import Anthropic
client = Anthropic(
base_url="https://api.orcarouter.ai",
api_key=os.environ["ORCAROUTER_API_KEY"],
)
message = client.messages.create(
model="anthropic/claude-sonnet-4",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
print(message.content[0].text)import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
baseURL: "https://api.orcarouter.ai",
apiKey: process.env.ORCAROUTER_API_KEY,
});
const message = await client.messages.create({
model: "anthropic/claude-sonnet-4",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }],
});
console.log(message.content[0].text);curl https://api.orcarouter.ai/v1/messages \
-H "x-api-key: $ORCAROUTER_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello"}]
}'import os
from google import genai
client = genai.Client(
api_key=os.environ["ORCAROUTER_API_KEY"],
http_options={"base_url": "https://api.orcarouter.ai"},
)
response = client.models.generate_content(
model="google/gemini-2.5-pro",
contents="Hello",
)
print(response.text)import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({
apiKey: process.env.ORCAROUTER_API_KEY,
httpOptions: { baseUrl: "https://api.orcarouter.ai" },
});
const response = await ai.models.generateContent({
model: "google/gemini-2.5-pro",
contents: "Hello",
});
console.log(response.text);curl "https://api.orcarouter.ai/v1beta/models/google/gemini-2.5-pro:generateContent" \
-H "x-goog-api-key: $ORCAROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contents": [{"parts": [{"text": "Hello"}]}]
}'We use the same vendor/model names, so anthropic/claude-sonnet-4 works as-is. OpenRouter's own openrouter/ router ids are the exception: those get renamed.
| On OpenRouter | On OrcaRouter |
|---|---|
openrouter/auto | orcarouter/auto |
openrouter/free | orcarouter/free |
| Any other openrouter/ router | No counterpart — name a model instead |
| Any vendor/model id, such as anthropic/claude-sonnet-4 | The same id, unchanged |
Better to know now than to find out in production.
For most integrations, yes. Change the base URL and swap in an OrcaRouter key; your SDK, model names, messages and streaming stay the same. The exceptions are OpenRouter's own openrouter/ router ids, which get renamed, and the four things OrcaRouter deliberately does not do, listed above.
The OpenAI SDK wants https://api.orcarouter.ai/v1. The other two want https://api.orcarouter.ai — each appends its own path.
The value changes — an OpenRouter key will not authenticate here — but the place does not: whichever header your SDK already sends, we read it.
OrcaRouter's routers are the orcarouter/ ones. Two match theirs directly: use orcarouter/auto in place of openrouter/auto, and orcarouter/free in place of openrouter/free — same idea either way, we pick a model for you at request time, and the free one keeps you on the zero-cost tier. Their other routers have no counterpart here, so name a model instead.
Move models and route inside extra_body. Everything else about it is the same: list your models in preference order and we try them in turn. At least 2 models, at most 5. Worth doing properly. At the top level we do not read them, so your fallbacks quietly stop working — and on deployments that forward the request body as-is, the model receives the extra fields and may reject the call outright.
Read X-Orca-Request-Id from the response, then call /v1/generation. A 501 means cost lookups are switched off on this deployment.
Send X-Title and your app keeps its name in your analytics with no change; we read OpenRouter's own header names too. HTTP-Referer on its own only names your app if its domain is one we already recognise, so send a title header if you want to be certain. This row is here so you do not remove them by mistake.
We do not use either. On a normal channel they are dropped before the request reaches the model; on a channel set to forward the raw body they are passed on, and a strict upstream can refuse the request. Taking them out is the safe move either way.
Everything you would expect, plus a few OpenRouter does not have. Served here and not by OpenRouter: /v1/messages, /v1beta/models … generateContent, /v1/rerank and /v1/realtime. Not available: /v1/fine-tunes, GET /v1/files and /v1/images/variations.
Errors come back in the same shape you already handle. Act on the HTTP status: retry a 429 when we send a Retry-After, back off and retry a 5xx apart from 501 — which means we do not serve that call at all — and treat a 4xx on a relay call as something to fix in your request. A 404 from /v1/generation is the exception: it can simply mean the request has not settled yet, so retry that one briefly. Do not read error.type as the origin: orcarouter_api_error is always ours, but we deliberately send the provider’s own vocabulary for some of our own failures so your SDK takes its normal branch — a rate limit we applied and one the model applied both say rate_limit_error. Quote X-Orca-Request-Id when you need us to look up a single call.