Every Ori service reads its configuration from a .env file. Each repository ships a .env.example you copy to .env and fill in — nothing is hard-coded, so the same build runs locally, on staging, and in production by swapping the file. This page is the complete inventory: every variable, what it means, a sensible default, and whether the service refuses to boot without it. It also calls out the handful of variables that wire the services to each other — get those wrong and the pieces won’t find one another.
Three of these .env files are runtime configuration — the Python services (Backend, Voice fleet, Dialler) read them every time they start, so a change takes effect on the next systemctl restart. The Console is different: its VITE_* variables are build-time and get baked into the static bundle. Changing a Console variable means rebuilding, not restarting. See Console (build-time vars).

Variables that wire the services together

Most variables are local to one service. A small set is shared or cross-referenced — these are the ones to set deliberately and keep consistent across hosts.
VariableSet inPoints at / shared withWhat breaks if wrong
VOXBRIDGE_CONFIG_URLVoice fleetThe Backend’s config endpointThe fleet can’t fetch per-call runtime config; every call fails to start.
FLEET_URLSDiallerThe Voice fleet host(s)The dialler can’t attach answered calls to a worker.
VOXCORE_SECRETDialler and Voice fleetShared secret on both sidesMismatch → the fleet rejects the dialler’s /attach (and config-URL passthrough).
MONGODB_DBBackend and DiallerSame MongoDB database (voxbridge)Different values → dialler paces against an empty/foreign campaign set.
VITE_API_URLConsoleThe Backend API base URLThe dashboard can’t reach the Backend; every API call fails.
MAX_CONCURRENT_CALLSVoice fleetPer-worker call slot countSet >1 in production and a worker takes a second call it can’t serve.
The cleanest way to reason about it: the fleet dials out to the Backend (VOXBRIDGE_CONFIG_URL), the dialler dials out to the fleet (FLEET_URLS), the Console dials out to the Backend (VITE_API_URL), and VOXCORE_SECRET is the shared password on the dialler→fleet hop. Everything else is internal to a single service.
Never commit a real .env. Secrets — JWT_SECRET, every provider API key, VOXCORE_SECRET, LIVEKIT_API_SECRET, MinIO and Mongo credentials — belong in the .env on each host (or your secret store), not in version control. Commit only the .env.example with placeholder values. JWT_SECRET in particular signs every operator session: leaking it lets anyone mint a valid admin token, and rotating it logs every operator out. Generate it with something like openssl rand -hex 32 and keep it per-environment.

Backend (vox-backend)

The Backend API runs on port 8080 and needs MongoDB and Redis reachable. It exposes eight variables.
VariableMeaningExample / defaultRequired
MONGODB_URIMongoDB connection stringmongodb://localhost:27017Yes
MONGODB_DBDatabase name — must match the DiallervoxbridgeYes
REDIS_URLRedis connection string (queues + config cache)redis://localhost:6379Yes
JWT_SECRETSecret that signs operator session tokens — keep secret(generate, no default)Yes
JWT_ALGORITHMJWT signing algorithmHS256No
JWT_EXPIRE_MINUTESSession token lifetime in minutes1440 (24 h)No
VOXCORE_WSS_BASE_URLPublic WSS base the Console hands telephony clientswss://voice.ourplatform.com/wsYes
LOG_LEVELLog verbosityINFONo
# vox-backend/.env
MONGODB_URI=mongodb://localhost:27017
MONGODB_DB=voxbridge
REDIS_URL=redis://localhost:6379
JWT_SECRET=replace-with-openssl-rand-hex-32
JWT_ALGORITHM=HS256
JWT_EXPIRE_MINUTES=1440
VOXCORE_WSS_BASE_URL=wss://voice.ourplatform.com/ws
LOG_LEVEL=INFO
The Backend boots only if the required settings are present, then answers GET /health. If it won’t start, check those four required values first.

Voice fleet (vox-agents)

The Voice fleet runs the call workers. In production each worker is a single-worker uvicorn process on its own Unix socket behind nginx; in dev it’s a single multi-worker process on port 8000. It needs a reachable Backend config URL, object storage for recordings, and LiveKit for SIP/widget calls. Twenty-five variables.
VariableMeaningExample / defaultRequired
VOXBRIDGE_CONFIG_URLBackend endpoint the fleet fetches per-call runtime config fromhttp://localhost:8080/api/v1/configYes

Provider keys

These are the speech-to-text, language-model, and text-to-speech credentials. Set the keys for the providers your bots actually use — a bot configured for a provider whose key is missing will fail when that stage runs.
VariableMeaningExample / defaultRequired
SONIOX_API_KEYSoniox STT key(provider key)If used
DEEPGRAM_API_KEYDeepgram STT key (Nova / Flux)(provider key)If used
OPENAI_API_KEYOpenAI LLM key(provider key)If used
GOOGLE_API_KEYGoogle (Gemini) LLM key(provider key)If used
ELEVENLABS_API_KEYElevenLabs TTS key(provider key)If used
SARVAM_API_KEYSarvam TTS key(provider key)If used

