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
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. Any other subscription can be added by pasting a credential instead, in which case you pick the protocol and base URL yourself. |
| custom | Set by the protocol | 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. |
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.
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.
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.
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.
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.
| 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 |
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.
| 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) |
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.
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.
| Attempt | Rendered outcome |
|---|---|
| gpt-5-mini · cheap | unavailable · HTTP 529 |
| claude-sonnet-5 · escalation | rate_limit |
| llama-3.3-70b · escalation | skipped — circuit open (provider not contacted) |
When a provider's base URL is on openrouter.ai, polyrouter adds two identity headers so it shows up in OpenRouter's app attribution.
HTTP-Referer: https://polyrouter.app
X-OpenRouter-Title: polyrouter