How it works Features Docs Compare GitHub ↗
Self-host it Log in
Documentation

Configuration reference.

The installer generates the five required secrets into a mode-600 .env. Most other variables are Docker Compose pass-throughs you set in that same file. Each value is validated by a Zod schema at boot — an out-of-range or malformed value fails the boot fast, naming the offending variable and the constraint it broke, so an instance never starts silently misconfigured.

Tip Only the five secrets are required, and the installer writes them for you. Every other variable ships a production-ready default — set one only to change behavior. Cross-field rules are enforced too: LOW must be lower than HIGH, unknown ROUTING_AUTO_LAYERS tokens reject boot, and a broken semantic bundle refuses to start rather than run degraded.

Required secrets

Five secrets protect sessions, agent keys, and encrypted credentials at rest. The installer generates all five — you should never have to write these by hand. They are generated once and never rotated automatically. Rotating PROVIDER_CREDENTIAL_KEY or NOTIFY_CREDENTIALS_SECRET orphans every stored provider/channel credential (you re-enter them), and rotating POSTGRES_PASSWORD breaks DB auth against the existing volume — the installer refuses to regenerate an existing .env for exactly this reason.

Variable
Default
Purpose
BETTER_AUTH_SECRET
auto-gen
Session signing key for the dashboard auth plane (Better Auth).
API_KEY_HMAC_SECRET
auto-gen
HMAC key that verifies agent API keys on the hot path; also derives the domain-separated per-tenant HMAC used by Layer 2 learning.
PROVIDER_CREDENTIAL_KEY
auto-gen
AES-256-GCM key for provider credentials (plain API keys and OAuth envelopes); also encrypts opt-in body capture at rest.
NOTIFY_CREDENTIALS_SECRET
auto-gen
AES-256-GCM key for notification-channel configuration.
POSTGRES_PASSWORD
auto-gen
PostgreSQL database password. Feeds the default DATABASE_URL.

App & mode

Server binding, public origin, trusted dashboard origin, sign-in providers, and the self-host vs. cloud mode switch. A non-Compose run binds loopback by default — expose it only behind a reverse proxy.

Variable
Default
Purpose
MODE
selfhosted
selfhosted (default) or cloud. Gates local-model use, loopback, opt-in body capture, localhost login, and the SSRF loopback exception.
NODE_ENV
development
development, production, or test. Compose pins production. On a loopback-bound, non-production self-host the four app secrets fall back to fixed dev values instead of being required — anything network-reachable or production demands the real ones.
PORT
3001
HTTP server port inside the container.
POLYROUTER_HOST
127.0.0.1
Host interface the published port binds to — loopback by default.
POLYROUTER_PORT
3001
Host port the app is published on.
APP_URL
unset
Public origin for auth callbacks and cookies. Set it when exposing the instance behind a reverse proxy. Compose interpolates it into BETTER_AUTH_URL; on bare-node or Kubernetes it has no effect.
BETTER_AUTH_URL
http://127.0.0.1:3001
The variable the app actually reads for its auth base URL — set it directly on non-Compose deploys. Must be a valid URL or boot rejects.
DASHBOARD_ORIGIN
http://localhost:3000
Origin trusted for dashboard sessions — better-auth trustedOrigins and the session-guard Origin check; also the dev-only CORS allow-list (CORS is enabled only when NODE_ENV=development, and Compose pins production).
BIND_ADDRESS
0.0.0.0 (in Compose)
HTTP bind inside the container. The shipped docker-compose.yml already hard-sets 0.0.0.0 — it is not a .env pass-through. Host-side exposure is controlled by POLYROUTER_HOST in the port mapping. 127.0.0.1 is the default only for a non-Compose run.
POLYROUTER_IMAGE
Prebuilt multi-arch image override (e.g. ghcr.io/izzoa/polyrouter:latest) to skip the local build.
TRUSTED_PROXY_CIDRS
unset
CIDRs of reverse proxies allowed to set X-Forwarded-For.
GOOGLE_CLIENT_ID · GOOGLE_CLIENT_SECRET · GITHUB_CLIENT_ID · GITHUB_CLIENT_SECRET · DISCORD_CLIENT_ID · DISCORD_CLIENT_SECRET
unset
Social sign-in credentials. A provider appears on the login page only when both its id and its secret are set; email/password is always enabled.
SEED_DATA
false
Set true to seed a dev admin account at boot. Allowed only on a loopback-bound, non-production, self-hosted instance — anywhere else it throws at boot rather than seeding.

