PPactDocs
Reference

Environment variables

The operator-facing environment variables the Pact backend and web app actually read at runtime — database, secrets, providers, storage, voice, and cost controls.

Pact is configured almost entirely through environment variables. This page enumerates the ones the code actually reads — grouped by concern — so an operator provisioning a tenant or a self-hosted stack knows what to set. Every variable below is resolved somewhere under api/, core/, utils/, or web/src/; if a name isn't here, the code doesn't read it.

These are platform-level, not per-user

Almost everything on this page is set once per deployment (Fly secrets, Container App config, or .env for local dev). Tenant-scoped credentials — a customer's own OpenAI key, their email provider, their Twilio SID — are stored encrypted in the database via the BYOK flow and the settings UI, never in these environment variables. Do not put a customer's secret in a shared env var.

Core runtime

VariablePurpose
DATABASE_URLSelects Postgres or SQLite at runtime. Resolved in core/db.py and core/auth/db.py. Absent → local SQLite.
REDIS_URLRedis connection for cache/counters when CACHE_BACKEND/COUNTER_BACKEND select it.
SECRET_KEYBase signing secret; falls back to a random per-process key if unset (see core/sso_domains.py).
JWT_SECRETSigns/encrypts auth tokens and handoff links; also used as the Fernet key source (core/security/fernet.py).
AUTH_TOKEN_ENCRYPTION_KEYPreferred Fernet key for at-rest token encryption; JWT_SECRET is the fallback.
CONSENT_TOKEN_SECRETSigns consent tokens; also an accepted fallback for JWT signing.
ENV / ENVIRONMENT / PACT_ENVEnvironment name used in logging and guard rails.
APP_VERSION / GIT_SHA / PACT_BUILD_ID / PACT_DEPLOYED_ATBuild/version stamps surfaced in health and observability.

Database pool tuning

DB_POOL_SIZE, DB_MAX_OVERFLOW, DB_POOL_TIMEOUT, DB_POOL_RECYCLE, DB_CONNECT_TIMEOUT, DB_STATEMENT_TIMEOUT_MS, and the DB_KEEPALIVES_* family tune the main SQLAlchemy pool. Auth traffic runs on a separate pool tuned by AUTH_DB_POOL_SIZE, AUTH_DB_MAX_OVERFLOW, AUTH_DB_POOL_TIMEOUT, AUTH_DB_POOL_RECYCLE, and AUTH_MAIN_POOL_WARN_RATIO — auth is a critical path and is deliberately isolated from bulk work.

Secrets backend

SECRET_STORE selects where platform secrets resolve from (core/secret_store.py): env (default) reads process environment; key_vault reads Azure Key Vault, which then requires KEY_VAULT_URI (or AZURE_KEY_VAULT_URI). An unknown value logs a warning and falls back to env. Related KMS material: AWS_KMS_KEY_ID, GCP_KMS_KEY_NAME.

Auth, SSO & CORS

VariablePurpose
ALLOWED_ORIGINS / FRONTEND_ORIGINCORS allow-list for the web app.
SSO_AUTO_PROVISIONAuto-create users on first SSO login.
SSO_JWKS_TTL_SECSCache TTL for SSO JWKS.
ENTRA_CLIENT_ID / ENTRA_TENANT_IDMicrosoft Entra ID SSO.
WEBAUTHN_RP_ID / WEBAUTHN_RP_NAME / WEBAUTHN_RP_ORIGINSPasskey / WebAuthn relying-party config.
PACT_ACCEPT_ANY_BEARERDev-only bearer bypass — must be unset in production.
PACT_SIGNUP_REQUIRE_EMAIL_VERIFYGate self-serve signup on email verification.
RECAPTCHA_SECRET_KEY / TURNSTILE_SECRET_KEYBot-protection on public forms.

Email & sequences

EMAIL_PROVIDER and SEQUENCE_PROVIDER select the send path; SEQUENCE_PROVIDER defaults to sandbox (no real mail) and only sends for real when set to resend or sendgrid (core/channels/email.py). Provider credentials and knobs: RESEND_API_KEY, RESEND_FROM, RESEND_WEBHOOK_SECRET, RESEND_SANDBOX_KEY, RESEND_TEST_SEND_CAP_PER_HOUR, SENDGRID_API_KEY, SENDGRID_WEBHOOK_HMAC. Addressing and safety: EMAIL_FROM_ADDR, EMAIL_FROM_NAME, SEQUENCE_FROM_EMAIL, SEQUENCE_FROM_NAME, and the global kill switch EMAIL_GLOBAL_OFF. Tenant invites use PACT_INVITE_PROVIDER, PACT_INVITE_FROM_EMAIL, PACT_INVITE_FROM_NAME.

Sandbox by default

Leaving SEQUENCE_PROVIDER unset means outbound sequence mail is sandboxed — messages are rendered and recorded but not delivered. Set it to resend or sendgrid and configure EMAIL_GLOBAL_OFF deliberately before expecting real delivery.

AI, embeddings & LLM

