> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zespan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Environment variables reference

> Complete reference for all environment variables used by the Zespan API server, including database connections, auth configuration, AI providers, and integrations.

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.

<ParamField body="DATABASE_URL" type="string" required>
  PostgreSQL connection string. Format: `postgresql://user:password@host:port/database`

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  DATABASE_URL="postgresql://lumiq:lumiq@localhost:5432/zespan"
  ```
</ParamField>

<ParamField body="CLICKHOUSE_HOST" type="string" required>
  ClickHouse HTTP endpoint. Include the protocol and port.

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  CLICKHOUSE_HOST="http://localhost:8123"
  ```
</ParamField>

<ParamField body="CLICKHOUSE_USER" type="string" required>
  ClickHouse username.
</ParamField>

<ParamField body="CLICKHOUSE_PASSWORD" type="string" required>
  ClickHouse password. Can be empty for local development.
</ParamField>

<ParamField body="CLICKHOUSE_DB" type="string" required>
  ClickHouse database name.

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  CLICKHOUSE_DB="zespan"
  ```
</ParamField>

<ParamField body="REDIS_URL" type="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).

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  REDIS_URL="localhost"
  REDIS_PORT="6379"
  ```
</ParamField>

<ParamField body="BETTER_AUTH_SECRET" type="string" required>
  Random secret used to sign authentication tokens. Must be at least 32 characters.

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  BETTER_AUTH_SECRET="a-very-long-random-string-of-at-least-32-characters"
  ```

  Generate a suitable value: `openssl rand -hex 32`
</ParamField>

<ParamField body="BETTER_AUTH_URL" type="string" required>
  The base URL of the API server, used for auth callback URLs.

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  BETTER_AUTH_URL="https://api.yourdomain.com"
  ```
</ParamField>

<ParamField body="CORS_ORIGIN" type="string" required>
  Allowed CORS origins. Comma-separate multiple values. Rejected at startup if set to `*` in production.

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  CORS_ORIGIN="https://app.yourdomain.com"
  # Or for multiple origins:
  CORS_ORIGIN="https://app.yourdomain.com,https://staging.yourdomain.com"
  ```
</ParamField>

## Security and secrets

<ParamField body="LLM_CONNECTION_SECRET" type="string">
  AES-256-GCM encryption key for BYOK (bring-your-own-key) LLM connection credentials — the API keys customers store when connecting their own OpenAI/Anthropic/etc. accounts via **LLM Connections**. Must be a 64-character hex string (32 bytes).

  If unset, the server falls back to `SSO_SECRET` and logs a warning. At least one of the two must be set to a valid 64-char hex string, or encryption calls throw. Set this explicitly and separately from `SSO_SECRET` in production so the two credential domains don't share key material.

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  LLM_CONNECTION_SECRET="<64-char hex string>"
  # Generate one with:
  # node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
  ```
</ParamField>

<ParamField body="SSO_SECRET" type="string">
  Encryption key used for two purposes: (1) encrypting stored SSO/OIDC client secrets (see [SSO](/platform/sso)), and (2) as the fallback encryption key for `LLM_CONNECTION_SECRET` if that variable is unset. Must be a 64-character hex string (32 bytes).
</ParamField>

<ParamField body="API_KEY_PEPPER" type="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.
</ParamField>

<ParamField body="COOKIE_SECRET" type="string">
  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.
</ParamField>

<ParamField body="AUTH_COOKIE_DOMAIN" type="string">
  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.
</ParamField>

<ParamField body="CSRF_ENFORCE" type="string" default="1">
  Origin-allowlist CSRF enforcement for state-mutating dashboard requests. Enabled by default; set to `0` to disable (not recommended in production).
</ParamField>

## OAuth / SSO login providers

Configure any of these provider pairs to enable that login method. See [SSO](/platform/sso) for the Team/Scale-gated enterprise OIDC connector, which is configured per-organization rather than via env vars.

<ParamField body="GOOGLE_CLIENT_ID" type="string">
  Google OAuth client ID for "Sign in with Google."
</ParamField>

<ParamField body="GOOGLE_CLIENT_SECRET" type="string">
  Google OAuth client secret.
</ParamField>

<ParamField body="GITHUB_CLIENT_ID" type="string">
  GitHub OAuth client ID for "Sign in with GitHub."
</ParamField>

