How it works Features Docs Compare Blog GitHub
Self-host it Sign up
Documentation

Connect any provider.

polyrouter reaches every provider through one adapter interface — bring your own OpenAI or Anthropic key, connect a Claude Pro/Max or ChatGPT subscription over OAuth, or point at any OpenAI-compatible or local gateway. Credentials are AES-256-GCM encrypted at rest and decrypted only at call time. This page covers the provider kinds, the model catalog and pricing, and the per-provider knobs.

Maintained by Anthony Izzo · Last updated

Provider kinds

Every provider is one of four kinds. All of them speak to the proxy through a single adapter interface — chat, chatStream, listModels, and testConnection — so the routing core stays protocol-agnostic.

KindAuth methodNotes
api_keyBearer token or x-api-keyOpenAI, Anthropic, or any compatible API. Anthropic sends x-api-key; OpenAI sends Authorization: Bearer.
subscriptionOAuth Bearer + preset headersClaude Pro/Max & ChatGPT Plus/Pro, connected over OAuth — auth headers are pinned by the preset, see below. Any other subscription can be added by pasting a credential instead, in which case you pick the protocol and base URL yourself.
customSet by the protocolAny OpenAI- or Anthropic-compatible API at a base URL you set — you pick the protocol on the provider form.
localOptional API keySelf-host only — a cloud instance rejects the local kind outright (422 local providers require MODE=selfhosted). On self-host, local is the one kind the SSRF guard lets reach loopback (127.0.0.1, ::1); every other address rule still applies.
Encrypted Provider credentials are AES-256-GCM encrypted at rest. Adapters are built lazily per chain attempt, so an unused provider's credentials are never decrypted.

Bring your own API keys

Add a provider with a name, a kind, a protocol, a base URL, and your API key. The base URL is checked against the SSRF guard and the key is encrypted before the row is written. Nothing is dialed at save time — hit Test on the provider card afterwards to run a live testConnection, then Sync models. The key is only ever decrypted at call time.

Add provider · modal
Name
Anthropic (prod)
Kind
API keyOpenAI, Anthropic, DeepSeek, Groq… pay per token
SubscriptionReuse ChatGPT Plus / Claude Max quota (check ToS)
Custom endpointAny OpenAI/Anthropic-compatible base URL
LocalOllama, LM Studio, llama.cpp — free, on this box
Protocol
Anthropic-compatible
Base URL
https://api.anthropic.com
API key
sk-ant-••••••••••••••••
Advanced — patience for slow models
First response (s)
30 · instance default
Between chunks (s)
30 · instance default
Blank inherits the instance default. Timeouts count against this provider's circuit breaker.
Custom base URLs are SSRF-checked — private and metadata ranges are rejected. Credentials are encrypted at rest.
Cancel Add provider
How the key is sent
Anthropic x-api-key: sk-ant-…
OpenAI Authorization: Bearer sk-…
A custom provider uses whichever of these its chosen protocol implies — there is no separate auth-scheme setting. local providers usually need no key at all: the credential field is marked optional for that kind, and any key you do set is sent normally.
Subscription OAuth · moved

Connect a Claude or ChatGPT subscription

This section moved to its own page. Connecting a Claude Pro/Max or ChatGPT Plus/Pro subscription — the guided OAuth flow, presets and wire headers, refresh and rotation safety, and reauthorize — is now covered in full at Subscription routing.

A subscription is still one of the provider kinds listed above, and behaves like any other member of a tier chain once connected.

Models & pricing

polyrouter keeps a model catalog per provider and prices every request from a versioned pricing catalog. Sync a provider's /models list, edit prices per model on custom and local providers (API-key and subscription providers are priced from the catalog — the API rejects a manual price with 422), and let the daily refresh keep public prices current — without ever rewriting a recorded cost.

Provider card · Models
gpt-5-mini $0.15 / $0.6 per 1M
from the price catalog
anthropic/claude-sonnet-5 $3 / $15 per 1M
native family · estimate — provider lists $3.1 / $15.5
deepseek/deepseek-v4 $0.3 / $1.2 per 1M
provider-listed · estimate
ft:gpt-5-mini:acme unpriced
unpriced — cost not tracked
Custom / local · priced by you
qwen3:8b unpriced
Free (no per-token cost)
in $/1M
out $/1M
Save price
Pricing catalog
1,240 models · newest: refresh · effective 2026-08-22 · applied 2026-08-22
Last refreshed: 2026-08-22 (+38) Refresh now
Auto-refresh: scheduled — 30 4 * * * (UTC) · opt out: PRICING_REFRESH_SCHED_ENABLED=false
New prices apply to new requests; recorded costs never change.
This card lives in Settings and is admin only.
Note The daily LiteLLM refresh is on by default for self-host (opt out with PRICING_REFRESH_SCHED_ENABLED=false). New prices apply to new requests only — every recorded cost is a snapshot at request time and is never rewritten, and pricing rows are append-only with effective dates. Provider-listed prices (OpenRouter-style /models data) are captured at sync and used only as a last resort — when the catalog covers neither the exact model nor its native family, the listed rate is snapshotted as the recorded cost and flagged provider-listed · estimate. LiteLLM always wins; a listed price never overrides the catalog, and a recorded cost is never rewritten.

Output caps

The catalog carries more than prices: each entry's max_output_tokens is ingested from the same LiteLLM data. When a router-chosen chain runs, members whose known cap can't satisfy the request's ask are deferred behind members that can; if nothing in the chain can, the attempt is clamped to that member's own cap and finishes honestly with length instead of dying on a guaranteed provider 400. Both are recorded in the routing reason, as output_cap_deferred and output_cap_clamped. Which wire field the cap travels in is a separate per-provider setting — see Max-tokens spelling.