Database & cache

Connection strings for the PostgreSQL 16 and Redis 7 services the stack ships with. The defaults resolve inside the Compose network — override them only to point at external datastores.

Variable
Default
Purpose
DATABASE_URL
postgresql://polyrouter:${POSTGRES_PASSWORD}@postgres:5432/polyrouter
PostgreSQL connection string. Migrations run on boot.
REDIS_URL
redis://redis:6379
Redis connection string. Backs circuit breakers, budget counters, rate limits, and Layer 2 learned state.

Routing

Layer toggles, the structural and cascade thresholds, and the global proxy timeouts. Out-of-range values reject boot rather than being silently clamped, and both structural thresholds are held to four decimal places for the calibration rails.

Variable
Default
Purpose
ROUTING_AUTO_LAYERS
structural
Comma-separated layer tokens — structural, cascade, semantic. cascade and semantic both imply structural; unknown tokens reject boot naming the offender.
ROUTING_STRUCTURAL_HIGH_THRESHOLD
0.6
Layer 1 high-band threshold, range 0–1.
ROUTING_STRUCTURAL_LOW_THRESHOLD
0.25
Layer 1 low-band threshold. Must be lower than HIGH or the bands collapse and boot rejects.
ROUTING_STRUCTURAL_BASELINE_ALPHA
0.2
Per-agent baseline EMA weight, interval (0, 1]. The baseline is subtracted so a big harness preamble can't force everything into the top tier.
ROUTING_STRUCTURAL_WEIGHTS
built-ins
JSON override for the Layer 1 classifier weights and the reasoning adjustment.
ROUTING_CASCADE_QUALITY_THRESHOLD
0.5
Cascade escalation threshold — accept the cheap tier at or above it, escalate to the strong tier below.
ROUTING_CASCADE_CHEAP_TIMEOUT_MS
30000
Cascade cheap-tier timeout (ms).
PROXY_FIRST_EVENT_TIMEOUT_MS
30000
Global time-to-first-token deadline. Raise for slow local models.
PROXY_IDLE_TIMEOUT_MS
30000
Global idle timeout. Raise alongside the first-token deadline.
PROXY_EVENT_TIMEOUT_MARGIN_MS
500
Added to PROXY_FIRST_EVENT_TIMEOUT_MS to derive the core per-event stream deadline (30 500 ms by default), so the adapter's typed unavailable timeout wins a pre-headers race.
PROXY_MAX_BODY_BYTES
10485760
Maximum /v1 request body size in bytes (10 MiB default).
Note Per-provider upstream timeouts (first_byte_timeout_ms / idle_timeout_ms, range 1 s–1 h) are set on the provider row in the dashboard, not in .env. A NULL there inherits these global proxy defaults.

Layer 2 — semantic optional

Layer 2 refines only the structurally-ambiguous slice by embedding request text with a local ONNX model. It stays absent until you point SEMANTIC_MODEL_PATH at a model bundle — an unset path means zero import and zero overhead.

Variable
Default
Purpose
SEMANTIC_MODEL_PATH
unset
Path to a local model bundle. Unset = the module is absent; set and valid = Layer 2 capability; set and broken = boot fails fast naming the file and reason.
SEMANTIC_TIMEOUT_MS
50
Per-embed hard timeout (ms). Out-of-range rejects boot.
SEMANTIC_MAX_INPUT_CHARS
2000
Input cap before tokenization (chars).
SEMANTIC_CONCURRENCY
2
Concurrent-inference cap. Saturation skips the layer for that request.
SEMANTIC_HIGH_THRESHOLD
0.15
Layer 2 high-band threshold (score ≥ high → high).
SEMANTIC_LOW_THRESHOLD
0.15
Layer 2 low-band threshold (score ≤ −low → low). Held to four decimal places.
Note Enabling Layer 2 needs the model runtime present: run the batteries-included -semantic image (ONNX runtime + reference MiniLM model pre-baked, SEMANTIC_MODEL_PATH preset) or install the exact-pinned onnxruntime-node optional peer — then add semantic to ROUTING_AUTO_LAYERS and opt in per tenant. The baseline image ships no runtime and no model.
Compose Layer the override file to get the semantic knobs: docker compose -f docker-compose.yml -f docker-compose.semantic.yml up -d. The base file passes no SEMANTIC_* variable through, so setting one without the override has no effect (SEMANTIC_MODEL_PATH is baked into the -semantic image, so Layer 2 still runs — just untunable).

