Authenticating and Configuring the OpenRouter Base URL
Pointing an OpenAI-compatible client at OpenRouter is mostly three settings: base URL, API key, and model slug.
Get those wrong and every higher-level agent framework fails in confusing ways.
Summary
OpenRouter authenticates with a Bearer API key and exposes an OpenAI-compatible root at https://openrouter.ai/api/v1 (verify at build).
Configure your SDK once, inject the key from the environment, and keep optional attribution headers separate from credentials.
Recipe
Create a single shared client factory that sets base URL + key so agent code never reimplements auth.
import os
from openai import OpenAI
OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
def openrouter_client() -> OpenAI:
key = os.environ.get("OPENROUTER_API_KEY")
if not key:
raise RuntimeError("OPENROUTER_API_KEY is not set")
return OpenAI(
base_url=OPENROUTER_BASE_URL,
api_key=key,
default_headers={
"HTTP-Referer": os.environ.get("APP_URL", "https://localhost"),
"X-OpenRouter-Title": os.environ.get("APP_NAME", "agent-app"),
},
)When to reach for this:
- Migrating existing OpenAI SDK code
- Centralizing gateway config for many agent call sites
- Separating dev and prod keys by environment
- Teaching teammates one copy-paste integration path
Working Example
Python (OpenAI SDK)
import os
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
completion = client.chat.completions.create(
model="openai/gpt-4o-mini", # verify at build
messages=[
{"role": "system", "content": "Be concise."},
{"role": "user", "content": "What is OpenRouter in one sentence?"},
],
extra_headers={
"HTTP-Referer": "https://example.com",
"X-OpenRouter-Title": "auth-recipe",
},
)
print(completion.choices[0].message.content)TypeScript (OpenAI SDK)
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": "auth-recipe",
},
});
const completion = await client.chat.completions.create({
model: "openai/gpt-4o-mini", // verify at build
messages: [{ role: "user", content: "What is OpenRouter in one sentence?" }],
});
console.log(completion.choices[0].message.content);Raw HTTP (sanity check)
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"openai/gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}'What this demonstrates:
- Base URL replaces the OpenAI host, not the whole SDK surface
- Auth is a Bearer token from an OpenRouter-issued key
- Optional headers are not substitutes for
Authorization - Model strings must be OpenRouter slugs
Deep Dive
Auth model
| Piece | Value |
|---|---|
| Header | Authorization: Bearer <OPENROUTER_API_KEY> |
| Key creation | openrouter.ai keys UI (verify at build) |
| Optional credit limits | Per-key spend caps in the dashboard |
| Optional attribution | HTTP-Referer, X-OpenRouter-Title |
If a key is exposed, revoke it in the dashboard and create a new one.
OpenRouter participates in secret scanning programs; treat email alerts as incidents.
Base URL pitfalls
| Mistake | Symptom |
|---|---|
Trailing path typos (/v1/v1) | 404 or opaque client errors |
| Leaving the OpenAI default host | 401 against wrong product / wrong key type |
| Using browser-exposed keys | Key theft and runaway spend |
| Mixing Anthropic Messages base path without docs | Wrong skin / wrong fields |
For OpenAI Chat Completions compatibility, use https://openrouter.ai/api/v1.
Other skins (Anthropic Messages, Responses API beta) may use related roots - verify docs before wiring.
Official OpenRouter SDKs
OpenRouter also ships first-party clients (openrouter Python package, @openrouter/sdk TypeScript) that take apiKey without manually setting base URL.
They are fine for greenfield work.
This page focuses on the OpenAI SDK path because most agent stacks already depend on it.
Environment matrix
# .env.example (never store real secrets in repo)
OPENROUTER_API_KEY=
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
APP_URL=https://localhost
APP_NAME=my-agent
DEFAULT_MODEL=openai/gpt-4o-miniLoad with your platform's secret system in production.
Do not bake keys into container images or frontend bundles.
Gotchas
- Using an OpenAI platform key against OpenRouter. Auth fails. Fix: create an OpenRouter key.
- Forgetting
base_url/baseURL. Requests hit OpenAI and fail or bill the wrong account. Fix: shared client factory. - Putting the key in the
modelfield or query string. Not supported for normal chat auth. Fix: Authorization header / SDKapiKey. - Assuming attribution headers are required. They are optional. Fix: treat them as ranking metadata only.
- Committing
.envwith live keys. Immediate rotate. Fix: gitignore + secret scanning. - One global prod key for every contractor. Blast radius is huge. Fix: scoped keys per app/env with credit limits.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| OpenAI SDK + base URL (this page) | Existing OpenAI-shaped code | You want first-party OpenRouter types only |
| Official OpenRouter SDK | New services, full API surface | Team standard is already OpenAI SDK |
Raw fetch / requests | Scripts, debugging | Large apps needing retries and types |
| Framework plugins (LangChain, Vercel AI, etc.) | Framework-native apps | You have not verified base URL mapping yet |
| Direct provider APIs | Single-provider lock-in is OK | You need multi-model routing |
FAQs
Is the API key the same as an OpenAI key?
No.
Create keys in the OpenRouter dashboard even if you only call OpenAI-hosted models through OpenRouter.
Can I put the key only in `OPENAI_API_KEY`?
Yes if you also set OPENAI_BASE_URL / client base_url to OpenRouter and your code reads those variables.
Prefer an explicit OPENROUTER_API_KEY name to avoid sending the wrong key to the wrong host.
Do I need to set organization headers?
Not for standard OpenRouter chat completions.
Use OpenRouter-documented headers for your product version.
What is the difference between management keys and inference keys?
Inference keys call models.
Management keys (where offered) administer keys and org settings - keep them off application servers if possible (verify product docs at build).
How do credit limits interact with auth?
A valid key with exhausted credits returns payment-required style errors (402).
Auth succeeded; billing did not.
Can frontends call OpenRouter directly?
Avoid embedding secret keys in browsers.
Proxy through your backend or use a documented OAuth / user-key flow if you deliberately expose user-owned keys.
Where do I set provider order while authenticating?
Provider preferences belong in the request body (provider object), not in auth headers.
Auth only proves who pays and who is allowed to call.
Does TLS pinning or special CA config matter?
Use normal public HTTPS.
Corporate proxies that MITM TLS need their CA trust store configured on the client.
Why does my request work in curl but not the SDK?
Usually a wrong base URL, double /v1, or env var not loaded in the process that runs the SDK.
Print the client configuration in a safe debug path (never print the full key).
Are HTTP-Referer values validated as real sites?
They are for attribution and rankings.
They are not a substitute for auth and should not contain secrets.
Related
- OpenRouter Setup & Routing Basics - first end-to-end call
- Calling OpenRouter from Python and TypeScript SDKs - fuller call recipes
- Troubleshooting Common OpenRouter Setup Errors - 401 / 402 / 404 checklist
- How Model Routing Actually Works Under an OpenRouter Request - what happens after auth
- OpenRouter Setup & Routing Best Practices - key hygiene practices
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.