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.
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.
Kind
Auth method
Notes
api_key
Bearer token or x-api-key
OpenAI, Anthropic, or any compatible API. Anthropic sends x-api-key; OpenAI sends Authorization: Bearer.
subscription
OAuth Bearer + preset headers
Claude Pro/Max & ChatGPT Plus/Pro, connected over OAuth. Auth headers are pinned by the preset — see below.
custom
Configurable
Any OpenAI- or Anthropic-compatible API at a base URL you set — you pick the protocol on the provider form.
local
Optional API key
Self-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.
EncryptedProvider 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 · encrypted at rest
sk-ant-••••••••••••••••
CancelAdd provider
How the key is sent
Anthropicx-api-key: sk-ant-…
OpenAIAuthorization: Bearer sk-…
Byte-identical to pre-OAuth behavior. custom providers set their own scheme; local providers usually need no key — the credential field is optional for this kind, and any key you do set is sent normally.
Subscription OAuth · v0.3
Connect a Claude or ChatGPT subscription
Instead of paying per-token API prices, connect a Claude Pro/Max or ChatGPT Plus/Pro subscription you already have. polyrouter runs a guided PKCE authorization-code flow against the provider's own identity service, stores the tokens in an encrypted typed envelope, and refreshes them automatically before they expire.
localhost:3000
Your keys, your accounts — requests go straight from this box to the provider.Add provider
Claude Pro / Maxsubscription
Healthy
https://api.anthropic.com/
credential set (encrypted)
● Connected · auto-refreshes · expires in 7h
TestSync modelsModelsEditDelete
ChatGPT Plus / Prosubscription
Last action failed
https://chatgpt.com/
credential set (encrypted)
Sign-in expired — reconnect to keep routing through this subscription.Reauthorize
TestSync modelsModelsEditDelete
OpenAI (prod)API key
Healthy
https://api.openai.com/v1
credential set (encrypted)
TestSync modelsModelsEditDelete
Add provider · modal · Subscription
Choose a preset
Kind
API key
Subscription
Custom endpoint
Local
Connect a subscription
Claude Pro / MaxSign in with your account — tokens are stored encrypted and auto-refresh.
ChatGPT Plus / ProSign in with your account — tokens are stored encrypted and auto-refresh.
Other subscription (paste a credential)
Then paste what you land on
1. Open the sign-in link and approve access.
Open sign-in link ↗
2. Paste what you land on — the full redirect URL or the code#state string.
Redirect URL or code
https://…/callback?code=…&state=… or code#state
BackConnect
Reusing a flat-rate subscription programmatically may violate the provider's ToS — pair it with a pay-per-token fallback.
The connect flow
1Start
POST /api/providers/oauth/start with the preset mints a PKCE pair and state, stores a single-use connect session in Redis, and returns an authorize URL.
2Sign in & paste
You authorize at claude.ai or auth.openai.com. Claude uses a code-display callback — the page shows a code#state string to copy. ChatGPT redirects to a dead localhost tab: the page fails to load and you copy the whole URL from the address bar. The paste field accepts either form.
3Complete
POST /api/providers/oauth/complete verifies the state, exchanges the code, encrypts the typed token envelope, and writes a kind=subscription provider.
Presets & wire headers
Claude Pro / Maxpreset: claude
Protocol anthropic_compatible. Models are synced from /v1/models with the OAuth token.
Protocol openai_responses. A streaming-only backend folded into buffered responses; no models endpoint — models are bundled and a probe model validates the connection.
Authorization: Bearer …chatgpt-account-id: …OpenAI-Beta: responses=experimentalno x-api-key · no fingerprints
Refresh & rotation safety
Pre-request only. Credentials resolve at adapter-build time on every proxied request; a token more than five minutes from expiry is served straight from the decrypted envelope.
Single-flight. At most one refresh per provider per instance — concurrent requests share the one in-flight refresh.
Advisory-locked. The refresh re-reads the envelope inside a per-provider lock; credential edits and reauthorize completions serialize on the same lock, so a refresh can never clobber a concurrent change.
Rotation-safe. Refresh tokens rotate, and the locked re-read always refreshes from the newest envelope.
Breaker-neutral. A credential failure is fallback-eligible and never trips the circuit breaker.
Reauthorize vs refresh
Automatic refresh
Runs near expiry, before the request. The breaker is never reset — it preserves genuine upstream failure history. A transient identity-provider failure serves the still-valid token behind a 30-second backoff.
One-click Reauthorize
When a credential goes durably bad, polyrouter persists credential_error: reauthorize_required and stops re-probing per request. The dashboard surfaces a Reauthorize action that runs a fresh connect flow bound to the same provider row and resets the breaker.
Heads-upReusing a flat-rate consumer subscription programmatically may violate the provider's terms of service. Pair a subscription provider with a pay-per-token API-key fallback in the tier chain — the dashboard surfaces this when you connect one.
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.
New prices apply to new requests; recorded costs never change.
This card lives in Settings and is admin only.
NoteThe 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.
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).
Setting
Outbound wire field
For
auto · default
local kind → max_tokens; everything else → max_completion_tokens
Baseline behavior
max_completion_tokens
max_completion_tokens on every OpenAI-compatible request
Reasoning-model-first operators
max_tokens
max_tokens on every OpenAI-compatible request
Legacy / local-only gateways
NoteInbound requests may use either spelling on every protocol; 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 under the provider form's Advanced section.
Field
Range
When NULL
first_byte_timeout_ms
1 s – 1 h
Inherits PROXY_FIRST_EVENT_TIMEOUT_MS (30 s)
idle_timeout_ms
1 s – 1 h
Inherits PROXY_IDLE_TIMEOUT_MS (30 s)
NoteBoth values are validated to 1000–3600000 ms by DB CHECK and Zod. The breaker uses the same per-call deadline, so a genuinely hung connect or stalled read still trips cleanly — you get patience for slow models without losing failure detection.
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 window
10 s
half-open lease
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.
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.
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.
NoteNon-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.