Calling OpenRouter from Python and TypeScript SDKs
Most agent codebases already depend on the OpenAI client libraries.
You can keep those libraries and call hundreds of models by changing base URL, key, and model slug - then pass OpenRouter-only options through extension fields when needed.
Summary
Use the official OpenAI Python and TypeScript SDKs pointed at https://openrouter.ai/api/v1.
Send standard chat.completions (or streaming) requests with OpenRouter model slugs.
Use extra_body (Python) or extra request fields (TypeScript) for OpenRouter extensions such as models fallbacks and provider preferences.
Recipe
One client per process; one function that accepts messages + logical model config.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
def chat(model: str, messages: list[dict], **openrouter_extra):
return client.chat.completions.create(
model=model,
messages=messages,
extra_body=openrouter_extra or None,
)When to reach for this:
- Python services and notebooks
- Node/TypeScript agents and Next.js route handlers (server-side only)
- Shared helpers used by LangGraph nodes, tools, or eval harnesses
- Teams standardizing on the OpenAI SDK instead of raw HTTP
Working Example
Python - non-streaming completion
import os
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
resp = client.chat.completions.create(
model="anthropic/claude-sonnet-4.5", # verify at build
messages=[
{"role": "system", "content": "Answer in one short paragraph."},
{"role": "user", "content": "What is model routing?"},
],
temperature=0.2,
extra_headers={
"HTTP-Referer": "https://example.com",
"X-OpenRouter-Title": "sdk-recipe",
},
extra_body={
"models": ["openai/gpt-4o-mini"], # fallbacks; verify at build
"provider": {"sort": "throughput"},
},
)
print(resp.model)
print(resp.choices[0].message.content)
print(resp.usage)TypeScript - non-streaming completion
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY,
defaultHeaders: {
"HTTP-Referer": "https://example.com",
"X-OpenRouter-Title": "sdk-recipe",
},
});
const resp = await client.chat.completions.create({
model: "anthropic/claude-sonnet-4.5", // verify at build
messages: [
{ role: "system", content: "Answer in one short paragraph." },
{ role: "user", content: "What is model routing?" },
],
temperature: 0.2,
// OpenRouter extensions (types may not include them):
// @ts-expect-error OpenRouter-only fields
models: ["openai/gpt-4o-mini"],
// @ts-expect-error OpenRouter-only fields
provider: { sort: "throughput" },
});
console.log(resp.model);
console.log(resp.choices[0].message.content);
console.log(resp.usage);Python - streaming deltas
stream = client.chat.completions.create(
model="openai/gpt-4o-mini", # verify at build
messages=[{"role": "user", "content": "List three agent tools."}],
stream=True,
)
for chunk in stream:
text = chunk.choices[0].delta.content or ""
if text:
print(text, end="", flush=True)TypeScript - streaming deltas
const stream = await client.chat.completions.create({
model: "openai/gpt-4o-mini", // verify at build
messages: [{ role: "user", content: "List three agent tools." }],
stream: true,
});
for await (const chunk of stream) {
const text = chunk.choices[0]?.delta?.content ?? "";
if (text) process.stdout.write(text);
}What this demonstrates:
- Identical product behavior from Python and TypeScript
- Standard OpenAI parameters still work (
messages,temperature,stream) - OpenRouter extensions ride alongside without a second HTTP client
- Resolved
modelandusageare available after the call
Deep Dive
Install
# Python
pip install openai
# TypeScript / Node
npm install openaiPin versions in your lockfile; verify current major at build.
Parameter map
| Goal | OpenAI SDK field | OpenRouter note |
|---|---|---|
| Host | base_url / baseURL | https://openrouter.ai/api/v1 |
| Auth | api_key / apiKey | OpenRouter key |
| Model | model | provider/model slug |
| Fallbacks | extra_body.models (Py) / models (TS) | Ordered backups |
| Provider prefs | extra_body.provider | order, sort, ignore, zdr, ... |
| Streaming | stream=True / stream: true | SSE under the hood |
| Attribution | extra_headers / defaultHeaders | Optional |
Tool calls
Function calling works through the same Chat Completions shapes when the selected model/provider supports tools.
OpenRouter routes tool requests toward capable endpoints when possible (verify tool-routing behavior at build).
Always re-test tool schemas after changing model policy.
Official OpenRouter SDKs (optional)
# pip install openrouter (verify package name at build)
from openrouter import OpenRouter
import os
with OpenRouter(api_key=os.environ["OPENROUTER_API_KEY"]) as client:
response = client.chat.send(
model="openai/gpt-4o-mini", # verify at build
messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content)// npm install @openrouter/sdk (verify package at build)
import { OpenRouter } from "@openrouter/sdk";
const openrouter = new OpenRouter({
apiKey: process.env.OPENROUTER_API_KEY!,
});
const completion = await openrouter.chat.send({
model: "openai/gpt-4o-mini", // verify at build
messages: [{ role: "user", content: "Hello" }],
});
console.log(completion.choices[0].message.content);Prefer these when you want first-class types for OpenRouter-only features.
Prefer the OpenAI SDK when the rest of the monorepo already standardizes on it.
Error handling sketch
from openai import APIStatusError
try:
resp = client.chat.completions.create(
model="openai/gpt-4o-mini",
messages=[{"role": "user", "content": "hi"}],
)
except APIStatusError as e:
# 401 auth, 402 credits, 429 rate limit, 503 no provider, ...
print(e.status_code, e.response.text if e.response is not None else e.message)
raiseHonor Retry-After on 429/503 when present.
Gotchas
- Bare OpenAI model ids without provider prefix. OpenRouter expects catalog slugs such as
openai/gpt-4o-mini. Fix: use full slugs from openrouter.ai/models. - Passing OpenRouter fields without
extra_bodyin Python. They may be dropped or rejected. Fix: putmodels/providerinextra_body. - TypeScript compile errors on extension fields. Types lag product. Fix: narrow cast or
@ts-expect-errorwith a comment. - Browser bundles with secret keys. Keys leak. Fix: server-only modules.
- Assuming tool-call message shapes are identical across every model. Providers differ. Fix: golden tool-call tests per policy tier.
- Ignoring
resp.modelafter fallbacks. Cost and quality retros break. Fix: log requested vs resolved model every time. - Mixing Responses API beta and Chat Completions casually. Different skins. Fix: stick to one API shape per code path until you need the other.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| OpenAI SDK (this page) | Shared OpenAI ecosystem | You need every OpenRouter admin API typed |
| Official OpenRouter SDK | Greenfield OpenRouter apps | Team forbids new deps |
| Vercel AI SDK + OpenRouter provider | Web UI streaming stacks | Pure Python workers |
| LangChain / LlamaIndex chat models | Framework agents | You only need a 20-line client |
| Raw HTTP | Debugging | Large production surface without retries |
FAQs
Which SDK version should I pin?
Pin the latest stable OpenAI SDK your framework supports and re-verify OpenRouter compatibility at build.
Can I use AsyncOpenAI?
Yes.
from openai import AsyncOpenAI
client = AsyncOpenAI(base_url="https://openrouter.ai/api/v1", api_key=...)How do I send provider.order with TypeScript?
Include a provider object on the create call (may need a type assertion) matching OpenRouter's JSON schema.
Does `max_tokens` still work?
Usually yes as a Chat Completions parameter.
Some models use related fields; check OpenRouter parameter docs for the model family at build.
How do I read the generation id?
OpenRouter may expose generation identifiers via response fields or headers such as X-Generation-Id (verify at build).
Log them for support tickets.
Can one client instance call many models?
Yes.
Model is per-request; base URL and key stay fixed.
Is Python 3.9 enough?
Use the Python version required by your pinned openai package (3.11+ recommended).
How do embeddings work?
OpenRouter exposes embeddings endpoints separately.
Configure the same base URL/key, then call the embeddings API for supported models (verify catalog at build).
Should eval harnesses use the same helper as production?
Yes.
Shared clients keep slug and header bugs out of "tests green, prod red" traps.
What about structured outputs?
Use OpenRouter/model-supported response_format or tool schemas, then validate with Pydantic/Zod in your code regardless of provider.
Related
- Authenticating and Configuring the OpenRouter Base URL - client construction detail
- Streaming Responses Through OpenRouter - stream edge cases
- Model Slugs and Naming Conventions on OpenRouter - slug reference
- Switching Models by Config, Not Code: A Routing Pattern - keep slugs out of call sites
- OpenRouter Setup & Routing Basics - minimal first call
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.