<ParamField body="GITHUB_CLIENT_SECRET" type="string">
  GitHub OAuth client secret.
</ParamField>

<ParamField body="MICROSOFT_CLIENT_ID" type="string">
  Microsoft OAuth client ID for "Sign in with Microsoft."
</ParamField>

<ParamField body="MICROSOFT_CLIENT_SECRET" type="string">
  Microsoft OAuth client secret.
</ParamField>

<ParamField body="MICROSOFT_TENANT_ID" type="string">
  Microsoft Entra ID (Azure AD) tenant ID to restrict sign-in to a specific tenant.
</ParamField>

## AI model providers

At least one AI provider key is required to use ZespanPilot, Cost Optimizer, Anomaly Detection, and Root Cause Analysis.

<ParamField body="ANTHROPIC_API_KEY" type="string">
  Anthropic API key. Used for ZespanPilot (cost optimization reasoning) and root cause analysis.

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  ANTHROPIC_API_KEY="sk-ant-..."
  ```
</ParamField>

<ParamField body="GOOGLE_GENERATIVE_AI_API_KEY" type="string">
  Google Generative AI API key. Used for ZespanPilot (function calling), anomaly explanations, and NLQ.

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  GOOGLE_GENERATIVE_AI_API_KEY="AIza..."
  ```
</ParamField>

<ParamField body="OPENAI_API_KEY" type="string">
  OpenAI API key. Used as a fallback AI provider for some features.

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  OPENAI_API_KEY="sk-..."
  ```
</ParamField>

<ParamField body="OPENROUTER_API_KEY" type="string">
  OpenRouter API key. Used for overview summaries via free models.

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  OPENROUTER_API_KEY="sk-or-..."
  ```
</ParamField>

## Email

<ParamField body="RESEND_API_KEY" type="string">
  Resend API key for transactional email (account verification, alert notifications, team invitations). Without this, emails will not be sent.

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  RESEND_API_KEY="re_..."
  ```
</ParamField>

<ParamField body="EMAIL_FROM" type="string" default="noreply@zespan.com">
  The sender address for transactional emails.

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  EMAIL_FROM="noreply@yourdomain.com"
  ```
</ParamField>

## 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.

<ParamField body="POLAR_ACCESS_TOKEN" type="string">
  Polar.sh API access token.
</ParamField>

<ParamField body="POLAR_ENV" type="string" default="production">
  Set to `sandbox` to use Polar's sandbox environment during development/testing. Any other value (or unset) uses production.
</ParamField>

<ParamField body="POLAR_WEBHOOK_SECRET" type="string">
  Webhook signing secret from Polar.sh. Used to verify that webhook payloads originate from Polar.
</ParamField>

<ParamField body="POLAR_PRODUCT_SOLO" type="string">
  Polar product ID for the Solo plan.
</ParamField>

<ParamField body="POLAR_PRODUCT_PRO" type="string">
  Polar product ID for the Pro plan.
</ParamField>

<ParamField body="POLAR_PRODUCT_TEAM" type="string">
  Polar product ID for the Team plan.
</ParamField>

<ParamField body="POLAR_PRODUCT_EXTRA_SEAT" type="string">
  Polar product ID used to bill extra seats beyond a plan's included member count.
</ParamField>

<ParamField body="POLAR_METER_ID_TRACE_OVERAGE" type="string">
  Polar usage-meter ID for reporting trace overage (traces ingested beyond the monthly quota) for billing.
</ParamField>

<ParamField body="POLAR_SUCCESS_URL" type="string">
  URL Polar redirects to after a successful checkout.
</ParamField>

<ParamField body="POLAR_PORTAL_RETURN_URL" type="string">
  URL the Polar customer portal returns to after the customer closes it. Falls back to `{NEXT_PUBLIC_APP_URL}/settings/billing` if unset.
</ParamField>

## Object storage (exports)

<ParamField body="R2_ACCOUNT_ID" type="string">
  Cloudflare R2 account ID, used to store audit-log export files.
</ParamField>

<ParamField body="R2_ACCESS_KEY_ID" type="string">
  R2 access key ID.
</ParamField>

<ParamField body="R2_SECRET_ACCESS_KEY" type="string">
  R2 secret access key.
</ParamField>

<ParamField body="R2_BUCKET" type="string">
  R2 bucket name for audit-log exports.