OPENAI_API_KEY, OPENAI_MODEL, OPENAI_TIMEOUT_SECONDS, OPENAI_MAX_RETRIES, and OPENAI_COST_OVERRIDE drive the OpenAI path; OLLAMA_URL / OLLAMA_MODEL drive a local model. EMBEDDINGS_BACKEND (core/embeddings/compute.py) defaults to noop and can be set to voyage (with VOYAGE_API_KEY) among others. ANTHROPIC_FALLBACK_TO_ENV controls whether Anthropic calls may fall back to a platform env key when a tenant hasn't supplied one. AI_SUBSYSTEM_DISABLED and AI_SUGGESTIONS_ENABLED are broad AI kill/enable switches; ANSWER_ENGINE_BUDGET_CENTS and ANSWER_ENGINE_FRESHNESS_HALFLIFE_DAYS bound the answer engine.

Object storage

Blob storage (core/object_store.py) prefers the Tigris family when present and falls back to the legacy Cloudflare R2 family:

  • Tigris (current platform backend): TIGRIS_ENDPOINT_URL, TIGRIS_BUCKET, TIGRIS_ACCESS_KEY_ID, TIGRIS_SECRET_ACCESS_KEY (plus a TIGRIS_BAA_* variant for BAA-scoped buckets).
  • Cloudflare R2 (legacy, still supported): R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET, optional R2_ENDPOINT_URL (plus the R2_BAA_* variant).

When both are set, TIGRIS_* wins. Local/dev falls back to a filesystem root under OBJECT_STORE_ROOT.

Voice

Voice is heavily flag-gated. Master switch VOICE_ENABLED; inbound push VOICE_INBOUND_PUSH_ENABLED; the no-dead-air typing bed VOICE_WAIT_BED_ENABLED; proactive coworker VOICE_PROACTIVE_SCHEDULER_ENABLED; MCP launch flag VOICE_MCP_PUBLIC_LAUNCH_FLAG; signature bypass (dev/self-test only) VOICE_MCP_SKIP_SIGNATURE; SIP guard VOICE_SIP_ALLOW_PRIVATE; token TTL VOICE_TOKEN_TTL_S. Recording/notes/voicemail land under VOICE_RECORDINGS_ROOT, VOICE_NOTES_ROOT, and VOICEMAILS_ROOT, with retention governed by CALL_RECORDING_RETENTION_DAYS and CALL_UPLOAD_RETENTION_DAYS. Telephony credentials (TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) apply where the platform provider is used.

Web Push VAPID keys are not env vars

Push VAPID keypairs are tenant-scoped and stored in tenant_push_config, not in an environment variable — each tenant gets its own keypair, exposed to the browser via /v1/push/vapid-public-key. Don't look for a VAPID_* env var; there isn't one.

Billing & cost controls

BILLING_PROVIDER (core/billing.py) defaults to noop; set stripe to enable metering with STRIPE_API_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_METERED_PRICE_IDS, and the guard BILLING_ALLOW_LIVE_KEY. Trial and credit caps: TRIAL_FREE_CREDIT_CENTS, TRIAL_AI_CAP_CENTS, FREE_CREDIT_CENTS_CAP, QUOTA_DEFAULT_ENRICHMENT_BUDGET_USD_CENTS. Infra cost rollup reads COST_FLY_MONTHLY_CENTS, COST_GITHUB_ACTIONS_MONTHLY_CENTS, R2_MONTHLY_CENTS, and the Fly/Actions provider knobs.

Observability

SENTRY_DSN / SENTRY_TRACES_SAMPLE_RATE, APPLICATIONINSIGHTS_CONNECTION_STRING, HONEYCOMB_API_KEY, and the OpenTelemetry family (OTEL_EXPORTER, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME). CI-runner observability reads GITHUB_OBSERVABILITY_TOKEN / GITHUB_OBSERVABILITY_REPO.

Cost-sensitive switches

A live OTEL exporter and per-event billing are cost surfaces that have caused budget incidents before. Do not enable OTEL_EXPORTER against a live collector or turn on per-event metering without a budgeted plan.

Frontend (NEXT_PUBLIC_*)

The web app reads NEXT_PUBLIC_API_BASE_URL (and legacy NEXT_PUBLIC_API_BASE), NEXT_PUBLIC_APP_URL / NEXT_PUBLIC_APP_BASE_URL, NEXT_PUBLIC_APP_NAME / NEXT_PUBLIC_APP_TAGLINE, NEXT_PUBLIC_SENTRY_DSN, NEXT_PUBLIC_SW_ENABLED (service worker/PWA), NEXT_PUBLIC_COLLAB_WS_URL, NEXT_PUBLIC_DOCS_BASE_URL, NEXT_PUBLIC_STATUS_URL, NEXT_PUBLIC_RELEASE_CHANNEL, and NEXT_PUBLIC_WEB_VITALS_SAMPLE_RATE. Anything prefixed NEXT_PUBLIC_ is compiled into the client bundle and is public — never put a secret there.