Layer 2 — learning default off

The per-tenant learning loop tracks a tenant's own outcome-labeled traffic to nudge learned centroids. It is off by default for privacy — the per-tenant semanticLearning preference defaults off. These knobs bound how far learned state may drift from the bundled anchors and how the daily sweep is gated.

Variable
Default
Purpose
SEMANTIC_LEARNING_ALPHA
0.2
EMA fold coefficient, interval (0, 0.5].
SEMANTIC_LEARNING_MAX_DRIFT
0.35
Maximum cosine-distance clamp from the bundled centroid (spherical SLERP).
SEMANTIC_LEARNING_MIN_SAMPLES
50
Per-label floor for a sweep rotation. Must be ≥ MIN_COHORT.
SEMANTIC_LEARNING_MIN_COHORT
8
Minimum accumulated embeddings before a cohort may flush to Redis — never a single raw vector.
SEMANTIC_LEARNING_MAX_COHORTS
4096
Bounded per-process cohort map.
SEMANTIC_LEARNING_COOLDOWN_H
24
Cooldown between applies (hours). Must stay under STATE_TTL_D × 24.
SEMANTIC_LEARNING_STATE_TTL_D
30
Learned active-state TTL (days).
SEMANTIC_LEARNING_SCHED_ENABLED
true
Enable the per-tenant learning sweep worker (only acts on tenants that opted in).
SEMANTIC_LEARNING_SCHED_CRON
0 3 * * *
Learning sweep cron.

Calibration

The daily per-tenant sweep that narrows the structural ambiguous band from your own outcomes. It is conservative, rail-bounded, audited, and one-click revertible — a poisoned or stale calibrated pair can never fail routing; it simply reads as the instance defaults.

Variable
Default
Purpose
CALIBRATION_SCHED_ENABLED
true
Enable the per-tenant calibration worker.
CALIBRATION_SCHED_CRON
0 4 * * *
Calibration sweep cron.
CALIBRATION_WINDOW_DAYS
14
Evidence window (days).
CALIBRATION_MIN_EDGE_SAMPLES
50
Minimum fresh edge-zone samples (hard floor 50).
CALIBRATION_STEP
0.02
Bounded per-run threshold step.
CALIBRATION_MAX_DRIFT
0.1
Maximum total drift from the instance thresholds.
Compose These reach the container only if your docker-compose.yml declares them in the app service's environment: block — it is an explicit allow-list, not a blanket pass-through. They were added to the shipped compose file after v0.9.2; on 0.9.2 and earlier, add them yourself (or use an env_file: as the prebuilt-image compose in the README does), or setting them in .env silently leaves calibration on its defaults.

Budgets

Budget enforcement fault behavior and the reconciler schedule. Enforcement itself uses atomic Redis counters that stay correct across instances.

Variable
Default
Purpose
BUDGET_FAIL_OPEN
true
On a Redis or enforcement fault, blocking budgets admit the request. Set false for a hard cap that returns 503.
BUDGET_SCHED_ENABLED
true
Enable the budget reconciler worker.
BUDGET_SCHED_CRON
* * * * *
Budget reconciler cron (every minute). Boot fails if its fire interval exceeds half of BUDGET_STALE_MS.
BUDGET_STALE_MS
180000
How stale the reconcile heartbeat may get before block enforcement stops trusting the counters and routes through BUDGET_FAIL_OPEN. It must clear at least twice the BUDGET_SCHED_CRON fire interval or boot fails naming the gap.
BUDGET_REDIS_TIMEOUT_MS · BUDGET_RECONCILE_TIMEOUT_MS · BUDGET_CACHE_TTL_MS · BUDGET_CACHE_MAX
50 · 2000 · 10000 · 5000
Hot-path Redis read bound, the reconciler's more generous write bound, and the in-process per-owner budget cache (TTL in ms, capped owners) that keeps the block check DB-free.

Pricing

The daily refresh of the bundled pricing catalog from the public LiteLLM source. New prices apply to new requests only — recorded costs are immutable and never rewritten.