</ParamField>

<ParamField body="S3_BUCKET" type="string">
  S3 bucket used for DSAR (data subject access request) data exports.
</ParamField>

<ParamField body="S3_AUDIT_BUCKET" type="string">
  S3 bucket used to mirror audit-log records, if configured separately from `R2_BUCKET`.
</ParamField>

<ParamField body="AWS_REGION" type="string" default="us-east-1">
  AWS region for `S3_BUCKET` operations.
</ParamField>

<ParamField body="AUDIT_EXPORT_STORAGE_PATH" type="string">
  Path prefix used when writing audit-log export files to object storage.
</ParamField>

<ParamField body="AUDIT_RETENTION_DAYS" type="number" default="90">
  Number of days audit log entries are retained before the retention worker purges them.
</ParamField>

## ZespanPilot

<ParamField body="ZESPANPILOT_MODEL" type="string">
  Default Gemini model ZespanPilot uses for its main reasoning loop.
</ParamField>

<ParamField body="ZESPANPILOT_MODEL_PRO" type="string">
  Higher-capability model tier ZespanPilot escalates to for complex queries.
</ParamField>

<ParamField body="ZESPANPILOT_MODEL_FLASH" type="string">
  Mid-tier fast model ZespanPilot uses for routine queries.
</ParamField>

<ParamField body="ZESPANPILOT_MODEL_LITE" type="string">
  Lightweight/cheapest model tier, used for simple or free-tier demo queries.
</ParamField>

<ParamField body="ZESPANPILOT_TRACE_API_KEY" type="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.
</ParamField>

<ParamField body="ZESPANPILOT_TRACE_URL" type="string">
  Ingest URL ZespanPilot's self-tracer sends spans to.
</ParamField>

<ParamField body="ZESPANPILOT_TRACE_PROJECT_ID" type="string">
  The project ID to associate with ZespanPilot self-traces.
</ParamField>

<ParamField body="FREE_PILOT_QUERIES_PER_MONTH" type="number" default="10">
  Overrides how many free ZespanPilot demo queries a Free-plan organization gets per month.
</ParamField>

<ParamField body="PILOT_MIN_SPANS_FOR_DEMO" type="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).
</ParamField>

## Simulations

<ParamField body="SIMULATIONS_ALLOW_PRIVATE_HTTP" type="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.
</ParamField>

<ParamField body="SIMULATIONS_HTTP_ALLOWLIST" type="string">
  Comma-separated hostname allowlist restricting which endpoints simulations are permitted to call.
</ParamField>

<ParamField body="SIMULATIONS_STUCK_BATCH_MINUTES" type="number">
  Minutes after which a simulation batch with no progress is considered stuck and swept by the worker.
</ParamField>

## Workers

<ParamField body="WORKER_TYPE" type="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.
</ParamField>

<ParamField body="DATABASE_WORKER_URL" type="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.
</ParamField>

## 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.

<ParamField body="NEXT_PUBLIC_API_BASE_URL" type="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.

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  NEXT_PUBLIC_API_BASE_URL="https://api.yourdomain.com"
  ```
</ParamField>

<ParamField body="NEXT_PUBLIC_APP_URL" type="string" required>
  Public URL of the web app itself. Used for auth callback URLs and email links.

  ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  NEXT_PUBLIC_APP_URL="https://app.yourdomain.com"
  ```
</ParamField>

## Optional server configuration

<ParamField body="PORT" type="number" default="3001">
  Port the API server listens on.
</ParamField>

<ParamField body="LOG_LEVEL" type="string" default="info">
  Logging verbosity. Accepted values: `fatal`, `error`, `warn`, `info`, `debug`, `trace`.
</ParamField>

<ParamField body="NODE_ENV" type="string">
  Standard Node environment flag. Set to `production` to enforce production-only checks (e.g. rejecting a wildcard `CORS_ORIGIN`, using secure cookies).
</ParamField>

<ParamField body="API_WORKERS" type="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.
</ParamField>

<ParamField body="REDIS_PORT" type="number" default="6379">
  Redis port, paired with the `REDIS_URL` hostname above.
</ParamField>

<ParamField body="REDIS_USERNAME" type="string">
  Redis username, if your Redis instance requires ACL auth.
</ParamField>

<ParamField body="REDIS_PASSWORD" type="string">
  Redis password.
</ParamField>
