Skip to main content

Configuration

The API reads its configuration once, from environment variables: Worker bindings in staging and production, process.env (plus .env at the repository root) under Node. parseConfig in apps/api/src/config.ts turns them into an AppConfig, and fails closed: anything that would weaken security outside development throws at start-up.

Variables​

VariableDefaultPurpose
ENVIRONMENTproduction in parseConfig; development under node.tsOne of development, test, staging, production. Anything else throws. Shown as a badge in the app outside production.
AUTH_MODEdev under node.ts; unset in the Workerdev enables development sign-in (x-dev-user). Only allowed when ENVIRONMENT is development or test. Unset means Cloudflare Access.
DEV_USER_EMAILdev@davies-group.comThe caller when AUTH_MODE=dev and no x-dev-user header is sent.
ACCESS_TEAM_DOMAIN—Cloudflare Access team domain, e.g. davies.cloudflareaccess.com. The JWT issuer and JWKS location. Required unless AUTH_MODE=dev.
ACCESS_AUD—The Access application's AUD tag. Required unless AUTH_MODE=dev.
ALLOWED_EMAIL_DOMAINSdavies-group.com under node.ts; empty otherwiseComma-separated domains provisioned automatically on first sign-in.
BOOTSTRAP_ADMIN_EMAILSdev@davies-group.com under node.ts; empty otherwiseComma-separated emails that become administrators when first provisioned (regardless of domain).
IDENTITY_KEKGenerated into DATA_DIR/dev-identity-kek under node.tsBase64 of 32 random bytes: the key-encryption key for per-engagement identity keys. Required outside development and test. Never change it once set.
AI_PROVIDERgemini when GEMINI_API_KEY is set, otherwise nonegemini, fake or none. fake (the development stand-in) is refused outside development and test; gemini requires GEMINI_API_KEY.
GEMINI_API_KEY—Secret. Enables Gemini.
GEMINI_MODELgemini-3.1-flash-liteThe default model; a prompt version can name another.
AI_MONTHLY_BUDGET_USD50AI features pause when estimated month-to-date spend reaches this.
GEMINI_ALLOWED_MODELSGEMINI_MODELComma-separated models a prompt version may name; GEMINI_MODEL is always allowed. Other models are refused when a prompt is saved or published.
AI_ASK_DAILY_LIMIT100"Ask the data" questions per person per UTC day.
AI_INPUT_PRICE_PER_MTOK0.1USD per million input tokens, for the spend estimate.
AI_OUTPUT_PRICE_PER_MTOK0.4USD per million output tokens.
DATABASE_URL—Postgres connection string (the owner role). Required in the Worker (secret) and for npm run db:migrate; optional under Node (PGlite otherwise).
DATA_DIR.dataNode only: where PGlite and the dev identity key live, relative to the repository root. The e2e suite points it at a temporary folder.
PORT8787Node only: the API's port.
HOST127.0.0.1Node only: the interface the API listens on. Under development sign-in, requests must still address it as localhost.
API_PORT8787The Vite dev server's proxy target (read from the shell environment, not .env).

Numeric settings fall back to their defaults if missing, blank, negative or not a number.

.env.example

.env.example documents most of these for local use. It doesn't yet mention AI_PROVIDER; add AI_PROVIDER=fake to .env to use the development stand-in model.

Where each environment gets its values​

EnvironmentWhere
Local (npm run dev)node.ts defaults, overridden by .env at the repository root, overridden by the shell environment.
Tests (Vitest)createHarness() in apps/api/test/harness.ts builds the config directly (ENVIRONMENT=test, dev sign-in, a fixed test KEK).
e2e (Playwright)apps/web/playwright.config.ts starts the Node API with DATA_DIR set to a temporary folder, AI_PROVIDER=fake and an empty GEMINI_API_KEY, on port 8797, and Vite on 5183.
Staging and productionNon-secret vars in apps/api/wrangler.jsonc (per environment) or the Cloudflare dashboard; secrets with wrangler secret put.

wrangler.jsonc sets ENVIRONMENT, GEMINI_MODEL and AI_MONTHLY_BUDGET_USD (50 in production, 10 in staging). ACCESS_TEAM_DOMAIN, ACCESS_AUD, ALLOWED_EMAIL_DOMAINS and BOOTSTRAP_ADMIN_EMAILS are set per environment; DATABASE_URL, IDENTITY_KEK and GEMINI_API_KEY are secrets. See Deployment and operations.

Fail-closed rules​

parseConfig throws, and the Worker or server refuses to start, when:

  • ENVIRONMENT isn't one of the four values;
  • AUTH_MODE=dev outside development or test;
  • neither AUTH_MODE=dev nor both ACCESS_TEAM_DOMAIN and ACCESS_AUD are set;
  • AI_PROVIDER is unknown, is fake outside development or test, or is gemini without a key;
  • IDENTITY_KEK is missing outside development or test, isn't base64, or doesn't decode to exactly 32 bytes.

The Worker caches the parsed config per env object: a new deployment (and so any changed variable or secret) gets a fresh one.

What /api/me exposes​

The app learns what it needs about configuration from GET /api/me:

{
"user": { "id": "…", "email": "dev@davies-group.com", "name": null, "role": "admin" },
"environment": "development",
"aiEnabled": true,
"signIn": { "mode": "dev", "signOutUrl": null }
}

signIn.mode is access (with signOutUrl: "/cdn-cgi/access/logout") or dev. The web client uses it to decide whether to send x-dev-user and whether to reload on an expired session. aiEnabled hides or disables AI controls.