Variable
Default
Purpose
PRICING_REFRESH_URL
LiteLLM catalog
Source for pricing refreshes — the public LiteLLM catalog.
PRICING_REFRESH_SCHED_ENABLED
true
Daily auto-refresh of the pricing catalog. Set exactly false to opt out — it is an exact-string compare, so 0, no and off leave it on. The worker additionally requires MODE=selfhosted.
PRICING_REFRESH_SCHED_CRON
30 4 * * *
Pricing refresh cron. Invalid syntax fails boot rather than silently reporting as enabled.
PRICING_FETCH_TIMEOUT_MS · PRICING_MAX_BYTES
15000 · 8000000
Timeout and body-size cap on the SSRF-guarded catalog pull.

Notifications

SMTP and/or Apprise channels for budget alerts and blocks, provider-down, and failure spikes, plus the weekly summary. Everything is queued off the request path — a failing channel never blocks a request or budget enforcement.

Variable
Default
Purpose
SMTP_HOST · SMTP_PORT · SMTP_USER · SMTP_PASS · SMTP_FROM · SMTP_SECURE
unset · 587 · unset · unset · unset · starttls
Server SMTP transport — host, port, credentials, from-address, and TLS mode. Active only when both SMTP_HOST and SMTP_FROM are set. SMTP_SECURE accepts exactly none | starttls | tls.
APPRISE_API_URL · NOTIFY_ALLOWED_ENDPOINTS
unset
Optional Apprise URL fan-out. The SSRF guard requires the port-bounded allowlist entry for a private-range host.
NOTIFY_APPRISE_EGRESS_CONFIRMED
false
Cloud-mode acknowledgement required before Apprise delivery runs.
NOTIFY_WEEKLY_ENABLED · NOTIFY_WEEKLY_CRON
false · 0 8 * * 1
Weekly summary producer toggle and cron. Off by default — set NOTIFY_WEEKLY_ENABLED=true (exact string). Default schedule is Mondays 08:00 UTC.
NOTIFY_FAILURE_THRESHOLD · NOTIFY_FAILURE_WINDOW_MS
20 · 900000
Failure-spike producer threshold and window — 20 failures in a 15-minute window.

Dashboard events

The server-sent event stream at /api/events that pushes live dashboard updates instead of polling. Every knob is defaulted, and the cross-field rules fail boot fast rather than silently loosening a bound.

Variable
Default
Purpose
EVENTS_ENABLED
true
Dashboard event stream toggle. Set false to turn it off.
EVENTS_HEARTBEAT_MS
25000
Keep-alive interval — it must stay under the ~60 s idle-reap window common to proxies and CDNs, so boot fails at 60000 or above. Lower it for a stricter intermediary.
EVENTS_RECONCILE_MS
30000
Low-rate verification read that confirms the stream is current. It verifies the stream rather than replacing it, so boot fails below 2500.
EVENTS_REVALIDATE_MS
derived
Server-side authorization revalidation bound — the revocation-detection window. Unset it derives from the heartbeat (at most 15 000 ms and never looser than it), so lowering the heartbeat can't fail boot on an unrelated knob; an explicit value above the heartbeat rejects boot.
EVENTS_MAX_STREAMS_PER_OWNER
6
Concurrent streams one account may hold (open tabs), range 1–64.
EVENTS_QUEUE_LIMIT
256
Bounded per-stream outbound queue, range 8–10 000.
EVENTS_NUDGE_COALESCE_MS
1000
Window over which analytics nudges are coalesced into one push. Minimum 1000.
Compose These reach the container only if your docker-compose.yml declares them in the app service's environment: block — it is an explicit allow-list, not a blanket pass-through. They were added to the shipped compose file after v0.9.2; on 0.9.2 and earlier, add them yourself (or use an env_file: as the prebuilt-image compose in the README does), or setting them in .env silently leaves the event stream on its defaults.

Observability

Prometheus metrics on by default, plus opt-in OpenTelemetry tracing. Health is always at /api/health.

Variable
Default
Purpose
OTEL_ENABLED
false
OpenTelemetry tracing toggle.
OTEL_EXPORTER_OTLP_ENDPOINT
SDK default
OTLP/HTTP collector endpoint (batched export). A malformed URL fails boot; an absent one falls back to the exporter's own default.
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
unset
Per-signal traces override. The exporter reads it directly and it takes precedence over the generic endpoint.
OTEL_SERVICE_NAME
polyrouter
Service name attached to exported spans.
METRICS_ENABLED
true
Prometheus /metrics endpoint toggle.
← Security & privacy Operations →