Object storage (recordings)

The fleet uploads each call’s WAV to MinIO or any S3-compatible store. Ori commonly runs local MinIO on each fleet host.
VariableMeaningExample / defaultRequired
MINIO_ENDPOINTObject-storage endpoint (host:port)localhost:9000Yes
MINIO_ACCESS_KEYStorage access key(credential)Yes
MINIO_SECRET_KEYStorage secret key — keep secret(credential)Yes
MINIO_BUCKETBucket recordings are uploaded torecordingsYes
MINIO_SECUREUse TLS to reach the endpointfalseNo

Call slots, audio, logging

VariableMeaningExample / defaultRequired
MAX_CONCURRENT_CALLSCalls a single worker will serve — 1 in production30 (example) / 1 (prod)Yes
AUDIO_SAMPLE_RATEPipeline sample rate in Hz16000No
LOG_LEVELLog verbosityINFONo
MAX_CONCURRENT_CALLS is per worker, and is 1 in production. The fleet’s scaling model is one call per worker process, enforced by nginx max_conns=1. The .env.example ships 30 for convenience when running a single dev process, but on a real fleet host each voxcore@<i> worker must keep it at 1 — otherwise nginx hands a worker a second call it has no slot for. You scale capacity by adding workers, not by raising this number.

Tracing (OpenTelemetry)

Tracing is off by default. When OTEL_ENABLED=true, the remaining OTEL_* variables describe where and how to export spans.
VariableMeaningExample / defaultRequired
OTEL_ENABLEDMaster switch for tracingfalseNo
OTEL_PROTOCOLExport protocolhttp/protobufIf enabled
OTEL_ENDPOINTOTLP collector endpointhttps://otlp.example.comIf enabled
OTEL_SERVICE_NAMEService name attached to spansvoice-fleetIf enabled
OTEL_API_KEYAuth key for the collector — keep secret(credential)If enabled
OTEL_HEADERSExtra OTLP headersx-api-key=...No
# vox-agents/.env  (dev single-process example)
VOXBRIDGE_CONFIG_URL=http://localhost:8080/api/v1/config

DEEPGRAM_API_KEY=
OPENAI_API_KEY=
ELEVENLABS_API_KEY=
SONIOX_API_KEY=
GOOGLE_API_KEY=
SARVAM_API_KEY=

MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=
MINIO_SECRET_KEY=
MINIO_BUCKET=recordings
MINIO_SECURE=false

MAX_CONCURRENT_CALLS=30
AUDIO_SAMPLE_RATE=16000
LOG_LEVEL=INFO

OTEL_ENABLED=false
OTEL_PROTOCOL=http/protobuf
OTEL_ENDPOINT=
OTEL_SERVICE_NAME=voice-fleet
OTEL_API_KEY=
OTEL_HEADERS=
VOXCORE_SOCKET (/tmp/voxcore_<i>.sock) is not an .env value — it’s supplied per worker by the systemd template unit voxcore@.service, so worker N listens on /tmp/voxcore_N.sock. See the production deployment docs for the socket model.

Dialler (vox-dialler)

The Dialler is a background asyncio worker (with a small health server), not a web app. It shares MongoDB with the Backend, attaches answered calls to the fleet, and dials out through LiveKit SIP. Eighteen variables.
Run exactly one Dialler per database. Two diallers against the same Mongo each pace as if they’re alone and over-dial. Place the single instance on the SIP / LiveKit host. This is a placement rule, not a config flag — there’s no setting that makes two instances safe.

Data + fleet + secret

VariableMeaningExample / defaultRequired
MONGODB_URIMongoDB connection stringmongodb://localhost:27017Yes
MONGODB_DBDatabase name — must match the BackendvoxbridgeYes
FLEET_URLSJSON array of Voice fleet base URLs to attach calls to["https://fleet.example.com"]Yes
VOXCORE_SECRETShared secret for /attachmust match the fleet(secret)Yes

LiveKit SIP

VariableMeaningExample / defaultRequired
LIVEKIT_URLLiveKit server URLwss://livekit.example.comYes
LIVEKIT_API_KEYLiveKit API key(credential)Yes
LIVEKIT_API_SECRETLiveKit API secret — keep secret(credential)Yes
LIVEKIT_TRUNK_IDOutbound SIP trunk ID to place calls onST_xxxxxxxxYes

Loop, health, staleness, metrics

VariableMeaningExample / defaultRequired
LOOP_INTERVAL_SECONDSSeconds between pacing ticks2.0No
LOG_LEVELLog verbosityINFONo
HEALTH_HOSTBind address for the health server127.0.0.1No
HEALTH_PORTPort for /health and /metrics8090No
HEALTH_STALE_SECONDSTick age after which the worker is reported unhealthy30.0No
STALE_IN_PROGRESS_TIMEOUT_MINUTESReap calls stuck in-progress past this10No
METRICS_PERSIST_INTERVAL_SECONDSHow often rolling metrics are persisted15.0No

