Skip to main content
All environment variables are read by the API server at startup. There is no hot-reload — restart the server after changing any variable. The .env.example file in the repository root documents all variables with example values.

Required variables

These variables must be set for the server to start.
string
required
PostgreSQL connection string. Format: postgresql://user:password@host:port/database
string
required
ClickHouse HTTP endpoint. Include the protocol and port.
string
required
ClickHouse username.
string
required
ClickHouse password. Can be empty for local development.
string
required
ClickHouse database name.
string
required
Redis hostname (not a full redis:// URL, despite the name) — used together with REDIS_PORT, REDIS_USERNAME, and REDIS_PASSWORD below to connect. If unset, the API server starts without Redis (BullMQ queues, API-key caching, and rate limiting are disabled — not suitable for production).
string
required
Random secret used to sign authentication tokens. Must be at least 32 characters.
Generate a suitable value: openssl rand -hex 32
string
required
The base URL of the API server, used for auth callback URLs.
string
required
Allowed CORS origins. Comma-separate multiple values. Rejected at startup if set to * in production.
string
Extra browser origins allowed to make sign-in and other cookie-bearing auth calls, comma-separated. Use it when the dashboard runs on an origin other than https://app.zespan.com or http://localhost:3000, which are always allowed. This setting only adds origins; it never removes the defaults.Each value must be a bare origin (scheme://host[:port]) with no path, query string, or credentials. Wildcards and malformed values are ignored. When NODE_ENV=production, http: origins are ignored unless the host is localhost or 127.0.0.1, so use https: for real hosts.
This is separate from CORS_ORIGIN, which controls which origins can call the API from a browser.

Security and secrets

string
AES-256-GCM encryption key for BYOK (bring-your-own-key) LLM connection credentials and HTTP target credentials — the keys customers store when connecting their own OpenAI/Anthropic/etc. accounts via LLM Connections. Must be a 64-character hex string (32 bytes).Required in production. New credentials are only ever encrypted with this key; SSO_SECRET is never used for new writes. Credentials written by earlier versions under SSO_SECRET can still be read, so keep SSO_SECRET set.If the value is set but is not exactly 64 hex characters (surrounding quotes or spaces count), saving and reading stored credentials fails. If it is not set, saving new credentials fails and values written earlier under SSO_SECRET still read. Either way the API still starts, logs an error naming the variable, and reports it on the health endpoint to callers inside the container only: docker compose exec -T api wget -qO- http://127.0.0.1:3001/health | jq -r .credentialKey prints ok, missing or invalid (the key itself is never shown, and requests from outside the container don’t get the field).
string
Optional, decrypt-only. When you rotate LLM_CONNECTION_SECRET, set this to the old value so credentials encrypted with it can still be read.
string
Encryption key for stored SSO/OIDC client secrets (see SSO). Must be a 64-character hex string (32 bytes). LLM connection credentials saved by earlier versions under this key remain readable, so keep it set.
string
Comma-separated list of exact hostnames, IP addresses, and IPv4 CIDRs (/8 or narrower) that LLM connections may reach even though they resolve to private addresses — for example an in-VPC model gateway. Loopback, link-local, and cloud-metadata ranges are always refused. Unset means nothing private is allowed. An invalid value stops the API from starting. Set it on both the API and background-job processes. See Private and self-hosted endpoints.
string
The AWS principal (an IAM role or account root ARN) your deployment uses to assume customers’ Bedrock roles. It appears as Principal in the trust policy shown under Amazon Bedrock → Assume role. If unset, the dashboard shows a placeholder and a warning.
string
Server-side pepper mixed into project API key hashing before storage. Security-critical — treat like a database credential and never expose it to the client.
Secret used to sign CSRF-protection cookies. Falls back to BETTER_AUTH_SECRET if unset — set a dedicated value in production to keep session-signing and CSRF-cookie-signing keys isolated.
Cookie domain for the auth session and CSRF cookies. Set this when the web app and API live on different subdomains of the same parent domain (e.g. .yourdomain.com) so the session cookie is shared across them.
string
default:"1"
Origin-allowlist CSRF enforcement for state-mutating dashboard requests. Enabled by default; set to 0 to disable (not recommended in production).

OAuth / SSO login providers

Configure any of these provider pairs to enable that login method. See SSO for the Team/Scale-gated enterprise OIDC connector, which is configured per-organization rather than via env vars.
string
Google OAuth client ID for “Sign in with Google.”
string
Google OAuth client secret.
string
GitHub OAuth client ID for “Sign in with GitHub.”
string
GitHub OAuth client secret.
string
Microsoft OAuth client ID for “Sign in with Microsoft.”
string
Microsoft OAuth client secret.
string
Microsoft Entra ID (Azure AD) tenant ID to restrict sign-in to a specific tenant.

AI features

Zespan’s own AI features (ZespanPilot, Natural Language, the Overview Digest, Cost Optimizer, Anomaly Detection, Root Cause Analysis, and Semantic Judge guardrail rules) need a model provider key configured on the server. Contact us for the supported configuration for your deployment. Features that run on your own LLM connection use the keys you save in Settings → LLM Connections and need nothing here.

Email

string
Resend API key for transactional email (account verification, alert notifications, team invitations). Without this, emails will not be sent.
string
default:"noreply@zespan.com"
The sender address for transactional emails.

Payments (Polar.sh)

These variables are only required if you want to use the billing integration. Note that Free has no Polar product (it’s the no-subscription default) and Scale is sold as a custom contract, so there is no POLAR_PRODUCT_* variable for either — only Solo, Pro, and Team map to real Polar products.
string
Polar.sh API access token.
string
default:"production"
Set to sandbox to use Polar’s sandbox environment during development/testing. Any other value (or unset) uses production.
string
Webhook signing secret from Polar.sh. Used to verify that webhook payloads originate from Polar.
string
Polar product ID for the Solo plan.
string
Polar product ID for the Pro plan.
string
Polar product ID for the Team plan.
string
Polar product ID used to bill extra seats beyond a plan’s included member count.
string
Polar usage-meter ID for reporting trace overage (traces ingested beyond the monthly quota) for billing.
string
URL Polar redirects to after a successful checkout.
string
URL the Polar customer portal returns to after the customer closes it. Falls back to {NEXT_PUBLIC_APP_URL}/settings/billing if unset.

Object storage (exports)

Account data exports, end-user exports, evidence packs, and audit-log exports are stored in Cloudflare R2. Set all four R2_* variables to enable them. The bucket must not be publicly readable.
string
Cloudflare R2 account ID.
string
R2 access key ID.
string
R2 secret access key.
string
R2 bucket name for exports and evidence packs. Must not be publicly readable.
string
default:"off"
Optionally copy a minimised record of every audit entry (identifiers, action, status, risk, time, and a hash of the full entry — no request payload, IP address, or user agent) to a separate object-storage bucket. One of off, r2, or s3-object-lock. Configure the bucket with AUDIT_MIRROR_BUCKET, and for r2 also AUDIT_MIRROR_R2_ACCOUNT_ID, AUDIT_MIRROR_R2_ACCESS_KEY_ID, and AUDIT_MIRROR_R2_SECRET_ACCESS_KEY. Use a dedicated bucket, with credentials scoped to that bucket only.
string
Deprecated alias for AUDIT_MIRROR_BUCKET. Set on its own, it selects the s3-object-lock sink and logs a warning. Use the AUDIT_MIRROR_* variables instead.
string
default:"us-east-1"
AWS region for S3_BUCKET operations.
string
Path prefix used when writing audit-log export files to object storage.
boolean
default:"false"
Audit retention deletes nothing until this is true (or 1). Enable it only after you have exported the audit history you need to keep. See Audit log → Integrity and retention.
number
default:"90"
Age in days after which non-critical audit entries are deleted, once AUDIT_RETENTION_DELETE_ENABLED is on. Critical security events — role changes, member removals, organization deletion, billing events, API key creation and rotation, ZespanPilot actions, denied authorizations, and critical-risk entries — are never deleted. Has no effect while AUDIT_RETENTION_DELETE_ENABLED is off.

Privacy and data lifecycle

number
default:"7"
Days between deleting a project and permanently purging its data. Whole number from 1 to 365; anything else falls back to 7. Doesn’t apply to organization deletion. See Data retention and deletion.
number
default:"30"
Days after a plan change during which the longer of the old and new plan’s retention applies. Whole number from 0 to 365; 0 turns the grace off; anything else falls back to 30.
string
default:"off"
on makes the project setting Redact personal data (PII) also detect person names. Off by default because name detection flagged too many product, model and place names. Emails, phone numbers, card numbers and the other kinds are detected either way. See PII redaction.
number
default:"7"
Days a finished account data export stays downloadable. Whole number from 1 to 30.
string
Comma-separated addresses that receive a daily email listing account data requests that are within 5 days of, or past, their 30-day deadline. The email contains request ids and types only. If unset, no email is sent.

ZespanPilot

string
Optional override for the model ZespanPilot uses for its main reasoning loop.
string
Higher-capability model tier ZespanPilot escalates to for complex queries.
string
Mid-tier fast model ZespanPilot uses for routine queries.
string
Lightweight/cheapest model tier, used for simple or free-tier demo queries.
string
A Zespan project API key used to self-trace ZespanPilot’s own AI calls. When set, Pilot’s model calls appear in your traces under the zespanpilot agent tag.
string
Ingest URL ZespanPilot’s self-tracer sends spans to.
string
The project ID to associate with ZespanPilot self-traces.
number
default:"10"
Overrides how many free ZespanPilot demo queries a Free-plan organization gets per month.
number
default:"20"
Minimum recent spans a project must have before the free-tier ZespanPilot demo will answer a query (otherwise it responds with a warning instead of consuming a query).

Simulations

string
Set to allow simulation webhook/HTTP targets to resolve to private/internal IP ranges. Leave unset in production — this exists for local/self-hosted testing only.
string
Comma-separated hostname allowlist restricting which endpoints simulations are permitted to call.
number
Minutes after which a simulation batch with no progress is considered stuck and swept by the worker.

Workers

string
Restricts a worker process to a subset of BullMQ queues (e.g. ingest, evaluate, simulate, notify, billing). Omit to run all worker types in one process — set this per-process when you want to scale specific queues independently.
Some maintenance jobs only run in the unrestricted worker. Expiring stale approval requests and sweeping expired CLI device grants are wired into the default (all) worker only. If every worker process sets WORKER_TYPE, no process consumes those queues and their rows accumulate indefinitely — keep at least one worker without the variable set. See abandoned logins.
string
Optional separate PostgreSQL connection string for worker processes. Falls back to DATABASE_URL if unset — use this to point workers at a different connection pool/replica than the API server.

Web app (frontend)

These variables are read by the Next.js web app container (apps/web). Create a separate .env.web or .env.local file for the web app.
string
required
Base URL of the API server. Used both by next.config.js to proxy /api/* requests to the Fastify backend (same-origin API calls in production) and by the frontend’s Axios client for direct calls.
string
Build-time argument that enables product analytics in the web app. Empty by default, so analytics is off. Even when set, analytics loads only for users who opt in under Settings → Profile → Privacy. The value is baked into the web image and visible in the browser bundle, so rebuild the image after changing it.
string
Build-time argument for the analytics API host. Use https://api-eu.mixpanel.com for EU data residency.
string
Build-time argument. When set, the sign-up and sign-in pages show a See a demo link. Accepted values are exactly /demo or an https:// URL without credentials; anything else is ignored and the link is hidden. External URLs open in a new tab. Leave it unset to hide the link.
string
required
Public URL of the web app itself. Used for auth callback URLs, email links, and the CLI sign-in approval URL.
The API reads this to tell the CLI where to send people to approve a sign-in. zespan auth login opens {NEXT_PUBLIC_APP_URL}/device. Point it at your dashboard origin, not the API origin — if it is unset or points at the API host, the browser opens a page that serves nothing and no one can complete a login. See zespan auth.The approval screen that URL opens also shows the IP the sign-in request came from, which is only meaningful if your proxy overwrites X-Forwarded-For — see Client IP behind a proxy.

Optional server configuration

string
Bearer token that protects GET /metrics. While unset, /metrics returns 404. Generate one with openssl rand -hex 32. See Health checks and monitoring.
boolean
default:"false"
When true, GET /ready reports not ready if the background-job process has stopped reporting. Otherwise that check is advisory.
number
default:"3001"
Port the API server listens on.
string
default:"info"
Logging verbosity. Accepted values: fatal, error, warn, info, debug, trace.
string
Standard Node environment flag. Set to production to enforce production-only checks (e.g. rejecting a wildcard CORS_ORIGIN, using secure cookies).
number
Number of Node.js cluster workers the API process forks in production. Has no effect when NODE_ENV isn’t production or when running under Vitest.
number
default:"6379"
Redis port, paired with the REDIS_URL hostname above.
string
Redis username, if your Redis instance requires ACL auth.
string
Redis password. Required in production: run Redis with authentication enabled and set the same password for the API and background-job processes. Don’t expose Redis outside your private network.
string
default:"off"
Set to on (or 1, true) to store OpenTelemetry spans from /v1/traces under the kind they declare: OpenInference RETRIEVER, RERANKER, EMBEDDING, AGENT, CHAIN and GUARDRAIL spans, and GenAI retrieval and invoke_workflow operations. While off, those spans are stored exactly as before. Read for each span, so changing it needs no restart of ingest workers. The TypeScript SDK reads the same variable in your application: with it on, spans forwarded with captureExternalSpans keep their openinference.span.kind as their kind.
string
default:"on"
The compatibility mapping for spans sent to /v1/traces: the older GenAI names (gen_ai.usage.prompt_tokens), OpenInference names (llm.model_name, llm.token_count.*) and the attribute names AI gateways publish fill a model, provider, token count, user, session or text that the standard names left empty, and a span with a model and tokens but no operation name is stored as a model call. off stores spans as before. Read for each span, so changing it needs no restart of ingest workers.
string
default:"on"
AI gateway handling for spans sent to /v1/traces: the gateway is detected from the span, and its proxy, routing, infrastructure and retry or fallback attempt spans are stored as gateway spans (with no model) so they are not counted as model calls. off stores gateway spans as before. See AI gateways.
string
default:"unset"
Set to off to turn the compatibility mapping and the gateway handling above off together; spans are then stored as they were before these features existed. It does not turn the gateway identity deny list off.
string
default:"on"
Removes the identity metadata AI gateways attach to their spans (the end user’s e-mail, the requester’s IP address, key hashes and ids, team and key aliases, request headers) before spans are stored, whatever the project’s content capture settings are. Set to off to keep them (they are then stored under the project’s capture rules). Applies to spans of a recognised gateway only.
string
default:"off"
Set to on to use the cost figure an AI gateway reports for a call only when Zespan has no price for the model. Off: every call is priced from Zespan’s table and your organization’s custom prices, and the gateway’s figure is kept as evidence only. The two figures are never added.
number
default:"8388608"
Largest body the OTLP endpoint (/v1/traces) accepts, measured after decompression, in bytes (between 1 MB and 16 MB). A bigger body is answered 413, which exporters do not retry. Rows of one request are queued in jobs of about 1 MB each. The native SDK ingest keeps its own 1 MB limit. Read when the API starts.
number
default:"3000"
Requests per minute per project the OTLP endpoint accepts (1 to 100000); over the limit the answer is 429 with Retry-After. The monthly event quota still applies. The native SDK ingest keeps its own limit. Read on every request.
string
default:"unset"
Merging of a call seen by both the Zespan SDK and an AI gateway that exports to /v1/traces, so its tokens and cost are counted once. Unset, nothing is merged and both rows are counted. Set to on to merge: both rows are kept, the gateway’s span is nested under the SDK span when it can be, and the tokens and cost of the row that arrived second are set to 0. Unless this is off, a gateway’s model-call row can be held back for up to 90 seconds in projects that also send SDK events, so the SDK’s row arrives first; off stores gateway rows at once and merges nothing. Matching uses the trace link (gatewayCorrelation) or the provider’s response id; when Zespan cannot tell, both rows are counted. Call counts built from hourly totals still count both rows as calls.
string
default:"shadow"
Stack-layer attribution (“where it started”, see Stack layers). shadow shows origins to owners and admins only; on shows them to every member; off computes nothing and hides stored values. The Failed at line follows the same switch. Any other value reads as shadow.
string
default:"on"
Set to off to stop assigning incidents by the stack layer they started in. Incidents are then assigned to the owner of the dominant agent only, as before. It does nothing while no origin is shown. See Instrumentation.

SDK variables

These are read by the Zespan SDKs inside your application, not by the Zespan server.
string
LangChain’s own setting. Set to true to have LangChain run callback handlers in the background. The Zespan LangChain handler then no longer puts a tool’s trace in place before the tool runs, so model, MCP or HTTP calls made inside a tool start traces of their own (see LangChain). Unset, the Zespan handler is awaited.
string
default:"on"
Set to off (or false, 0, no) to stop the SDK from recording each guardrail check as a guardrail span. Each span counts toward your event quota. Read at call time. The check, its result and its errors are unchanged. The same switch is the guardrailSpans: false (TypeScript) or guardrail_spans=False (Python) option. See Guardrails.
string
default:"on"
Set to off (or false, 0, no) to stop the SDK from tracing calls made through MCP clients. Read at call time, so it takes effect for clients that already exist. The same switch is the mcpAutopatch: false (TypeScript) or mcp_autopatch=False (Python) option. See MCP.
string
default:"off"
Self-hosted only. Set to on to let /v1/ingest and /v1/traces answer 503 with the code ingest_overloaded and a Retry-After header when live traffic is backed up: more than 2,000 live jobs waiting, the oldest waiting more than 120 seconds, or Redis using more than 70% of maxmemory. Import backlogs do not count. A failed or stale check never rejects requests. The SDKs wait the Retry-After and retry.