# OpenRouter Guide: API, Pricing, Provider Routing and BYOK

> How OpenRouter works: one API for 400+ models, the 5.5% credit fee, provider routing, fallbacks, ZDR, prompt caching, BYOK and billing traps.
> Author: Roman Belov · Published: 2026-10-26 · Source: https://futurecraft.pro/blog/openrouter-guide/

OpenRouter is a cloud gateway to language models. One OpenAI-compatible API and one credit balance give you hundreds of models from different labs, from Claude and GPT to open Qwen, DeepSeek and Gemma. You pick a model with a string like `anthropic/claude-haiku-5.5`, and OpenRouter routes the request to one of the providers serving it. Inference prices carry no markup; the fee is charged when you top up. In its [August 19, 2026 announcement](https://openrouter.ai/blog/announcements/openrouter-is-joining-stripe/), OpenRouter cited 400+ models and 10+ million developers.

Your first request takes a `base_url` change. The trouble starts with money and routing. A $5 top-up costs $0.80 in fees. A "free model first, paid model second" fallback chain turns out to be paid in practice. The same open model runs in different quantizations, with different parameter sets, at different providers. And a request with `tool_choice` can land on a provider that silently ignores it.

This guide covers how the OpenRouter catalog and providers work, what a top-up really costs, how to control provider selection and fallback, what happens to your data and cache, and how to connect Claude Code, OpenCode and Codex CLI. API behavior was tested on November 7, 2026 with a key capped at $5: a Python script using only the standard library, `POST /api/v1/chat/completions`, a short prompt and `max_tokens: 30`. All tests together cost $0.0018. The top-up fee, BYOK, ZDR and Claude Code through OpenRouter weren't tested; those parts come from the documentation.

## What OpenRouter is and when you need it

OpenRouter handles four jobs. It gives you one API format for many labs: code written for the OpenAI SDK switches between Claude, GPT and Qwen by changing the model string. It keeps one balance and one bill instead of an account with every vendor. It fails over between providers of one model and, if you ask, between different models. And it shows spend by key, model and provider in one place.

| | OpenRouter | Direct provider API | LiteLLM Proxy |
|---|---|---|---|
| Where it runs | OpenRouter cloud | Provider cloud | Your server |
| Keys | One OpenRouter key (or your own via BYOK) | A key per vendor | Provider keys stay with you, virtual keys for the team |
| Billing | Credits, fee at top-up | A bill from each vendor | Provider bills, the software is free |
| Fallback across labs | Yes, the `models` parameter | No | Yes, in `config.yaml` |
| Data path | Through OpenRouter and the chosen provider | Provider only | Through your host and the provider |
| What you maintain | Nothing | Nothing | Server, database, updates |

A direct API makes more sense when you work with one vendor and need its features on release day, with no fee and no extra hop. The [Claude API guide](https://futurecraft.pro/blog/claude-api-developer-guide/) covers Anthropic's API directly. A self-hosted gateway fits when provider keys must stay inside your perimeter or you need per-employee budgets; see the [LiteLLM guide](https://futurecraft.pro/blog/litellm-guide/). Where a gateway sits in a multi-provider setup is covered in [Multi-Provider LLM Routing](https://futurecraft.pro/blog/multi-provider-llm-architecture/).

## Models and providers: how the catalog works

The catalog needs no key: `GET https://openrouter.ai/api/v1/models`. On November 7, 2026 it had 460 entries, including `:free` and `:batch` variants and service routers. The vendors with the most models were openai (98), google (43), qwen (43) and anthropic (30). The count changes almost daily.

A model ID is `vendor/model`. Besides pinned versions there are tilde aliases: `~anthropic/claude-sonnet-latest` always points to the newest model in the family ([latest resolution](https://openrouter.ai/docs/guides/routing/routers/latest-resolution)). An alias is handy for a prototype. In production, pin the version so behavior doesn't change without a release on your side.

IDs take suffixes. Per the [FAQ](https://openrouter.ai/docs/faq), there are two kinds:

| Suffix | Kind | What it does |
|---|---|---|
| `:free` | Catalog | Free version of the model, a separate entry with its own limits |
| `:batch` | Catalog | Batch processing, half price for Claude 5.5 |
| `:nitro` | Routing | Providers sorted by throughput, plus priority endpoints |
| `:floor` | Routing | Providers sorted by price, plus flex endpoints |
| `:exacto` | Routing | Providers ordered by tool-calling reliability |
| `:online` | Routing, deprecated | Web search in the prompt; replaced by the server tool `openrouter:web_search` |

Catalog variants exist only for models that list them in `/models`. Routing suffixes work on any model.

### One model, different providers

Several providers serve most models, each through its own endpoint. You can see the list without a key: `GET /api/v1/models/<vendor>/<model>/endpoints`. For open models the gap between providers can be large.

Snapshot for `qwen/qwen3-coder` on November 7, 2026:

| Provider | Quantization | Price per 1M (input / output) | Parameters | `tool_choice` | Context |
|---|---|---|---|---|---|
| DeepInfra | fp4 | $0.30 / $1.00 | 15 | none, auto, required, function | 262,144 |
| Venice | fp8 | $0.35 / $1.50 | 11 | none and auto only | 256,000 |

Of these two providers, only DeepInfra supports `logit_bias`, `min_p`, `repetition_penalty` and `seed`. If a request with `tool_choice: "required"` lands at Venice, you won't get an error: per the documentation, without special settings an unsupported parameter is simply ignored.

Closed models vary less, but they still vary. On the same date Haiku 5.5 was served by Anthropic, Amazon Bedrock, Google Vertex, Azure and Claude Platform on AWS. Global endpoints cost $0.10 per 1M input tokens; regional ones (`google-vertex/europe`, `amazon-bedrock/eu-west-1` and others) cost $0.11.

## Quick start: your first request

You need an account and a key from [openrouter.ai/settings/keys](https://openrouter.ai/settings/keys); the key looks like `sk-or-v1-...`. The API base URL is `https://openrouter.ai/api/v1`. OpenRouter implements OpenAI-compatible `/chat/completions`, `/completions` and `/responses` ([FAQ](https://openrouter.ai/docs/faq)). The [Quickstart](https://openrouter.ai/docs/quickstart) examples now use `/responses`, but `/chat/completions` works the same way.

### curl

```bash
curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "HTTP-Referer: https://example.com" \
  -H "X-OpenRouter-Title: My App" \
  -d '{
    "model": "anthropic/claude-haiku-5.5",
    "max_tokens": 30,
    "messages": [{"role": "user", "content": "Reply with exactly: ok"}]
  }'
```

The `HTTP-Referer` and `X-OpenRouter-Title` headers are optional: they attribute your app in the openrouter.ai rankings. Per the [documentation](https://openrouter.ai/docs/app-attribution), the old `X-Title` name is still accepted.

### OpenAI SDK

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=os.environ["OPENROUTER_API_KEY"],
)

resp = client.chat.completions.create(
    model="anthropic/claude-haiku-5.5",
    max_tokens=30,
    messages=[{"role": "user", "content": "Reply with exactly: ok"}],
)
print(resp.choices[0].message.content)
```

TypeScript works the same way: `baseURL: "https://openrouter.ai/api/v1"` in the `OpenAI` constructor. Parameters the OpenAI SDK doesn't know about (`models`, `provider`) go through `extra_body`; there's an example in the fallback section below.

### What comes back in the response

Besides the usual OpenAI-format fields, the response includes `provider` (who actually served the request), `model` (which model answered, which matters with fallback) and `usage.cost` (cost in dollars). Per the [usage accounting docs](https://openrouter.ai/docs/guides/guides/usage-accounting) as of November 7, `usage` with `cost` comes back in every response. The `usage: {include: true}` parameter is deprecated and has no effect.

**Verified:** November 7, 2026, `anthropic/claude-haiku-5.5`, prompt "Reply with exactly: ok". The answer "ok" arrived in 1.05 s from provider `Claude Platform on AWS`, with 18 input and 4 output tokens and a `cost` of $0.0000038. That matches the price of $0.10 / $0.50 per 1M tokens. Usage in `GET /api/v1/key` showed 0 right after the tests and updated a few minutes later.

## OpenRouter pricing and fees

OpenRouter passes provider prices through unchanged, and the [FAQ](https://openrouter.ai/docs/faq) says so directly: there's no markup on inference. The platform makes its money on a fee when you buy credits.

| Plan | Top-up fee | BYOK | Limits |
|---|---|---|---|
| Free | Doesn't apply | No | 50 requests a day, free models only |
| Standard | 5.5%, $0.80 minimum; crypto 5% with no minimum | $25,000 a month at list price with no fee, 5% above that | Pay-as-you-go |
| Business | 8% | Same as Standard | Pay-as-you-go |
| Enterprise | By contract | Custom | By contract |

The 5.5% and 8% rates come from the [Pricing page](https://openrouter.ai/pricing). The $0.80 minimum and the 5% crypto rate come from the [fee post](https://openrouter.ai/blog/announcements/simplifying-our-platform-fee/) (updated June 11, 2026). Terms are as of November 2026. The actual charge couldn't be verified: the test account already had a balance, and no new top-up was made.

Because of the $0.80 minimum, the effective rate depends on the amount:

| Top-up | Fee | Effective rate |
|---|---|---|
| $5 | $0.80 | 16% |
| $10 | $0.80 | 8% |
| $25 | $1.38 | 5.5% |
| $100 | $5.50 | 5.5% |
| $1,000 | $55.00 | 5.5% |

A few more conditions from the [Terms](https://openrouter.ai/terms) (version of August 31, 2026). A single purchase is $5 to $25,000. You can request a refund of unused credits only within 24 hours of payment; the fee isn't refunded, and crypto payments are never refunded. OpenRouter may expire unused credits 365 days after purchase, so topping up a year ahead doesn't pay off.

### Claude 5.5 and Fable 5.1 prices in the catalog

As of November 7, 2026, per 1M tokens:

| Model | Input | Output | Cache read |
|---|---|---|---|
| Fable 5.1 | $10 | $50 | $0.25 |
| Opus 5.5 | $4 | $20 | $0.20 |
| Sonnet 5.5 | $2 | $10 | $0.10 |
| Haiku 5.5 | $0.10 | $0.50 | $0.01 |

All four have `:batch` variants at half price. How to measure cost per solved task rather than per token is covered in [LLM Cost Optimization](https://futurecraft.pro/blog/ai-cost-optimization/).

## Provider routing: how to pick a provider

If you don't name a provider, OpenRouter picks one. The default algorithm weighs provider price and stability, and it changes over time. When tested on November 7, the [documentation](https://openrouter.ai/docs/guides/routing/provider-selection) described it as comparing effective price, speed, time to first token, 429 and error rates, and tool-call quality. If a provider fails, the request moves to the next one automatically. Setting `sort`, `order`, `:nitro`, `:floor` or your own provider key turns the default algorithm off.

The `provider` object in the request body controls the choice:

| Field | Default | What it does |
|---|---|---|
| `order` | - | Providers in priority order |
| `allow_fallbacks` | `true` | With `false`, only providers from `order`; otherwise an error |
| `only` / `ignore` | - | Provider allowlist and blocklist |
| `sort` | - | `price`, `throughput` or `latency` |
| `quantizations` | - | Filter: `int4`, `int8`, `fp4`, `fp8`, `fp16`, `bf16`, `unknown` and others |
| `require_parameters` | `false` | Only providers that support every parameter in the request |
| `data_collection` | `"allow"` | `"deny"` excludes providers that store data and may train on it |
| `zdr` | - | ZDR endpoints only |
| `max_price` | - | Price cap per 1M tokens; if no provider fits, the request fails |
| `preferred_min_throughput`, `preferred_max_latency` | - | Soft speed thresholds: slow providers move down the list but aren't excluded |

Example: fp8 only, no more than $1 per 1M input and $2 per 1M output tokens.

```json
{
  "model": "qwen/qwen3-coder",
  "messages": [{"role": "user", "content": "Reply with exactly: ok"}],
  "provider": {
    "quantizations": ["fp8"],
    "max_price": {"prompt": 1, "completion": 2}
  }
}
```

**Verified:** November 7, 2026, `qwen/qwen3-coder`, the same short prompt.

| Request | Provider | cost, $ |
|---|---|---|
| `sort: "price"` | DeepInfra | 0.0000059 |
| `sort: "throughput"` | DeepInfra | 0.0000059 |
| `quantizations: ["fp8"]` | Venice | 0.00000755 |

Without a filter the request went to DeepInfra, which runs fp4 per `/endpoints`. With the `fp8` filter, Venice served it, and the request cost 28% more. On an open model, quantization affects answer quality, so set it explicitly.

### 404 errors and routing_funnel

OpenRouter's routing errors are detailed, which helps with debugging.

**Verified:** November 7, 2026, `anthropic/claude-haiku-5.5`, `provider: {only: ["nonexistent-provider"], allow_fallbacks: false}`. The response was a 404. Its `metadata` listed the model's providers (`google-vertex`, `claude-on-aws`, `azure`, `anthropic`, `amazon-bedrock`), a step-by-step `routing_funnel` and `failed_routing_step`. The funnel: 10 endpoints, 5 after the regional markup filter, 0 after the provider filter. The error shows the full model ID: `anthropic/claude-haiku-5.5-20261007`.

The funnel tells you right away which filter killed the route: a typo in a provider slug, a `max_price` that's too strict, or account privacy settings.

## Model fallbacks and Auto Router

Fallback between providers of one model is on by default. You set fallback between different models with a `models` list in priority order ([model fallbacks](https://openrouter.ai/docs/guides/routing/model-fallbacks)). OpenRouter tries the next model on any error from the first: context overflow, moderation refusal, rate limit, downtime. You pay for the model that answered, and its name comes back in the `model` field.

With the OpenAI SDK, pass the list through `extra_body`. The model in `model` goes first, and `models` holds the fallbacks in order:

```python
resp = client.chat.completions.create(
    model="anthropic/claude-haiku-5.5",
    messages=[{"role": "user", "content": "Reply with exactly: ok"}],
    extra_body={
        "models": ["anthropic/claude-sonnet-5.5"],  # fallback models, in order
    },
)
print(resp.model)  # the model that actually answered
```

**Verified:** November 7, 2026. A request with `models: ["nonexistent-vendor/no-such-model", "anthropic/claude-haiku-5.5"]` returned 400 "is not a valid model ID". OpenRouter validates the model list before routing, so a typo in the first model fails the whole request instead of falling through to the next one. Two requests in a row with `models: ["google/gemma-4-26b-a4b-it:free", "anthropic/claude-haiku-5.5"]` both returned 200 from `anthropic/claude-haiku-5.5` at $0.0000038 each. The free model was rate-limited at the provider at that moment, and the request quietly went to the paid one.

The documentation warns about this: if a paid model follows a `:free` one in `models`, fallback bills the paid price. To keep a request guaranteed free, list only `:free` models, or use the `openrouter/free` router, which switches only between free models. Log `model` from the response in your app, or a switch to the fallback model goes unnoticed.

For the Anthropic format (`/api/v1/messages`) there's a `fallbacks` parameter instead of `models`. It takes at most 3 entries, each with only a `model` field, and it can't be combined with `models`. Breaking any of these rules returns 400.

### Auto Router

Auto Router (`openrouter/auto`) picks a model for each request. The router itself costs nothing extra: you pay the price of the chosen model, and `provider.max_price` still applies. OpenRouter updates its routers often. On November 7 the catalog had several entries with the `openrouter/` prefix (`auto`, `auto-code`, `free` and others), and the [Auto Router page](https://openrouter.ai/docs/guides/routing/routers/auto-router) already described a new generation with a `cost_tier` parameter. Auto Router wasn't run in testing. For predictable production spend, set the model explicitly.

## Free models (`:free`)

Models with the `:free` suffix cost $0. They're separate catalog entries with their own limits and providers. On November 7, 2026 the catalog had 12 of them plus the `openrouter/free` router. Limits per the [documentation](https://openrouter.ai/docs/api-reference/limits):

| Credits bought (all time) | Requests per minute | Requests per day |
|---|---|---|
| Fewer than 10 | 20 | 50 |
| 10 or more | 20 | 1,000 |

`GET /api/v1/key` returns the current daily counter in the `free_model_daily_requests` field. The documentation adds a caveat to the threshold: to cover rounding and fees, the higher daily limit kicks in one credit early. With a negative balance, you may see errors even on free models.

**Verified:** November 7, 2026, `google/gemma-4-26b-a4b-it:free`. The response was 429 "temporarily rate-limited upstream", `limit_source: upstream_provider_shared_pool`, provider Google AI Studio. The `remedy_hint` suggested adding your own key (BYOK) or picking another provider. The response had no `X-RateLimit-*` headers.

You can get a 429 on `:free` with your personal limit untouched: the free model's pool at the provider is shared, and your own counter won't explain it. Beyond experiments, free models aren't reliable. Mind privacy too: the account setting that allows providers who train on data is separate for paid and free models ([provider logging](https://openrouter.ai/docs/guides/privacy/provider-logging)).

## BYOK: bring your own provider keys

BYOK (bring your own key) means connecting your own provider key, for example Anthropic or Bedrock. Requests still go through the OpenRouter API, but you pay for inference on your provider account, and the rate limits are yours too. Per the [BYOK docs](https://openrouter.ai/docs/use-cases/byok) and Pricing, on the Standard and Business plans the first $25,000 a month at list price carries no fee. Above that, OpenRouter deducts 5% of the cost of the same request from your credits. The Free plan has no BYOK. Older summaries mention a "1 million free BYOK requests a month" rule; the cost-based limit replaced it.

Two details affect the bill:

- **Shared capacity fallback.** By default, if your key hits a limit or returns an error, OpenRouter falls back to its own endpoints and spends your credits. To prevent that, pick "Never use shared capacity" in the key settings.
- **Priority.** Keys are either Prioritized (tried before OpenRouter endpoints) or Fallback (tried after them). Without `order` in the request, providers you hold a Prioritized key for go first.

BYOK wasn't tested: it needs a separate provider key.

## Privacy, logging and ZDR

Per the [FAQ](https://openrouter.ai/docs/faq), OpenRouter logs request metadata (time, model, token counts) and doesn't store prompts or responses unless you turn logging on. If you do, OpenRouter gives a 1% discount on usage.

The request still goes to a provider, and every provider has its own policy. As of November 7, the [ZDR documentation](https://openrouter.ai/docs/guides/features/zdr) splits endpoints into three tiers:

| Tier | What happens to data |
|---|---|
| ZDR (Zero Data Retention) | Prompts and responses usually aren't stored after processing |
| Safety Retention | The provider or OpenRouter may keep data only for abuse prevention, no training |
| Retention and Training | Stored per the provider's policy, training possible |

How to restrict the route:

- `provider.zdr: true` in the request: ZDR endpoints only (plus Safety Retention if the account allows it).
- `provider.data_collection: "deny"`: no providers that collect data.
- Account privacy settings or guardrails: ZDR only mode, no training. If you set `provider` and no provider matches the account settings, the request fails.

Self-serve accounts allow Safety Retention by default; Enterprise accounts get ZDR only. ZDR covers inference but not plugins and server tools such as web search, which follow their own third-party policies. When tested, the ZDR page also said that all requests, ZDR traffic included, are screened for CSAM, and suspicious content may be kept for review. For sensitive data, check the specific endpoint's policy before sending, and keep a data policy layer in your application, as in [Multi-Provider LLM Routing](https://futurecraft.pro/blog/multi-provider-llm-architecture/).

## Prompt caching through OpenRouter

The prompt cache lives at the provider, so a repeat request has to reach the same provider. OpenRouter handles this with sticky routing ([prompt caching](https://openrouter.ai/docs/guides/best-practices/prompt-caching)). It turns on only when the provider's cache read is cheaper than normal input, and it doesn't work with a manual `provider.order`. OpenRouter identifies a conversation by a hash of the first system message and the first non-system message, or by an explicit `session_id` in the body or the `x-session-id` header (up to 256 characters). Per the documentation as of November 7, a session lives for 60 minutes without requests.

The rules differ by provider:

| Provider | How it's enabled | Cache write | Read |
|---|---|---|---|
| Anthropic | Explicitly, `cache_control` (on a block or at the top level of the request) | 1.25x input (5-minute TTL), 2x (1-hour TTL) | 0.1x input; 0.05x on Opus 5.5 and Sonnet 5.5, 0.025x on Fable 5.1 |
| OpenAI | Automatically from 1,024 tokens | Free on models before GPT-5.6, 1.25x on GPT-5.6 and later (30-minute TTL) or 2.5x (12 hours) | 0.1x or 0.05x input on GPT-6 and GPT-6.1, 0.25x or 0.5x on older models |
| Alibaba Qwen | Explicitly, `cache_control` | 1.25x, 5-minute TTL | 0.1x input |

Gemini, DeepSeek and a cache break-even formula are covered in [how prompt caching works across providers](https://futurecraft.pro/blog/prompt-caching/).

The minimum cacheable prompt for Claude 5.5 and Fable 5.1 is 512 tokens. Here's an example with the cache on the system prompt:

```json
{
  "model": "anthropic/claude-haiku-5.5",
  "messages": [
    {"role": "system", "content": [
      {"type": "text", "text": "<long instructions or document>",
       "cache_control": {"type": "ephemeral"}}
    ]},
    {"role": "user", "content": "Reply with exactly: ok"}
  ]
}
```

### How much the cache saves: a Haiku 5.5 test

**Verified:** November 7, 2026, `anthropic/claude-haiku-5.5`, a 12,635-token system prompt with `cache_control: {type: "ephemeral"}`, two identical requests 2 s apart.

| Request | cached_tokens | cost, $ | Time, s |
|---|---|---|---|
| First (cache write) | 0 | 0.00158 | 1.51 |
| Second (cache read) | 12,619 | 0.00013 | 1.41 |

The second request was 12 times cheaper than the first. The first cost more than a normal request: 12,635 tokens at $0.10 per 1M would be $0.00126, but $0.00158 was charged, which is the 1.25x cache write premium. The same provider served both requests; otherwise the cache read wouldn't have happened.

The cache pays off from the second hit, and a prompt you send only once costs 25% more with `cache_control`. Cache metrics come back in `usage.prompt_tokens_details.cached_tokens` and `cache_write_tokens`. How caching works on Anthropic's side is covered in the [Claude API guide](https://futurecraft.pro/blog/claude-api-developer-guide/).

## Tool calling and structured outputs: what differs

In the November 7 catalog, 398 of 460 entries support `tools`, 393 support `tool_choice`, 387 `structured_outputs` and 401 `response_format`. That's support at the model level. A specific provider can support less, as Venice does for Qwen3 Coder.

Per the [documentation](https://openrouter.ai/docs/guides/routing/provider-selection), `tools`, the response format (`response_format`, structured outputs) and `verbosity` act as soft preferences: if the model has providers that support them, the request goes there. With `require_parameters: false`, a provider that doesn't support any other parameter simply ignores it. To send a request only to providers that support all of its parameters, set `require_parameters: true`.

```json
{
  "model": "qwen/qwen3-coder",
  "messages": [{"role": "user", "content": "What time is it?"}],
  "tools": [{"type": "function", "function": {
    "name": "get_time", "parameters": {"type": "object", "properties": {}}}}],
  "tool_choice": "required",
  "provider": {"require_parameters": true}
}
```

**Verified:** November 7, 2026, `qwen/qwen3-coder`, one tool `get_time`, `tool_choice: "required"`, `provider: {require_parameters: true}`. The response was 200 from DeepInfra and included `tool_calls`. That matches the `/endpoints` data: Venice doesn't support `tool_choice: required`, and DeepInfra does.

For agents that rely on tool calls, `require_parameters: true` is a sensible default. The alternative is the `:exacto` suffix, which orders providers by tool-calling reliability; it wasn't run in testing.

## Connecting agents: Claude Code, OpenCode, Codex CLI

### Claude Code

Per the [official OpenRouter guide](https://openrouter.ai/docs/cookbook/coding-agents/claude-code-integration), Claude Code connects through environment variables with no local proxy, because OpenRouter accepts the Anthropic Messages API.

```bash
export ANTHROPIC_BASE_URL="https://openrouter.ai/api"
export ANTHROPIC_AUTH_TOKEN="$OPENROUTER_API_KEY"
export ANTHROPIC_API_KEY=""            # must be explicitly empty
# optional: pin models per class
export ANTHROPIC_DEFAULT_SONNET_MODEL="~anthropic/claude-sonnet-latest"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="~anthropic/claude-haiku-latest"
claude
```

If you previously signed in to Claude Code with an Anthropic account, run `/logout` once; otherwise the cached session conflicts with the gateway key. Put the variables in your shell profile, not in a project `.env`: the native Claude Code installer doesn't read it. Check the connection with `/status`: the Auth token line should name `ANTHROPIC_AUTH_TOKEN`.

The setup comes with two warnings. The OpenRouter guide itself says it's guaranteed to work only with the Anthropic first-party provider and recommends putting that provider first in provider routing. And the [Claude Code documentation](https://code.claude.com/docs/en/llm-gateway) says plainly that Anthropic doesn't endorse or audit third-party gateways and doesn't support routing Claude Code to non-Claude models through any gateway. Anthropic gives no official support for Claude Code with OpenRouter, so you run it at your own risk. Claude Code through OpenRouter wasn't run for this article.

### OpenCode

OpenRouter is a built-in provider in OpenCode ([guide](https://openrouter.ai/docs/cookbook/coding-agents/opencode-integration)). In the TUI, run `/connect`, choose OpenRouter and enter the key, then pick a model with `/models`. Add more models to `opencode.json` by their OpenRouter slug. Agent configuration and model tests are covered in the [OpenCode guide](https://futurecraft.pro/blog/opencode-guide/).

### Codex CLI

Codex CLI connects through a provider block in `~/.codex/config.toml` ([guide](https://openrouter.ai/docs/cookbook/coding-agents/codex-cli)):

```toml
model_provider = "openrouter"
model = "<vendor/model from the catalog>"

[model_providers.openrouter]
name = "openrouter"
base_url = "https://openrouter.ai/api/v1"

[model_providers.openrouter.auth]
command = "sh"
args = ["-c", "echo $OPENROUTER_API_KEY"]
```

`env_key = "OPENROUTER_API_KEY"` also works, but then Codex doesn't load the OpenRouter model catalog and shows an unknown-model warning for non-OpenAI models.

## Limits and errors

| Code | When | What to do |
|---|---|---|
| 400 | Invalid model ID (including in `models`), extra fields in `fallbacks` | Check the ID against `/api/v1/models` |
| 402 | Not enough credits, or the in-flight budget is used up | Check `metadata.reason`, lower `max_tokens`, top up |
| 404 | No provider passed the filters (`only`, `max_price`, privacy) | Check `routing_funnel` and `failed_routing_step` |
| 429 | `:free` limit or the provider's shared pool | Check `limit_source`, retry with backoff, add a fallback |

The in-flight budget ([limits](https://openrouter.ai/docs/api-reference/limits)) applies to some prepaid accounts with small balances and to new accounts. OpenRouter reserves the estimated cost of running requests and returns 402 when that runs short, even with a positive balance. `reason: "in_flight_budget_exhausted"` is temporary: wait for `Retry-After`. `reason: "weight_exceeds_budget"` means a single request costs more than the whole budget. Retrying won't help; cut `max_tokens` or the prompt.

The documentation doesn't name a fixed RPM for paid models. Capacity is managed globally, and extra accounts or keys don't raise the limit. When a provider returns 429, OpenRouter first tries other providers of the same model and only then passes the error to the client. `GET /api/v1/key` shows spend and the remaining limit for a key: `limit_remaining`, `usage_daily`, `usage_weekly`, `usage_monthly`. You can set a spend limit on each key, which is an easy way to cap an experiment or a single service.

## OpenRouter limitations and when to use something else

- **Data goes through a third party.** Even with ZDR, the request passes through OpenRouter's and the provider's servers. If keys and traffic must stay inside your perimeter, you need a self-hosted gateway such as [LiteLLM](https://futurecraft.pro/blog/litellm-guide/) or a local model. The options are collected in the [local LLM guide](https://futurecraft.pro/blog/local-llm-guide/), setup is covered in the [Ollama guide](https://futurecraft.pro/blog/ollama-guide/), and the choice between the two popular apps in [LM Studio vs Ollama](https://futurecraft.pro/blog/lm-studio-vs-ollama/).
- **Quality depends on the provider.** Quantization and parameter support differ between providers of one model, and the default provider can change without you doing anything. For reproducible results, pin `quantizations`, `order` or `only` and log `provider` from the response.
- **The catalog changes.** Models get an `expiration_date`, and prices and provider lineups shift. A hardcoded ID with no monitoring will start returning errors one day.
- **Fees on small amounts.** A $5 top-up costs 16% on top. You can get credits refunded only in the first 24 hours, and unused credits can expire after a year.
- **One vendor and its new features.** If you work only with Anthropic or OpenAI and need new API features on release day, the direct API is simpler and cheaper by the size of the fee.

How to tie the gateway, fallback, evals and monitoring into one system is covered in [Production LLM Stack](https://futurecraft.pro/blog/production-llm-stack/).

## Where to start

1. Create a key on openrouter.ai and set a spend limit on it right away.
2. If you plan regular use, top up at least $25: at that amount the fee is 5.5%, not 16%. Keep the 24-hour refund window and the 365-day expiry in mind.
3. Connect the OpenAI SDK by changing `base_url`, and log `model`, `provider` and `usage.cost` from every response.
4. For open models, check `/api/v1/models/<id>/endpoints` and pin `quantizations` or `order`.
5. For agents and tool calls, set `provider.require_parameters: true`.
6. For sensitive data, turn on ZDR only mode in privacy settings or send `provider.zdr: true` with requests.
7. Send long system prompts to Claude with `cache_control` and check `cached_tokens` on the second request.
