OpenRouter, one interface to hundreds of models
OpenRouter is an intermediary between your application and language model providers. It exposes a single endpoint that follows the OpenAI specification, gives you models from dozens of companies through it, switches providers when one fails, and settles everything from one credit balance. On the day of checking, 20 August 2026, the catalogue returned by the public API held 417 entries from 59 publishers, and the provider list held 102 records.
What it is and how it looks in code
The shortest description goes like this: OpenRouter sits in the path of your request, accepts it in the format you know from the OpenAI library, picks a specific provider, and passes it along. The response comes back in the same format, enriched with fields describing what happened along the way.
The base address is https://openrouter.ai/api/v1, and the most used path is /chat/completions. Since the contract follows the OpenAI specification, moving existing code onto OpenRouter amounts to swapping the base address and the key. There is also an /api/v1/messages endpoint that accepts the request shape known from Anthropic libraries, so code written for Claude can be redirected without rewriting the transport layer.
Beyond the API itself you get official client libraries. The @openrouter/sdk package sits at version 1.2.49, the openrouter package on PyPI at version 1.1.69 requiring Python 3.10 or newer, and @openrouter/ai-sdk-provider at version 3.0.0 plugs OpenRouter into the Vercel AI SDK and declares Node 22 or newer. All three carry Apache 2.0, and this is a case where three independent sources agree: the license field in the npm registry, the repository metadata in the GitHub API, and the LICENSE.md file inside the published tarball say the same thing.
One distinction matters here and is easy to miss. Only the client libraries are open. The service itself, meaning the prompt classifier, the provider selection logic, and the billing system, stays closed and runs solely on the company's servers. There is no version you can host yourself, so deciding to plug OpenRouter in is a decision to add an external dependency to the critical path.
# the simplest call, with application attribution headers
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "HTTP-Referer: https://my-application.example" \
-H "X-OpenRouter-Title: My application" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-5",
"models": ["openai/gpt-5.6-luna", "google/gemini-3.7-flash"],
"messages": [{"role": "user", "content": "Summarise this paragraph in one sentence."}]
}'
# the model catalogue and key balance, with no library at all
curl -s https://openrouter.ai/api/v1/models | jq '.data | length'
curl -s https://openrouter.ai/api/v1/key -H "Authorization: Bearer $OPENROUTER_API_KEY"The HTTP-Referer header is required if you want your application to appear in the public rankings, because it acts as the identifier. The title header is now called X-OpenRouter-Title, and the older X-Title still works for backwards compatibility. A title without HTTP-Referer creates no entry, which regularly surprises people who set only one of the two.
Pricing, a fee on top-ups rather than on tokens
This is the part most people come to this text for, so let me describe the mechanism precisely, because it works differently from a typical intermediary service.
For tokens themselves you pay the provider's rate, with no markup. OpenRouter states this plainly in the documentation and repeats it in several places: the price per million tokens is the one you would pay buying directly at the source. The revenue appears elsewhere, when you fund the account.
The platform fee is 5.5 percent of the credit top-up value, with a 0.80 dollar minimum, for card payments. Payment in USDC carries a 5 percent fee. Both numbers are embedded directly in the code of the frequently asked questions page, and the pricing page lists the same 5.5 percent in the pay-as-you-go column.
The 0.80 dollar minimum has concrete consequences at small amounts. For a top-up of a dozen or so dollars the percentage yields less than that minimum, so the real overhead climbs above the stated 5.5 percent, and at the smallest amounts it lands several points higher. At top-ups counted in hundreds of dollars the minimum stops mattering and only the percentage remains. The practical conclusion is trivial yet often skipped: top up less often and in larger amounts.
Credits are denominated in United States dollars. A refund for unused funds can be requested within twenty four hours of the transaction, via a button on the credits page, with platform fees excluded from refunds and cryptocurrency payments never refundable. A year after purchase, unused credits may expire. If you delete the account, the balance is gone and cannot be reclaimed by registering again. Volume discounts are officially not offered.
There is one more pricing mechanism easy to forget when assessing costs. Turning on optional logging of prompts and completions grants a 1 percent discount on usage costs. By default request content is not logged, so this is a deliberate trade of privacy for money rather than a setting to flip without thinking.
Using your own provider keys is billed separately. The pay-as-you-go plan includes 25,000 dollars per month measured at list price with no fee, the enterprise plan includes 200,000 dollars, and above that threshold the fee is 5 percent of what the same request would normally cost on OpenRouter, deducted from your credit balance. The allowance is measured by spend value, not by request count.
Free models and what you can actually do with them
Free models exist and are real, but their role differs from what the word in the name suggests.
On the day of checking, the catalogue held 17 identifiers ending in :free, among them Gemma family variants from Google, several Nemotron models from NVIDIA, and openai/gpt-oss-20b. On top of that comes openrouter/free, a zero-priced router that picks a model at random from the currently available free ones. The pricing page speaks of more than twenty five free models and four free providers, so the number in the API and the number on the marketing page need not match exactly.
The limits are hard and well documented. Free variants get 20 requests per minute regardless of account state. The daily limit depends on how many credits you have ever purchased: below 10 dollars it is 50 requests per day, from 10 dollars upward it is 1000 requests per day. Limits are governed globally per account, so creating extra keys or accounts changes nothing.
Two things surprise people most often. First, a negative credit balance blocks free models as well and shows up as a 402 code, so an account in debt stops working entirely. Second, the composition of the free model list shifts, because entries appear and disappear along with provider promotions. Code with one :free identifier hard-coded will break the moment that entry leaves the catalogue.
The practical conclusion is that free variants suit a prototype, integration tests, and workshop work very well. For production with real traffic they do not qualify, purely because of the 20 requests per minute cap, no matter how many credits sit on the account.
Routing between providers
The same model is often served by many infrastructure providers, and this is where OpenRouter does more than pass a request along.
The default strategy is load balancing with price as the priority. First, providers that saw significant outages in the last thirty seconds drop out. Among the rest, one is chosen with probability proportional to the inverse square of the price. The documentation gives its own example: with providers charging 1, 2 and 3 dollars per million tokens, the chance of hitting the first is nine times higher than the third. The remaining ones become fallbacks in case of an error.
Behaviour changes through the provider object in the request body. The sort field, with values price, throughput or latency, orders providers explicitly and disables load balancing. So does order, a list of provider identifiers tried in sequence. The :nitro and :floor suffixes appended to a model identifier are shorthands for sorting by throughput and by price.
The remaining fields narrow the pool. only and ignore take lists of provider identifiers to permit or skip. quantizations filters by quantization level, with values such as int4, int8, fp8, bf16 and fp16. max_price takes an object with prompt, completion, request and image fields and rejects providers above the given threshold. require_parameters drops providers that do not support every parameter in your request. data_collection set to deny excludes providers that may store data, and zdr narrows routing to zero data retention endpoints. There are also preferred_min_throughput and preferred_max_latency, which take a number or an object with p50, p75, p90 and p99 percentile cutoffs.
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key="<OPENROUTER_API_KEY>",
)
completion = client.chat.completions.create(
model="meta-llama/llama-4-maverick",
messages=[{"role": "user", "content": "List three advantages of a partial index."}],
extra_headers={
"HTTP-Referer": "https://my-application.example",
"X-OpenRouter-Title": "My application",
},
extra_body={
"models": ["mistralai/mistral-large-2512", "deepseek/deepseek-v3.2"],
"provider": {
"sort": "throughput",
"require_parameters": True,
"data_collection": "deny",
"quantizations": ["fp8", "bf16"],
"max_price": {"prompt": 3, "completion": 6},
"ignore": ["some-provider-slug"],
},
},
)
print(completion.model)
print(completion.choices[0].message.content)The tighter you set these conditions, the higher the chance that no combination satisfies them. The answer then is a 503 code, meaning no provider matches the requirements. That is not a service outage but the result of your own configuration, and the first move should be relaxing preferences rather than retrying the request.
A separate layer is the routers that pick a model for you. The openrouter/auto identifier runs the prompt through a lightweight classifier that assigns it to one of roughly thirty task types, such as code:debugging or agent:multi_step_planning, then selects a model by the community's share of spend over a trailing seven day window. The cost band is set through the cost_tier field with values from low to max, passed via a plugin with the auto-router identifier. In the model catalogue the openrouter/auto entry carries a price of -1, because the real rate depends on what the router picks. The convenience is obvious, but the cost per request stops being predictable, which makes it a trap rather than a help when budgeting.
Model fallbacks and the limits of that mechanism
Fallback between providers of the same model happens automatically. Fallback between different models has to be declared, and the models field does that with a list of identifiers in priority order.
Practically any error triggers it: context length validation failures, moderation flags on filtered content, rate limiting on the provider side, and downtime. Billing follows the model that ultimately served the request, whose identifier comes back in the model field of the response. That field is the only reliable source of truth about what actually answered, and it belongs in your logs.
There are two limits, and both hurt only in production. First, if the fallback model also returns an error, you get that error and the chain ends. A fallback is not a guarantee of an answer, only a second attempt. Second, on the Anthropic compatible endpoint a separate fallbacks field applies, which accepts only the model key, permits at most three entries, and cannot be combined with the models field. Breaking any of those conditions ends in a 400 code.
There is also a problem the documentation says less about, one that surfaces in every serious deployment. Models from different families follow tool call schemas differently and react differently to the same system prompt. A fallback list built from OpenAI, Google Gemini and Mistral AI models will rescue availability, but it may return a structure your parser rejects. If you depend on tool calls or on an enforced response format, set require_parameters and test every model on the list separately before writing it in as a backup.
Errors that appear mid-stream need separate handling. Since the 200 status has already been sent, the error cannot arrive as an HTTP code and instead arrives as an SSE event with finish_reason set to error. Code that only checks the response status will treat such a request as successful and store empty content.
Billing, credits and cost control
Every response carries a usage object with input and output token counts measured by the model's native tokenizer, the cost in credits, the reasoning token count, and the cached_tokens and cache_write_tokens fields describing prompt caching. With streaming, the object arrives in the final SSE message. In the cost breakdown, cost is the amount taken from your account, while cost_details.upstream_inference_cost is the amount charged by the provider, though when retrieved by generation identifier that second field is meaningful only for requests on your own keys.
Key state is checked with a GET /api/v1/key request. The response holds limit, limit_reset and limit_remaining describing the optional spending cap on an individual key, include_byok_in_limit, usage totals in usage, usage_daily, usage_weekly and usage_monthly, their counterparts for own keys under the byok_ prefix, and is_free_tier. That is enough to build an alert before funds run out, rather than learning about it from a 402 code.
{
"id": "gen-abc123",
"model": "anthropic/claude-sonnet-5",
"provider": "anthropic",
"usage": {
"prompt_tokens": 1420,
"completion_tokens": 318,
"total_tokens": 1738,
"cost": 0.0121,
"cost_details": {
"upstream_inference_cost": 0.0121
}
}
}One more safeguard is worth knowing, because it removes real cost during failures. Zero completion insurance applies by default across all models and providers: if a response has zero output tokens and a blank finish_reason, or a finish_reason of error, tokens are not billed, even when the provider charged OpenRouter for processing the prompt. It does not cover auxiliary services that already ran, though: fees for web search, for file parsing and text recognition in PDF files, and for web fetching. Those stay on the bill.
A hard spending ceiling is built from three parts: the per-key limit, the workspace budget, and the fact that the credit balance is prepaid. That last property is an advantage people rarely mention. An account without funds stops generating cost instead of producing an invoice at the end of the month.
OpenRouter against the alternatives
Four criteria are enough to line these solutions up: where the intermediary layer runs, what it costs by itself, whether it has built-in fallback, and whether you can read its code.
| Solution | Where it runs | Intermediary layer fee | Fallback | Code |
|---|---|---|---|---|
| OpenRouter | hosted service | 5.5 percent on top-ups, 0.80 dollar minimum | built in, providers and models | closed, client libraries open |
| LiteLLM Proxy | you host it | none, you pay providers directly | configurable, in your own file | MIT outside the enterprise directory |
| Portkey Gateway | self-hosted or hosted | depends on the hosted plan | built in | MIT |
| Vercel AI Gateway | hosted service | no markup and no fee on tokens | built in | closed |
| Direct provider API | library inside your app | none | you write it yourself | closed API |
A simple split follows from that table. If what you want is to maintain no infrastructure and hold one billing relationship instead of five provider contracts, you pay for it with the top-up fee and accept a closed service in the request path. If you have a team that already runs its own services, LiteLLM gives you the same function without the overhead, at the cost of configuration and on-call duty. Vercel AI Gateway is worth checking too, since it declares zero markup and zero platform fee on tokens, which at high volume is a difference expressed directly in percent of the bill.
An entirely different route is leaving the cloud. If the workload can be handled by an open weights model, Ollama on your own hardware removes both the fee and the transfer of prompts to a third party. That is a choice for narrow, repeatable tasks, not for an application that needs a top tier model.
There is also a middle road, cheaper than both: going straight to a provider that prices itself below the market. DeepSeek exposes an OpenAI-compatible interface, so you change the base URL and the model name rather than the library, and drop an order of magnitude on the token bill. The price has two sides: the provider operates outside the European Union, which has to be settled before deployment where personal data is involved, and its model names are aliases pointing at the current release, so you cannot pin a specific version the way you can with other providers.
Common mistakes
The first is costing the work as if you called the provider directly. The token rate is the same, but every dollar flowing through this service was bought with a 5.5 percent fee attached. When comparing offers you have to add that fee to the provider rate, and at small top-ups also account for the 0.80 dollar minimum, which raises the effective percentage.
The second is treating the models list as an availability guarantee. If the last model on the list returns an error, that error reaches your application. Retrying with exponential backoff and honouring the Retry-After header remains your responsibility.
The third is ignoring differences between providers of the same model. One identifier can land on a provider with different quantization, a different maximum context, and different tool call support. If answer quality jumps between requests with no prompt change, this is usually why, and the remedy is quantizations, require_parameters or an explicit order.
The fourth is the attribution headers. X-OpenRouter-Title alone creates no application entry, because the identifier is HTTP-Referer. People copying older examples also send X-Title, which still works but does not substitute for the missing address.
The fifth is unhandled errors inside a stream. A response with a 200 status that ends with an event carrying finish_reason equal to error is a failure, even though the HTTP code says otherwise. Without checking that field an application stores empty responses and nobody notices for weeks.
The sixth is building production on free variants. The 20 requests per minute cap always applies, and the composition of the free model list changes over time. A :free identifier hard-coded into configuration is an outage scheduled for later.
The seventh is overlooking that you are adding a third party to the critical path. Prompts travel through someone else's infrastructure, one network hop is added, and an outage at the intermediary takes down every provider at once, which no fallback repairs. By default request content is not logged, but data policy settings and the choice of providers that do log stay on your side. An observability layer such as Langfuse does not change that arithmetic, it only gives you visibility into what is happening, and is itself one more piece to maintain.
FAQ
What does OpenRouter really cost above provider rates?
The fee is 5.5 percent of the credit top-up value on cards, with a 0.80 dollar minimum, and 5 percent for payments in USDC. For tokens themselves you pay the provider rate with no markup. Using your own keys is billed separately: above the allowance included in your plan, the fee is 5 percent of what the request would normally cost.
Are free models enough for production?
For a prototype and tests, yes. For production with real traffic, no. Variants with the :free suffix carry a hard cap of 20 requests per minute, and the daily limit is 50 requests below 10 dollars of lifetime purchases and 1000 requests above that threshold. The composition of the free model list changes over time.
How do I check which model and which provider served a request?
The response contains a model field with the identifier of the model that actually answered, plus a provider field. With a fallback list this is the only reliable way to know what you are paying for, and both fields belong in your logs on every request.
Can I force one provider and block the rest?
Yes. The provider.order field sets the order of attempts, provider.only restricts the pool to named providers, and provider.allow_fallbacks set to false stops the router reaching for others. The tighter the conditions, the more often you will see a 503 code meaning no matching provider.
Is moving from OpenRouter to a direct API difficult?
The transport layer follows the OpenAI specification, so returning to the direct OpenAI API amounts to changing the base address and the key. What is harder is recreating what the service did for you: provider selection, switching on failure, and one shared billing relationship. With Meta Llama models you additionally have to choose a specific infrastructure provider, because the same weights are not served by a single company.
Is enabling prompt logging worth the discount?
The discount is 1 percent of usage costs and requires consent to storing request and response content. With personal data or confidential client material this is usually a bad deal, since one percent does not cover the risk. With public, non-personal queries the decision is purely arithmetic.
Documentation lives on the OpenRouter site, current pricing and plan limits on the pricing page, and the full model catalogue in the public API.