All configuration is supplied through environment variables. Copy .env.example to .env and fill in the values before starting the server. When DATABASE_URL is not set, the app uses an in-memory store — no database is required for local development or demos.
The following variables are production-critical. The application starts with the development defaults, but using them in production is a security risk.
JWT_SECRET — change from dev-secret-change-me before any real traffic
CAUSELOOP_MASTER_KEY — must be set when DATABASE_URL is set; encrypts all per-workspace data keys
Core server
| Variable | Default | Required | Description |
|---|
PORT | 4000 | No | HTTP listen port. Caddy and docker-compose.yml proxy to this port. |
JWT_SECRET | dev-secret-change-me | Yes (prod) | HS256 signing key for JWTs issued at /auth/token. Generate with python3 -c "import secrets; print(secrets.token_urlsafe(48))". |
CORS_ORIGINS | http://localhost:3000 | Yes (prod) | Comma-separated list of allowed CORS origins. Set to your frontend domain in production, e.g. https://app.causeloop.ai. |
Authentication — Clerk
These variables are only required if you use the /auth/exchange endpoint to verify Clerk session JWTs issued by your frontend.
| Variable | Default | Required | Description |
|---|
CLERK_JWKS_URL | https://regular-mosquito-10.clerk.accounts.dev/.well-known/jwks.json | No (required for Clerk auth) | Your Clerk instance’s JWKS endpoint. Find it in the Clerk dashboard under API Keys → Advanced. Replace the example value with your own instance URL. |
CLERK_AUDIENCE | (empty) | No | Optional expected aud claim in Clerk JWTs. Leave empty to skip audience validation. |
CLERK_AUTHORIZED_PARTIES | http://localhost:3000 | No | Comma-separated list of authorized frontend origins. Clerk uses this to validate the azp claim. |
If CLERK_JWKS_URL is set to the placeholder value from .env.example, the app still starts but /auth/exchange will verify JWTs against Anthropic’s demo Clerk instance — which will reject tokens from your own frontend. Always replace it with your own JWKS URL.
LLM providers
Leave both API key variables empty to run in offline mock mode — the mock provider returns plausible stub responses and is suitable for local development and tests.
| Variable | Default | Required | Description |
|---|
ANTHROPIC_API_KEY | (empty) | No | Anthropic API key. When set, Anthropic Claude is available as an LLM provider. |
OPENAI_API_KEY | (empty) | No | OpenAI API key. When set, GPT-4o models are available. |
LLM_PROVIDER_ORDER | anthropic,openai,mock | No | Comma-separated resolution order. The first provider with a usable key is selected. |
USE_MOCK_LLM | false | No | Force the offline mock regardless of which API keys are set. Useful for CI or air-gapped environments. |
OPENAI_MODEL_REASONING | gpt-4o | No | OpenAI model used for multi-step reasoning tasks (pattern analysis, predictions). |
OPENAI_MODEL_FAST | gpt-4o-mini | No | OpenAI model used for quick completions (summaries, labels). |
Database
| Variable | Default | Required | Description |
|---|
DATABASE_URL | (unset) | No (prod: Yes) | PostgreSQL connection string. When unset, the app uses an in-memory store. For Neon: postgresql://<user>:<pass>@<endpoint>.neon.tech/<db>?sslmode=require. |
POSTGRES_PASSWORD | (unset) | Only with docker-compose | Used by docker-compose.yml to set the Postgres superuser password and inject DATABASE_URL into the API container. Do not set this if you are using an external managed database. |
For local Postgres: DATABASE_URL=postgresql://localhost:5432/causeloopFor Neon (hosted, SSL required): DATABASE_URL=postgresql://<user>:<pass>@<endpoint>.neon.tech/<db>?sslmode=require
Encryption
| Variable | Default | Required | Description |
|---|
CAUSELOOP_MASTER_KEY | (empty) | Yes when DATABASE_URL is set | Base64-encoded 32-byte AES-256 key (the Key Encryption Key). Wraps per-workspace data keys stored in workspace_keys. Never commit this value. Generate with: python3 -c "import os,base64;print(base64.b64encode(os.urandom(32)).decode())" |
If DATABASE_URL is set but CAUSELOOP_MASTER_KEY is empty, the application will raise a KeyError_ on the first request that touches an encrypted column. Generate and store this secret securely before pointing the app at a real database.
Frontend integration
Add these to your Next.js (or any frontend) .env.local:
NEXT_PUBLIC_USE_MOCK=0
NEXT_PUBLIC_API_BASE_URL=https://api.causeloop.ai/v1
NEXT_PUBLIC_WS_URL=wss://api.causeloop.ai/v1/stream
For local development:
NEXT_PUBLIC_API_BASE_URL=http://localhost:4000/v1
NEXT_PUBLIC_WS_URL=ws://localhost:4000/v1/stream
The API client reads the JWT from localStorage['causeloop_token'].
Full .env.example
PORT=4000
JWT_SECRET=dev-secret-change-me
CORS_ORIGINS=http://localhost:3000
# Leave LLM keys empty to use the offline mock provider:
ANTHROPIC_API_KEY=
OPENAI_API_KEY=
LLM_PROVIDER_ORDER=anthropic,openai,mock
USE_MOCK_LLM=false
OPENAI_MODEL_REASONING=gpt-4o
OPENAI_MODEL_FAST=gpt-4o-mini
# Clerk — required for POST /auth/exchange to verify Clerk session JWTs (RS256)
CLERK_JWKS_URL=https://regular-mosquito-10.clerk.accounts.dev/.well-known/jwks.json
CLERK_AUDIENCE=
CLERK_AUTHORIZED_PARTIES=http://localhost:3000
# Postgres (when set, the app uses the Postgres repo instead of in-memory)
# DATABASE_URL=postgresql://localhost:5432/causeloop
# Envelope-encryption master key (base64 of 32 bytes). REQUIRED when DATABASE_URL is set.
# Generate: python3 -c "import os,base64;print(base64.b64encode(os.urandom(32)).decode())"
CAUSELOOP_MASTER_KEY=