skip to content

How do you point the OpenAI SDK at OpenRouter, and how are models named there?

level: juniorimportance: must knowfreq 78%

answer

  1. One endpoint, one key, many vendors
  2. Reuse the OpenAI client, change base URL
  3. Bearer token in Authorization
  4. Model names are namespaced
  5. vendor/model, e.g. anthropic/claude-sonnet-4.5

basics

~10 s

Set the OpenAI client's base URL to https://openrouter.ai/api/v1 and send your OpenRouter key as a bearer token. Models are addressed by a vendor-prefixed slug such as anthropic/claude-sonnet-4.5, so one key reaches every vendor.

solid answer

~40 s

OpenRouter speaks the OpenAI Chat Completions wire format, so the usual client works unchanged: construct it with `base_url="https://openrouter.ai/api/v1"` and `api_key=<your OpenRouter key>`, which the SDK sends as `Authorization: Bearer …`. The one thing that changes is the `model` field: instead of a bare name you pass a namespaced slug of the form `vendor/model`, for example `openai/gpt-4o`, `anthropic/claude-sonnet-4.5` or `meta-llama/llama-3.3-70b-instruct`. That slug is the only thing that has to change to switch vendors — the gateway translates your OpenAI-shaped request into whatever the upstream provider actually speaks and translates the reply back into an OpenAI-shaped `choices[].message` response. Beyond `POST /api/v1/chat/completions` there is a `GET /api/v1/models` catalog listing every slug with its context length and supported parameters. You can also call the endpoint with plain HTTP; the SDK is a convenience, not a requirement.

code

python · 13 lines
python
from openai import OpenAI

client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key="<OPENROUTER_API_KEY>",
)

resp = client.chat.completions.create(
    model="anthropic/claude-sonnet-4.5",
    messages=[{"role": "user", "content": "Say hi in five words."}],
    max_tokens=64,
)
print(resp.choices[0].message.content)

go deeper

for a junior

Be able to say the three concrete things: base URL https://openrouter.ai/api/v1, an OpenRouter key as a bearer token, and a model slug shaped vendor/model. Showing you can do it with the plain OpenAI client is enough here.

for a middle

Explain why the drop-in works at all — the gateway accepts the OpenAI Chat Completions shape and translates both directions — and describe what the normalised response and stream look like coming back.

for a senior

Talk about running this in production: pulling the model catalog instead of hardcoding slugs, handling the SSE keep-alive comments, and not assuming that a slug swap preserves capabilities your code depends on.

for a principal

Frame the slug as a configuration value, not a constant. Owning model selection as config with a validation step against the catalog is what lets an organisation change models without a code deploy.

## What OpenRouter is doing OpenRouter is a gateway: a single HTTPS endpoint, a single API key, and a catalog of hundreds of models from dozens of vendors behind it. The design bet is that most applications only need the common denominator of chat — a list of messages in, a message out, optionally streamed, optionally with tool calls — and that the OpenAI Chat Completions request/response shape is the de-facto lingua franca for that. So OpenRouter accepts that shape, converts it into whatever the upstream vendor really speaks (Anthropic's Messages API, Google's `generateContent`, a vLLM deployment, and so on), and converts the answer back. ## Wiring an existing client Because the wire format matches, you do not need a new SDK. Every OpenAI client library lets you override the base URL, and that is the whole integration: - base URL: `https://openrouter.ai/api/v1` - credential: your OpenRouter key, sent as the standard `Authorization: Bearer <key>` header (the SDK does this for you when you pass it as the api key) - endpoint: `POST /api/v1/chat/completions`, with the familiar `model`, `messages`, `temperature`, `max_tokens`, `stream` fields That also means curl works, and so does any framework that already targets an OpenAI-compatible base URL. There is no OpenRouter-only client you must adopt. ## Model slugs are namespaced The field that differs is `model`. OpenAI's own API takes a bare name because there is only one vendor; OpenRouter serves many, so the identifier is namespaced as `author/model-slug`. Examples: `openai/gpt-4o`, `anthropic/claude-sonnet-4.5`, `google/gemini-2.0-flash-001`, `mistralai/mistral-large`, `meta-llama/llama-3.3-70b-instruct`. Some slugs carry a suffix after a colon that selects a variant of the same model rather than a different model. Two consequences follow. First, a bare `gpt-4o` is not a valid OpenRouter model id — the vendor prefix is part of the name, and omitting it is the single most common first-request error. Second, switching vendors is a one-string change in your code, which is exactly the property people adopt the gateway for: the same `messages` array, the same parsing code, a different slug. ## What comes back The response is normalised to the OpenAI shape: an object with `id`, `model`, `choices[]` where each choice has a `message` with `role` and `content` (plus `tool_calls` when the model called a tool) and a `finish_reason`, and a `usage` object with `prompt_tokens`, `completion_tokens` and `total_tokens`. Streaming works the same way as OpenAI's — `"stream": true` yields Server-Sent Events whose `data:` lines carry `choices[].delta` fragments and terminate with `data: [DONE]`. The response also records which model actually served the request, which matters because the served model is not always the exact one you named. One streaming detail bites hand-rolled parsers: while a request is queued upstream, the gateway may emit SSE *comment* lines (lines starting with `:`) purely as keep-alive so proxies do not time the connection out. The SSE specification says comments are to be ignored, and every real SDK does; a naive `split("data: ")` parser that tries to JSON-decode every line will blow up on them. ## Discovering the catalog `GET /api/v1/models` returns the machine-readable catalog: each entry carries the slug, a context length, the modalities it accepts, and the list of request parameters it honours. Use it rather than hardcoding assumptions — the catalog changes weekly as vendors ship and retire models. ## Common mistakes Passing an OpenAI key to OpenRouter (it is a distinct credential, issued by OpenRouter), forgetting the vendor prefix, assuming a vendor-specific SDK such as `anthropic` will work against the gateway (it will not — the gateway's chat surface is OpenAI-shaped, so use an OpenAI-shaped client), and assuming everything that works for one slug works for the next. The transport is uniform; the models behind it are not.

  • What has to change in your code to move a call from an OpenAI model to a Claude model on OpenRouter?
    In the normal case, only the `model` string. The messages array, the parameters, the streaming loop and the response parsing all stay put, because the gateway translates the request into the vendor's native format and normalises the reply back into the OpenAI shape. What can still break is capability: the new model may not honour a parameter or a modality you were relying on, so check the catalog rather than assuming parity.
  • Can you use the vendor's own SDK — say the Anthropic client — against OpenRouter?
    No, not for the chat surface. OpenRouter's endpoint accepts the OpenAI Chat Completions request shape, not Anthropic's Messages shape, so a native Anthropic client would send `system` as a top-level field and expect content blocks back. Use an OpenAI-compatible client, or plain HTTP, and let the gateway do the translation to whichever vendor you name in the slug.
  • How do you consume a streamed response from OpenRouter?
    Set `"stream": true` and read the SSE body exactly as you would from OpenAI: each `data:` line is a JSON chunk whose `choices[].delta.content` you append, and the stream ends with `data: [DONE]`. Ignore SSE comment lines beginning with a colon — OpenRouter sends them as keep-alives while a request waits upstream, and a parser that tries to JSON-decode them will crash.

saying these in an interview costs you the question

  • Thinking your OpenAI API key works against OpenRouter
  • Passing a bare model name without the vendor prefix
  • Believing OpenRouter needs its own proprietary SDK
  • Assuming every OpenAI parameter behaves identically on every slug
  • Crashing on SSE keep-alive comment lines while streaming

context