OpenRouter API
OpenRouter is a unified API in front of 300+ LLMs. Schema is OpenAI-compatible at /api/v1/chat/completions plus OpenRouter-only extensions (models[], provider, plugins, reasoning, cache_control, presets, :suffix shortcuts). Auth is Authorization: Bearer <OPENROUTER_API_KEY>.
Base URL & auth
POST https://openrouter.ai/api/v1/chat/completions
Authorization: Bearer $OPENROUTER_API_KEY
Content-Type: application/json
Optional attribution headers (enable leaderboard ranking):
HTTP-Referer: <YOUR_SITE_URL>
X-OpenRouter-Title: <YOUR_SITE_NAME> (X-Title also accepted)
X-OpenRouter-Categories: <comma,separated>
API keys are created at https://openrouter.ai/keys. Each key supports a credit limit and works for OAuth flows. OpenRouter is a GitHub secret-scanning partner — leaked sk-or-... keys trigger email notification.
Three integration paths
| Path |
Package |
Best for |
| Raw HTTP |
none |
Any language, no deps |
| Client SDK |
@openrouter/sdk (npm) / openrouter (pip) |
Type-safe thin wrapper over REST |
| Agent SDK |
@openrouter/agent (npm only, TS) |
Multi-turn loops, tool execution, state via callModel |
| OpenAI SDK |
openai with baseURL: https://openrouter.ai/api/v1 |
Drop-in for existing OpenAI code |
Use OpenRouter SDKs by default. Reference the OpenAI SDK only when user explicitly asks.
Quickstart (TS SDK)
import OpenRouter from '@openrouter/sdk';
const client = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
const completion = await client.chat.send({
model: 'openai/gpt-5.2',
messages: [{ role: 'user', content: 'What is the meaning of life?' }],
});
console.log(completion.choices[0].message.content);
Quickstart (Python SDK)
from openrouter import OpenRouter
import os
with OpenRouter(api_key=os.getenv("OPENROUTER_API_KEY")) as client:
response = client.chat.send(
model="openai/gpt-5.2",
messages=[{"role": "user", "content": "What is the meaning of life?"}],
)
print(response.choices[0].message.content)
Quickstart (raw HTTP)
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"openai/gpt-5.2","messages":[{"role":"user","content":"hi"}]}'
Model identifiers
Format: <provider>/<model> (e.g., anthropic/claude-sonnet-4.6, openai/gpt-5.2, google/gemini-3-flash-preview, meta-llama/llama-3.3-70b-instruct, deepseek/deepseek-v3.2). Browse all: https://openrouter.ai/models. List via API: GET /api/v1/models?supported_parameters=tools&output_modalities=text.
Suffix shortcuts
| Suffix |
Effect |
Equivalent |
:nitro |
Sort providers by throughput |
provider.sort = "throughput" |
:floor |
Sort providers by lowest price |
provider.sort = "price" |
:online |
Enable web plugin (deprecated — prefer openrouter:web_search server tool) |
plugins: [{id: "web"}] with openrouter/auto |
:free |
Free-tier variants (rate-limited, daily caps) |
— |
Combinable: openai/gpt-oss-20b:free:online.
Special model slugs
openrouter/auto — Auto Router (NotDiamond-powered prompt-aware model selection)
openrouter/bodybuilder — natural-language → structured request bodies for parallel multi-model fan-out (free)
@preset/<slug> — invoke a saved preset (system prompt + params + provider rules) by name. Configure at https://openrouter.ai/settings/presets. Also usable as preset: "<slug>" field or model: "openai/gpt-4@preset/<slug>" (combined). Per-request params shallow-merge over preset config.
Request body — top-level shape
type Request = {
// Either messages or prompt
messages?: Message[];
prompt?: string;
model?: string; // omit = user default
models?: string[]; // model fallbacks in order
stream?: boolean;
stop?: string | string[];
response_format?: ResponseFormat; // json_object | json_schema
tools?: Tool[];
tool_choice?: ToolChoice;
parallel_tool_calls?: boolean; // default true
// OpenRouter-only
provider?: ProviderPreferences; // see references/routing.md
plugins?: Plugin[]; // web, file-parser, response-healing, context-compression, auto-router
reasoning?: ReasoningConfig; // see references/reasoning.md
preset?: string;
user?: string; // stable end-user ID for abuse detection
debug?: { echo_upstream_body?: boolean }; // streaming only
// Standard sampling — see references/api-reference.md
max_tokens?: number;
temperature?: number;
top_p?: number; top_k?: number; min_p?: number; top_a?: number;
frequency_penalty?: number; presence_penalty?: number; repetition_penalty?: number;
seed?: number; logit_bias?: Record<number, number>;
logprobs?: boolean; top_logprobs?: number;
prediction?: { type: 'content'; content: string };
verbosity?: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
};
Unsupported parameters for the chosen model are silently ignored; the rest forward to the upstream. To require providers that support all parameters, set provider.require_parameters: true.
Response shape
{
"id": "gen-xxxxxxxxxxxxxx",
"model": "openai/gpt-4o",
"object": "chat.completion",
"choices": [{
"finish_reason": "stop",
"native_finish_reason": "stop",
"message": { "role": "assistant", "content": "Hello there!" }
}],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 4,
"total_tokens": 14,
"prompt_tokens_details": { "cached_tokens": 0, "cache_write_tokens": 0 },
"completion_tokens_details": { "reasoning_tokens": 0 },
"cost": 0.00014
}
}
Streaming chunks have delta instead of message and object: "chat.completion.chunk". The final chunk before data: [DONE] carries usage with empty choices.
finish_reason is normalized to one of: tool_calls, stop, length, content_filter, error. Raw provider value is in native_finish_reason. The generation ID is also returned in the X-Generation-Id response header.
Errors
{ "error": { "code": 400, "message": "...", "metadata": { ... } } }
| Code |
Meaning |
| 400 |
Bad request / invalid params / CORS |
| 401 |
Invalid API key / expired OAuth |
| 402 |
Insufficient credits — add credits |
| 403 |
Moderation flagged input |
| 408 |
Request timeout |
| 429 |
Rate limited |
| 502 |
Upstream model down / invalid response |
| 503 |
No provider meets routing requirements |
Mid-stream errors (HTTP 200 already sent) arrive as a final SSE chunk with top-level error and a choice with finish_reason: "error". See streaming.md.
Feature map
| Feature |
Trigger |
Reference |
| Sampling params, full request/response schema, debug option |
any chat call |
references/api-reference.md |
| SSE streaming, cancellation, mid-stream errors |
stream: true |
references/streaming.md |
| Function/tool calling, agentic loop, parallel tools, streaming tool calls, MCP-to-OpenAI conversion |
tools: [...] |
references/tool-calling.md |
Structured outputs (json_schema, json_object) |
response_format |
references/structured-outputs.md |
| Reasoning/thinking tokens (effort, max_tokens, exclude) |
reasoning: {...} |
references/reasoning.md |
Prompt caching (auto, cache_control, sticky routing) |
cache_control or supported provider |
references/prompt-caching.md |
| Provider routing (sort, order, only, ignore, max_price, throughput, ZDR) |
provider: {...} |
references/routing.md |
| Model fallbacks, Auto Router, Body Builder, presets |
models[], openrouter/auto, @preset/... |
references/routing.md |
| Multimodal — images, PDFs, audio, video, image-gen, TTS |
image_url/file/input_audio content parts |
references/multimodal.md |
Plugins — web, file-parser, response-healing, context-compression, auto-router |
plugins: [...] |
references/plugins.md |
openrouter:web_search server tool, :online shortcut, citations annotations |
tools: [{type:'openrouter:web_search'}] |
references/web-search.md |
Usage accounting, costs, /generation, key/credits, rate limits |
usage field, GET /api/v1/key |
references/usage-and-limits.md |
| BYOK provider keys, OAuth PKCE, app attribution |
cbat_-style provider keys configured at /settings/integrations |
references/byok-and-oauth.md |
Always-fresh docs
OpenRouter exposes Markdown of any docs page by appending .md to the URL, plus aggregated llms-full.txt files per section. When reference material here is insufficient for a specific edge case, fetch the latest from:
Append .md to any docs URL for clean Markdown of that single page.
Common pitfalls
prompt and messages are mutually exclusive — pick one.
- The
tools array must be repeated on every turn of an agentic loop. The router validates the schema each call. Forgetting to append the assistant's tool-call message to history before the tool-result message breaks Anthropic models.
- Anthropic automatic caching (top-level
cache_control) only routes to the Anthropic provider — Bedrock and Vertex AI are excluded. Per-block cache_control works on all three.
max_tokens cap is context_length - prompt_length, not unbounded.
- OpenAI o-series and some reasoning models do not return reasoning tokens even when present internally.
reasoning.exclude: true controls visibility, not internal use.
debug.echo_upstream_body only works with stream: true and is dev-only — it can leak request internals.
:online shortcut is the deprecated web plugin routed through openrouter/auto. New code uses the openrouter:web_search server tool so the model decides when to search.
- Mid-stream errors keep HTTP 200 because headers were already sent. Detect via the top-level
error field on a chunk plus finish_reason: "error".
- BYOK keys always try first before OpenRouter shared capacity, regardless of
provider.order.
- Free-tier (
:free) rate limits are global per account — extra API keys do not multiply quota.
- The
usage: { include: true } and stream_options: { include_usage: true } parameters are deprecated — usage is always included now.
1---2name: openrouter-api3description: OpenRouter unified-LLM-API expertise. Use when code POSTs to `openrouter.ai/api/v1`, imports `@openrouter/sdk`, `openrouter` (Python), or `@openrouter/agent`; when the OpenAI SDK is pointed at `https://openrouter.ai/api/v1`; when the user asks about routing across hundreds of LLMs (OpenAI, Anthropic, Google, DeepSeek, Meta, xAI, Mistral, etc.) through one endpoint; when working with provider routing, model fallbacks (`models` array), the Auto Router (`openrouter/auto`), `:nitro`/`:floor`/`:online` model suffixes, presets (`@preset/...`), prompt caching with `cache_control`, structured outputs, tool calling, the `openrouter:web_search` server tool, reasoning tokens, multimodal (image/PDF/audio/video) inputs, BYOK provider keys, OAuth PKCE, the `openrouter:` plugins (`web`, `file-parser`, `response-healing`, `context-compression`), or generation/usage accounting via `/api/v1/generation` and `/api/v1/key`.4---56# OpenRouter API78OpenRouter is a unified API in front of 300+ LLMs. Schema is OpenAI-compatible at `/api/v1/chat/completions` plus OpenRouter-only extensions (`models[]`, `provider`, `plugins`, `reasoning`, `cache_control`, presets, `:suffix` shortcuts). Auth is `Authorization: Bearer <OPENROUTER_API_KEY>`.910## Base URL & auth1112```13POST https://openrouter.ai/api/v1/chat/completions14Authorization: Bearer $OPENROUTER_API_KEY15Content-Type: application/json16```1718Optional attribution headers (enable leaderboard ranking):19- `HTTP-Referer: <YOUR_SITE_URL>`20- `X-OpenRouter-Title: <YOUR_SITE_NAME>` (`X-Title` also accepted)21- `X-OpenRouter-Categories: <comma,separated>`2223API keys are created at <https://openrouter.ai/keys>. Each key supports a credit limit and works for OAuth flows. OpenRouter is a GitHub secret-scanning partner — leaked `sk-or-...` keys trigger email notification.2425## Three integration paths2627| Path | Package | Best for |28|------|---------|----------|29| Raw HTTP | none | Any language, no deps |30| Client SDK | `@openrouter/sdk` (npm) / `openrouter` (pip) | Type-safe thin wrapper over REST |31| Agent SDK | `@openrouter/agent` (npm only, TS) | Multi-turn loops, tool execution, state via `callModel` |32| OpenAI SDK | `openai` with `baseURL: https://openrouter.ai/api/v1` | Drop-in for existing OpenAI code |3334Use OpenRouter SDKs by default. Reference the OpenAI SDK only when user explicitly asks.3536### Quickstart (TS SDK)3738```typescript39import OpenRouter from '@openrouter/sdk';4041const client = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });4243const completion = await client.chat.send({44 model: 'openai/gpt-5.2',45 messages: [{ role: 'user', content: 'What is the meaning of life?' }],46});47console.log(completion.choices[0].message.content);48```4950### Quickstart (Python SDK)5152```python53from openrouter import OpenRouter54import os5556with OpenRouter(api_key=os.getenv("OPENROUTER_API_KEY")) as client:57 response = client.chat.send(58 model="openai/gpt-5.2",59 messages=[{"role": "user", "content": "What is the meaning of life?"}],60 )61 print(response.choices[0].message.content)62```6364### Quickstart (raw HTTP)6566```bash67curl https://openrouter.ai/api/v1/chat/completions \68 -H "Authorization: Bearer $OPENROUTER_API_KEY" \69 -H "Content-Type: application/json" \70 -d '{"model":"openai/gpt-5.2","messages":[{"role":"user","content":"hi"}]}'71```7273## Model identifiers7475Format: `<provider>/<model>` (e.g., `anthropic/claude-sonnet-4.6`, `openai/gpt-5.2`, `google/gemini-3-flash-preview`, `meta-llama/llama-3.3-70b-instruct`, `deepseek/deepseek-v3.2`). Browse all: <https://openrouter.ai/models>. List via API: `GET /api/v1/models?supported_parameters=tools&output_modalities=text`.7677### Suffix shortcuts7879| Suffix | Effect | Equivalent |80|--------|--------|------------|81| `:nitro` | Sort providers by throughput | `provider.sort = "throughput"` |82| `:floor` | Sort providers by lowest price | `provider.sort = "price"` |83| `:online` | Enable web plugin (deprecated — prefer `openrouter:web_search` server tool) | `plugins: [{id: "web"}]` with `openrouter/auto` |84| `:free` | Free-tier variants (rate-limited, daily caps) | — |8586Combinable: `openai/gpt-oss-20b:free:online`.8788### Special model slugs8990- `openrouter/auto` — Auto Router (NotDiamond-powered prompt-aware model selection)91- `openrouter/bodybuilder` — natural-language → structured request bodies for parallel multi-model fan-out (free)92- `@preset/<slug>` — invoke a saved preset (system prompt + params + provider rules) by name. Configure at <https://openrouter.ai/settings/presets>. Also usable as `preset: "<slug>"` field or `model: "openai/gpt-4@preset/<slug>"` (combined). Per-request params shallow-merge over preset config.9394## Request body — top-level shape9596```typescript97type Request = {98 // Either messages or prompt99 messages?: Message[];100 prompt?: string;101102 model?: string; // omit = user default103 models?: string[]; // model fallbacks in order104105 stream?: boolean;106 stop?: string | string[];107 response_format?: ResponseFormat; // json_object | json_schema108 tools?: Tool[];109 tool_choice?: ToolChoice;110 parallel_tool_calls?: boolean; // default true111112 // OpenRouter-only113 provider?: ProviderPreferences; // see references/routing.md114 plugins?: Plugin[]; // web, file-parser, response-healing, context-compression, auto-router115 reasoning?: ReasoningConfig; // see references/reasoning.md116 preset?: string;117 user?: string; // stable end-user ID for abuse detection118 debug?: { echo_upstream_body?: boolean }; // streaming only119120 // Standard sampling — see references/api-reference.md121 max_tokens?: number;122 temperature?: number;123 top_p?: number; top_k?: number; min_p?: number; top_a?: number;124 frequency_penalty?: number; presence_penalty?: number; repetition_penalty?: number;125 seed?: number; logit_bias?: Record<number, number>;126 logprobs?: boolean; top_logprobs?: number;127 prediction?: { type: 'content'; content: string };128 verbosity?: 'low' | 'medium' | 'high' | 'xhigh' | 'max';129};130```131132Unsupported parameters for the chosen model are silently ignored; the rest forward to the upstream. To require providers that support all parameters, set `provider.require_parameters: true`.133134## Response shape135136```json137{138 "id": "gen-xxxxxxxxxxxxxx",139 "model": "openai/gpt-4o",140 "object": "chat.completion",141 "choices": [{142 "finish_reason": "stop",143 "native_finish_reason": "stop",144 "message": { "role": "assistant", "content": "Hello there!" }145 }],146 "usage": {147 "prompt_tokens": 10,148 "completion_tokens": 4,149 "total_tokens": 14,150 "prompt_tokens_details": { "cached_tokens": 0, "cache_write_tokens": 0 },151 "completion_tokens_details": { "reasoning_tokens": 0 },152 "cost": 0.00014153 }154}155```156157Streaming chunks have `delta` instead of `message` and `object: "chat.completion.chunk"`. The final chunk before `data: [DONE]` carries `usage` with empty `choices`.158159`finish_reason` is normalized to one of: `tool_calls`, `stop`, `length`, `content_filter`, `error`. Raw provider value is in `native_finish_reason`. The generation ID is also returned in the `X-Generation-Id` response header.160161## Errors162163```json164{ "error": { "code": 400, "message": "...", "metadata": { ... } } }165```166167| Code | Meaning |168|------|---------|169| 400 | Bad request / invalid params / CORS |170| 401 | Invalid API key / expired OAuth |171| 402 | Insufficient credits — add credits |172| 403 | Moderation flagged input |173| 408 | Request timeout |174| 429 | Rate limited |175| 502 | Upstream model down / invalid response |176| 503 | No provider meets routing requirements |177178Mid-stream errors (HTTP 200 already sent) arrive as a final SSE chunk with top-level `error` and a choice with `finish_reason: "error"`. See [streaming.md](references/streaming.md).179180## Feature map181182| Feature | Trigger | Reference |183|---------|---------|-----------|184| Sampling params, full request/response schema, debug option | any chat call | [references/api-reference.md](references/api-reference.md) |185| SSE streaming, cancellation, mid-stream errors | `stream: true` | [references/streaming.md](references/streaming.md) |186| Function/tool calling, agentic loop, parallel tools, streaming tool calls, MCP-to-OpenAI conversion | `tools: [...]` | [references/tool-calling.md](references/tool-calling.md) |187| Structured outputs (`json_schema`, `json_object`) | `response_format` | [references/structured-outputs.md](references/structured-outputs.md) |188| Reasoning/thinking tokens (effort, max_tokens, exclude) | `reasoning: {...}` | [references/reasoning.md](references/reasoning.md) |189| Prompt caching (auto, `cache_control`, sticky routing) | `cache_control` or supported provider | [references/prompt-caching.md](references/prompt-caching.md) |190| Provider routing (sort, order, only, ignore, max_price, throughput, ZDR) | `provider: {...}` | [references/routing.md](references/routing.md) |191| Model fallbacks, Auto Router, Body Builder, presets | `models[]`, `openrouter/auto`, `@preset/...` | [references/routing.md](references/routing.md) |192| Multimodal — images, PDFs, audio, video, image-gen, TTS | `image_url`/`file`/`input_audio` content parts | [references/multimodal.md](references/multimodal.md) |193| Plugins — `web`, `file-parser`, `response-healing`, `context-compression`, `auto-router` | `plugins: [...]` | [references/plugins.md](references/plugins.md) |194| `openrouter:web_search` server tool, `:online` shortcut, citations annotations | `tools: [{type:'openrouter:web_search'}]` | [references/web-search.md](references/web-search.md) |195| Usage accounting, costs, `/generation`, key/credits, rate limits | `usage` field, `GET /api/v1/key` | [references/usage-and-limits.md](references/usage-and-limits.md) |196| BYOK provider keys, OAuth PKCE, app attribution | `cbat_`-style provider keys configured at `/settings/integrations` | [references/byok-and-oauth.md](references/byok-and-oauth.md) |197198## Always-fresh docs199200OpenRouter exposes Markdown of any docs page by appending `.md` to the URL, plus aggregated `llms-full.txt` files per section. When reference material here is insufficient for a specific edge case, fetch the latest from:201202- Quickstart: <https://openrouter.ai/docs/quickstart/llms-full.txt>203- API reference: <https://openrouter.ai/docs/api/reference/llms-full.txt>204- Features: <https://openrouter.ai/docs/guides/features/llms-full.txt>205- Routing: <https://openrouter.ai/docs/guides/routing/llms-full.txt>206- Plugins: <https://openrouter.ai/docs/guides/features/plugins/llms-full.txt>207- Multimodal: <https://openrouter.ai/docs/guides/overview/multimodal/llms-full.txt>208- Best practices (caching, reasoning): <https://openrouter.ai/docs/guides/best-practices/llms-full.txt>209- Auth (BYOK, OAuth): <https://openrouter.ai/docs/guides/overview/auth/llms-full.txt>210- Administration (usage accounting): <https://openrouter.ai/docs/guides/administration/llms-full.txt>211- Server tools (web search): <https://openrouter.ai/docs/guides/features/server-tools/llms-full.txt>212- OpenAPI spec: `https://openrouter.ai/openapi.yaml` or `.json`213214Append `.md` to any docs URL for clean Markdown of that single page.215216## Common pitfalls217218- **`prompt` and `messages` are mutually exclusive** — pick one.219- **The `tools` array must be repeated on every turn** of an agentic loop. The router validates the schema each call. Forgetting to append the assistant's tool-call message to history before the tool-result message breaks Anthropic models.220- **Anthropic automatic caching (top-level `cache_control`) only routes to the Anthropic provider** — Bedrock and Vertex AI are excluded. Per-block `cache_control` works on all three.221- **`max_tokens` cap is `context_length - prompt_length`**, not unbounded.222- **OpenAI o-series and some reasoning models do not return reasoning tokens** even when present internally. `reasoning.exclude: true` controls visibility, not internal use.223- **`debug.echo_upstream_body` only works with `stream: true`** and is dev-only — it can leak request internals.224- **`:online` shortcut is the deprecated `web` plugin** routed through `openrouter/auto`. New code uses the `openrouter:web_search` server tool so the model decides when to search.225- **Mid-stream errors keep HTTP 200** because headers were already sent. Detect via the top-level `error` field on a chunk plus `finish_reason: "error"`.226- **BYOK keys always try first** before OpenRouter shared capacity, regardless of `provider.order`.227- **Free-tier (`:free`) rate limits are global per account** — extra API keys do not multiply quota.228- **The `usage: { include: true }` and `stream_options: { include_usage: true }` parameters are deprecated** — usage is always included now.