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:

.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.

SurfaceBase URLReaches
OpenAI-compatiblehttps://openrouter.ai/api/v1Every model
Anthropic Messageshttps://openrouter.ai/apiEvery model
Nativevia @openrouter/ai-sdk-providerEvery 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"

claude

Claude 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:

routing.ts
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