SDK recipes
Calling the gateway from every client worth naming, including the agent runtimes.
AI/LLM: this page is available in plain markdown at /docs/resources/ai-gateway/sdks.md
Every snippet below uses anthropic/claude-opus-5. Swap in any id from the
model list — the
sample browser does it for you.
Two variables are already in your .env:
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
OPENROUTER_API_KEY=sk-or-v1-9c4e17b2…Which client reaches which model
The gateway exposes three surfaces, and that decides what each client can call.
| Surface | Base URL | Reaches |
|---|---|---|
| OpenAI-compatible | https://openrouter.ai/api/v1 | Every model |
| Anthropic Messages | https://openrouter.ai/api | Every model |
| Native | via @openrouter/ai-sdk-provider | Every model, plus routing controls |
All three reach the whole catalogue, so the OpenAI SDK calls a Claude model and the Anthropic SDK calls a Gemini one. The gateway maps the model id and forwards to whichever provider actually serves it; the protocol you speak to it in is your choice, not the model's.
The exception is a provider SDK whose protocol the gateway does not speak at all — Google's, Cohere's, Amazon's. Those reach nothing here, whatever key they hold. Use one of the three surfaces above instead.
What each recipe below does care about is which variables it reads.
OPENROUTER_* is written by every one of these commands; the OPENAI_* and
ANTHROPIC_* pairs are written when you ask for that provider by name.
The recipes
OpenAI SDK (Node)
npm i openai
import OpenAI from "openai";
// Both variables are in .env already — the SDK reads them itself.
const client = new OpenAI();
const response = await client.chat.completions.create({
model: "anthropic/claude-opus-5",
messages: [{ role: "user", content: "Draft a status report from these logs." }],
});
console.log(response.choices[0]?.message.content);OpenAI SDK (Python)
pip install openai
from openai import OpenAI
# Reads OPENAI_API_KEY and OPENAI_BASE_URL from the environment.
client = OpenAI()
response = client.chat.completions.create(
model="anthropic/claude-opus-5",
messages=[{"role": "user", "content": "Draft a status report from these logs."}],
)
print(response.choices[0].message.content)Anthropic SDK (Node)
npm i @anthropic-ai/sdk
import Anthropic from "@anthropic-ai/sdk";
// ANTHROPIC_BASE_URL points at the gateway's Anthropic-compatible surface,
// which is https://openrouter.ai/api — the SDK appends /v1/messages itself.
const anthropic = new Anthropic();
const message = await anthropic.messages.create({
model: "anthropic/claude-opus-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Draft a status report from these logs." }],
});
console.log(message.content);Anthropic SDK (Python)
pip install anthropic
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="anthropic/claude-opus-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Draft a status report from these logs."}],
)
print(message.content)Vercel AI SDK v7
npm i ai @openrouter/ai-sdk-provider
import { streamText } from "ai";
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
const openrouter = createOpenRouter({
apiKey: process.env.OPENROUTER_API_KEY,
});
const result = streamText({
model: openrouter("anthropic/claude-opus-5"),
prompt: "Draft a status report from these logs.",
});
for await (const chunk of result.textStream) process.stdout.write(chunk);The dedicated provider reaches every model on the gateway, including ones no OpenAI-shaped client can call.
fetch (no SDK)
const response = await fetch(
`${process.env.OPENROUTER_BASE_URL}/chat/completions`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "anthropic/claude-opus-5",
messages: [{ role: "user", content: "Draft a status report from these logs." }],
}),
},
);
const body = await response.json();
console.log(body.choices[0].message.content);curl
curl "$OPENROUTER_BASE_URL/chat/completions" \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-opus-5",
"messages": [{ "role": "user", "content": "Draft a status report from these logs." }]
}'LangChain
npm i @langchain/openai
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({
model: "anthropic/claude-opus-5",
apiKey: process.env.OPENROUTER_API_KEY,
configuration: { baseURL: "https://openrouter.ai/api/v1" },
});
const response = await model.invoke("Draft a status report from these logs.");
console.log(response.content);Claude Code
export OPENROUTER_API_KEY="sk-or-v1-…" # from your .env
export ANTHROPIC_BASE_URL="https://openrouter.ai/api"
export ANTHROPIC_AUTH_TOKEN="$OPENROUTER_API_KEY"
export ANTHROPIC_API_KEY="" # explicitly blank
export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1
export ANTHROPIC_DEFAULT_SONNET_MODEL="anthropic/claude-opus-5"
claudeClaude Code does not read .env — the fenced block will not reach it. Export these in your shell, or put them under `env` in .claude/settings.local.json. Run /logout first if you were signed in with Anthropic credentials.
Hermes agent
# ~/.hermes/config.yaml
model:
provider: openrouter
default: anthropic/claude-opus-5
# ~/.hermes/.env
# OPENROUTER_API_KEY=sk-or-v1-…Hermes knows OpenRouter's endpoint from the provider name, so it needs no base URL. `hermes model` does this interactively.
Streaming and tool use
Both survive the round trip on every surface. The Anthropic Messages surface passes extended thinking blocks and native tool use through unchanged, which is what lets an agent runtime like Claude Code work against it rather than merely connect to it.
Provider routing
The native provider accepts OpenRouter's routing controls — pinning a provider, ordering fallbacks, requiring a data policy:
const result = streamText({
model: openrouter("anthropic/claude-opus-5"),
prompt: "Draft a status report from these logs.",
providerOptions: {
openrouter: {
provider: { order: ["anthropic", "google-vertex"], allow_fallbacks: true },
},
},
});The same object works over the OpenAI-compatible surface as an extra body field,
which most SDKs expose as extraBody or extra_body.
Next
- Models — the full list, with prices
- Providers and keys — what the CLI writes for each