Scope An explicitly named model is never reordered or clamped — the guardrail applies only where the router picked the chain. An unknown cap changes nothing. Capacity outranks a subscription member's quota-first position across stages, never inside one, and the synthesized Anthropic max_tokens default is capped to the model's known limit.

Max-tokens spelling

OpenAI's o-series and reasoning models require max_completion_tokens and reject the older max_tokens; many local and legacy gateways accept only max_tokens. Each provider carries a max_tokens_spelling setting so the outbound request always uses the field that provider expects. This setting applies to OpenAI-compatible providers only — Anthropic-compatible providers always emit max_tokens, and the ChatGPT Responses protocol drops the cap entirely (it is rejected upstream). This one is API-only — the dashboard's provider form does not offer it. Set it with maxTokensSpelling on POST /api/providers or PATCH /api/providers/:id; omitting the field keeps the stored value, and an explicit null is rejected with 400.

SettingOutbound wire fieldFor
auto · defaultlocal kind → max_tokens; everything else → max_completion_tokensBaseline behavior
max_completion_tokensmax_completion_tokens on every OpenAI-compatible requestReasoning-model-first operators
max_tokensmax_tokens on every OpenAI-compatible requestLegacy / local-only gateways
Note The OpenAI-compatible ingress accepts either spelling (max_completion_tokens wins when both are sent); the Anthropic-compatible ingress reads max_tokens only, as that protocol requires. The resolver picks the effective cap before serializing, and the outbound request emits exactly one field. Existing local providers switch to max_tokens on upgrade; everything else is unchanged.

Upstream timeouts

Research-class models — Deep Research, Opus with thinking — can spend seconds to minutes on prefill before the first byte, then stream normally. A blanket 30-second first-event timeout would 503 them and trip the breaker. Set per-provider patience in the provider form's Advanced — patience for slow models block: First response (s) and Between chunks (s), in seconds, up to 3600. Leave a field blank to inherit — the placeholder reads 30 · instance default, read live from GET /api/providers/timeout-defaults rather than hard-coded. Raising patience here does not lift your client SDK's own timeout, which is the one bound the router can't reach.

FieldRangeWhen NULL
first_byte_timeout_ms1 s – 1 hInherits PROXY_FIRST_EVENT_TIMEOUT_MS (30 s)
idle_timeout_ms1 s – 1 hInherits PROXY_IDLE_TIMEOUT_MS (30 s)
Note Both values are validated to 1000–3600000 ms in two places: a DB CHECK constraint and the API DTO validators. In the steady state the breaker uses the same per-call deadline, so a genuinely hung connect or stalled read still trips cleanly. Recovery is deliberately more patient: a half-open probe runs with both bounds doubled, so a slow-but-healthy provider passes on the very workload that tripped it — a hung one still times out, at most twice as late, and only on the probe path. See Circuit breakers.

Circuit breakers

Each provider has a Redis-backed circuit breaker, shared across instances, that stops hammering a failing upstream. State is keyed per provider — every model on that provider shares one breaker, so a failing upstream is skipped wholesale. Provider rows are owner-scoped, so one tenant's breaker never affects another's. Model-specific faults (unknown_model, bad_request) deliberately do not trip it.

closed — 5 failures → open — 30 s cooldown → half-open — probe ok → closed
Threshold
5
failures to open
Cooldown
30 s
stays open
Probe patience
×2
widened bounds, capped 1 h
State TTL
5 min
Redis key
  • Generation-stamped. An incrementing generation counter means a stale completion from a previous generation can't corrupt current state under retries.
  • Long streams survive. The half-open probe lease renews, so a legitimate long stream isn't mistaken for a hang.
  • The probe is patient. A half-open probe runs with the member's first-byte and idle bounds doubled — min(2 × effective, 1 h) — and takes a lease wide enough to cover that silence, so a slow-but-healthy provider closes its breaker on the workload that tripped it. The multiplier is a constant, not a knob: per-provider first_byte_timeout_ms stays the tuning surface for heavy chains.
  • Long-stall trips. Hung connects and stalled reads — bounded by the provider's first-byte / idle timeouts — record a failure atomically.
  • Redis-authoritative. The server clock comes from Redis TIME to avoid instance skew; an in-memory store is the fallback if Redis is unavailable.

What a skipped member records

A chain member skipped by an open breaker is never dialed, so it must not read as an upstream failure. It records the pseudo-token skip@<model> in the request's routing reason — deliberately outside the provider-error taxonomy — alongside structured per-attempt metadata (attempt_failures: model, error kind, HTTP status, cascade leg; no free text). The requests inspector renders that as a Fallback trail — see Decision trail & inspector.

AttemptRendered outcome
gpt-5-mini · cheapunavailable · HTTP 529
claude-sonnet-5 · escalationrate_limit
llama-3.3-70b · escalationskipped — circuit open (provider not contacted)
Note The trail is data-driven off the stored column alone: rows written before 0.14.0 carry no attempt metadata, render exactly as they always did, and are never backfilled.

OpenRouter attribution

When a provider's base URL is on openrouter.ai, polyrouter adds two identity headers so it shows up in OpenRouter's app attribution.

HEADERS ADDED ON openrouter.ai PROVIDERS
HTTP-Referer: https://polyrouter.app
X-OpenRouter-Title: polyrouter
Note Non-secret and disclosed only to OpenRouter. The host gate matches the exact openrouter.ai host only — subdomains and spoofed suffixes are excluded — and your auth headers are never affected.
Routing & auto-routing API reference