Circuit breaker

The dialler trips a breaker when SIP dialing keeps failing, then cools down before retrying.
VariableMeaningExample / defaultRequired
CIRCUIT_FAILURE_THRESHOLDConsecutive failures before the breaker opens5No
CIRCUIT_COOLDOWN_SECONDSTime the breaker stays open before a trial30.0No
CIRCUIT_SUCCESS_THRESHOLDSuccesses needed to close the breaker again2No
# vox-dialler/.env
MONGODB_URI=mongodb://localhost:27017
MONGODB_DB=voxbridge
FLEET_URLS=["https://fleet.example.com"]
VOXCORE_SECRET=shared-secret-must-match-the-fleet

LIVEKIT_URL=wss://livekit.example.com
LIVEKIT_API_KEY=
LIVEKIT_API_SECRET=
LIVEKIT_TRUNK_ID=ST_xxxxxxxx

LOOP_INTERVAL_SECONDS=2.0
LOG_LEVEL=INFO

HEALTH_HOST=127.0.0.1
HEALTH_PORT=8090
HEALTH_STALE_SECONDS=30.0
STALE_IN_PROGRESS_TIMEOUT_MINUTES=10
METRICS_PERSIST_INTERVAL_SECONDS=15.0

CIRCUIT_FAILURE_THRESHOLD=5
CIRCUIT_COOLDOWN_SECONDS=30.0
CIRCUIT_SUCCESS_THRESHOLD=2
FLEET_URLS is a JSON array string, not a comma-separated list. A single fleet is ["https://fleet.example.com"]; multiple fleets are ["https://fleet-1.example.com","https://fleet-2.example.com"]. The dialler picks a free worker across all of them when attaching an answered call.

Console (build-time vars)

The Console is a React + Vite single-page app. Its variables are build-time: Vite reads the VITE_* values during npm run build and bakes them into the static dist/ bundle that nginx serves. There is no runtime .env on the web host — to change any of these you rebuild and redeploy the bundle, you don’t restart a service. For Ori’s single-brand repo the brand .env is committed at the brand path and the build copies it to the root .env, so the build is just npm install && npm run build.
VariableMeaningExample / defaultRequired
VITE_API_URLBackend API base URL the dashboard callshttps://api.example.comYes
VITE_TOKEN_KEYLocal-storage key the session token is stored underori_tokenYes

Branding

VariableMeaningExample / defaultRequired
VITE_BRAND_NAMEProduct name shown in the UIOriYes
VITE_BRAND_POWERED_BY”Powered by” attribution lineOriNo
VITE_BRAND_LOGOLogo asset path/logo.pngYes

Theme colours

VariableMeaningExample / defaultRequired
VITE_PRIMARY_COLORPrimary brand colour#0F9B8EYes
VITE_PRIMARY_SOFTSoft/secondary primary tint#78DFC5No
VITE_BG_COLORApp background colour#0A2540No
VITE_AURORA_COLOR1First aurora gradient stop#0F9B8ENo
VITE_AURORA_COLOR2Second aurora gradient stop#78DFC5No

Optional flags

VariableMeaningExample / defaultRequired
VITE_WIDGET_GLOBAL_NAMEGlobal JS name for the embeddable widgetOriWidgetNo
VITE_CALLS_ONLY_DOMAINSDomains restricted to the calls-only viewcalls.example.comNo
VITE_BRAND_THEMENamed theme presetoriNo
# vox-frontend brand .env  (committed at the brand path, copied to root .env at build)
VITE_API_URL=https://api.example.com
VITE_TOKEN_KEY=ori_token

VITE_BRAND_NAME=Ori
VITE_BRAND_POWERED_BY=Ori
VITE_BRAND_LOGO=/logo.png

VITE_PRIMARY_COLOR=#0F9B8E
VITE_PRIMARY_SOFT=#78DFC5
VITE_BG_COLOR=#0A2540
VITE_AURORA_COLOR1=#0F9B8E
VITE_AURORA_COLOR2=#78DFC5

VITE_WIDGET_GLOBAL_NAME=OriWidget
VITE_CALLS_ONLY_DOMAINS=
VITE_BRAND_THEME=ori
Because VITE_* values are compiled into the shipped bundle, treat them as public. Never put a secret in a VITE_* variable — anything prefixed VITE_ is visible in the browser. The Backend’s VITE_API_URL is fine to expose; API keys and JWT_SECRET are not, and they live on the Backend, never here.

Where to go next

Run it locally

Clone the four repos, fill in these .env files, and bring the stack up on your machine.

Deploy it

How these .env files land on each host through the Bitbucket → Jenkins pipeline.

Repository map

The four repositories and the contracts between them.

System architecture

How the services fit together and why the configuration is split this way.