Configuration reference
Every environment variable for the four Ori services — Backend, Voice fleet, Dialler, and Console — grouped by service, with meaning, default, and which ones are required to boot.
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.
| Variable | Set in | Points at / shared with | What breaks if wrong |
|---|---|---|---|
VOXBRIDGE_CONFIG_URL | Voice fleet | The Backend's config endpoint | The fleet can't fetch per-call runtime config; every call fails to start. |
FLEET_URLS | Dialler | The Voice fleet host(s) | The dialler can't attach answered calls to a worker. |
VOXCORE_SECRET | Dialler and Voice fleet | Shared secret on both sides | Mismatch → the fleet rejects the dialler's /attach (and config-URL passthrough). |
MONGODB_DB | Backend and Dialler | Same MongoDB database (voxbridge) | Different values → dialler paces against an empty/foreign campaign set. |
VITE_API_URL | Console | The Backend API base URL | The dashboard can't reach the Backend; every API call fails. |
MAX_CONCURRENT_CALLS | Voice fleet | Per-worker call slot count | Set >1 in production and a worker takes a second call it can't serve. |
Tip
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.
| Variable | Meaning | Example / default | Required |
|---|---|---|---|
MONGODB_URI | MongoDB connection string | mongodb://localhost:27017 | Yes |
MONGODB_DB | Database name — must match the Dialler | voxbridge | Yes |
REDIS_URL | Redis connection string (queues + config cache) | redis://localhost:6379 | Yes |
JWT_SECRET | Secret that signs operator session tokens — keep secret | (generate, no default) | Yes |
JWT_ALGORITHM | JWT signing algorithm | HS256 | No |
JWT_EXPIRE_MINUTES | Session token lifetime in minutes | 1440 (24 h) | No |
VOXCORE_WSS_BASE_URL | Public WSS base the Console hands telephony clients | wss://voice.ourplatform.com/ws | Yes |
LOG_LEVEL | Log verbosity | INFO | No |
# 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=INFOThe 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.
Backend link
| Variable | Meaning | Example / default | Required |
|---|---|---|---|
VOXBRIDGE_CONFIG_URL | Backend endpoint the fleet fetches per-call runtime config from | http://localhost:8080/api/v1/config | Yes |
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.
| Variable | Meaning | Example / default | Required |
|---|---|---|---|
SONIOX_API_KEY | Soniox STT key | (provider key) | If used |
DEEPGRAM_API_KEY | Deepgram STT key (Nova / Flux) | (provider key) | If used |
OPENAI_API_KEY | OpenAI LLM key | (provider key) | If used |
GOOGLE_API_KEY | Google (Gemini) LLM key | (provider key) | If used |
ELEVENLABS_API_KEY | ElevenLabs TTS key | (provider key) | If used |
SARVAM_API_KEY | Sarvam 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.
| Variable | Meaning | Example / default | Required |
|---|---|---|---|
MINIO_ENDPOINT | Object-storage endpoint (host:port) | localhost:9000 | Yes |
MINIO_ACCESS_KEY | Storage access key | (credential) | Yes |
MINIO_SECRET_KEY | Storage secret key — keep secret | (credential) | Yes |
MINIO_BUCKET | Bucket recordings are uploaded to | recordings | Yes |
MINIO_SECURE | Use TLS to reach the endpoint | false | No |
Call slots, audio, logging
| Variable | Meaning | Example / default | Required |
|---|---|---|---|
MAX_CONCURRENT_CALLS | Calls a single worker will serve — 1 in production | 30 (example) / 1 (prod) | Yes |
AUDIO_SAMPLE_RATE | Pipeline sample rate in Hz | 16000 | No |
LOG_LEVEL | Log verbosity | INFO | No |
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.
| Variable | Meaning | Example / default | Required |
|---|---|---|---|
OTEL_ENABLED | Master switch for tracing | false | No |
OTEL_PROTOCOL | Export protocol | http/protobuf | If enabled |
OTEL_ENDPOINT | OTLP collector endpoint | https://otlp.example.com | If enabled |
OTEL_SERVICE_NAME | Service name attached to spans | voice-fleet | If enabled |
OTEL_API_KEY | Auth key for the collector — keep secret | (credential) | If enabled |
OTEL_HEADERS | Extra OTLP headers | x-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
| Variable | Meaning | Example / default | Required |
|---|---|---|---|
MONGODB_URI | MongoDB connection string | mongodb://localhost:27017 | Yes |
MONGODB_DB | Database name — must match the Backend | voxbridge | Yes |
FLEET_URLS | JSON array of Voice fleet base URLs to attach calls to | ["https://fleet.example.com"] | Yes |
VOXCORE_SECRET | Shared secret for /attach — must match the fleet | (secret) | Yes |
LiveKit SIP
| Variable | Meaning | Example / default | Required |
|---|---|---|---|
LIVEKIT_URL | LiveKit server URL | wss://livekit.example.com | Yes |
LIVEKIT_API_KEY | LiveKit API key | (credential) | Yes |
LIVEKIT_API_SECRET | LiveKit API secret — keep secret | (credential) | Yes |
LIVEKIT_TRUNK_ID | Outbound SIP trunk ID to place calls on | ST_xxxxxxxx | Yes |
Loop, health, staleness, metrics
| Variable | Meaning | Example / default | Required |
|---|---|---|---|
LOOP_INTERVAL_SECONDS | Seconds between pacing ticks | 2.0 | No |
LOG_LEVEL | Log verbosity | INFO | No |
HEALTH_HOST | Bind address for the health server | 127.0.0.1 | No |
HEALTH_PORT | Port for /health and /metrics | 8090 | No |
HEALTH_STALE_SECONDS | Tick age after which the worker is reported unhealthy | 30.0 | No |
STALE_IN_PROGRESS_TIMEOUT_MINUTES | Reap calls stuck in-progress past this | 10 | No |
METRICS_PERSIST_INTERVAL_SECONDS | How often rolling metrics are persisted | 15.0 | No |
Circuit breaker
The dialler trips a breaker when SIP dialing keeps failing, then cools down before retrying.
| Variable | Meaning | Example / default | Required |
|---|---|---|---|
CIRCUIT_FAILURE_THRESHOLD | Consecutive failures before the breaker opens | 5 | No |
CIRCUIT_COOLDOWN_SECONDS | Time the breaker stays open before a trial | 30.0 | No |
CIRCUIT_SUCCESS_THRESHOLD | Successes needed to close the breaker again | 2 | No |
# 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=2FLEET_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.
Backend link
| Variable | Meaning | Example / default | Required |
|---|---|---|---|
VITE_API_URL | Backend API base URL the dashboard calls | https://api.example.com | Yes |
VITE_TOKEN_KEY | Local-storage key the session token is stored under | ori_token | Yes |
Branding
| Variable | Meaning | Example / default | Required |
|---|---|---|---|
VITE_BRAND_NAME | Product name shown in the UI | Ori | Yes |
VITE_BRAND_POWERED_BY | "Powered by" attribution line | Ori | No |
VITE_BRAND_LOGO | Logo asset path | /logo.png | Yes |
Theme colours
| Variable | Meaning | Example / default | Required |
|---|---|---|---|
VITE_PRIMARY_COLOR | Primary brand colour | #0F9B8E | Yes |
VITE_PRIMARY_SOFT | Soft/secondary primary tint | #78DFC5 | No |
VITE_BG_COLOR | App background colour | #0A2540 | No |
VITE_AURORA_COLOR1 | First aurora gradient stop | #0F9B8E | No |
VITE_AURORA_COLOR2 | Second aurora gradient stop | #78DFC5 | No |
Optional flags
| Variable | Meaning | Example / default | Required |
|---|---|---|---|
VITE_WIDGET_GLOBAL_NAME | Global JS name for the embeddable widget | OriWidget | No |
VITE_CALLS_ONLY_DOMAINS | Domains restricted to the calls-only view | calls.example.com | No |
VITE_BRAND_THEME | Named theme preset | ori | No |
# 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=oriBecause 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.
Running the stack
The core local dev loop: bring up MongoDB and Redis, run the Backend, run the Console, and run a single voice-fleet worker — then watch a real call land on your machine.
CI/CD pipeline
How a change travels from a developer's git push to running on the servers — the Bitbucket → Jenkins pipeline, the branch strategy, per-service lint/test/build stages, and the Jenkins-to-host deploy model.