Skip to main content
apps/api is the FastAPI application that owns every document in VoicEra: users, organisations, agents, phone numbers, call logs, campaigns, and knowledge documents. It listens on port 8000 and is the only service that talks to FerretDB.
The same package also runs as the ARQ worker and the campaign orchestrator, with a different command. This page covers the HTTP process only — see Workers and orchestrator for the other two.

Responsibilities

Router map

Every router is mounted under settings.API_V1_PREFIX, which defaults to /api/v1. app/main.py includes them in this order: users · members · organisations · languages · configuration · auth · agents · phone-numbers · calls · campaign · knowledge · rag The tables below are reproduced from apps/api/README.md. /docs on a running instance is always the authoritative list.

Auth and users

Service-to-service callers use POST /users/bot/token with the internal key and an org_id, then send the returned access_token as a Bearer token. An unknown org_id returns 404.

Configuration catalogs

These are generated from apps/providers and apps/telephony at request time. There is no hand-maintained list in the router.

Provider credentials

Credentials are provider-level — one key set is shared across the STT, TTS, and LLM slots of that provider. Only secret fields are stored in ProviderAuth, and the whole auth object is Fernet-encrypted at rest with PROVIDER_AUTH_ENCRYPTION_KEY. See Provider credentials (ProviderAuth).

Agents

Agents store typed behaviour plus AI model configs, secret-free. For telephony agents the API automatically creates a provider application using org credentials from ProviderAuth and stores the attachment on the agent document. WebSocket agents skip telephony provisioning. Set VOICE_SERVER_BASE_URL before creating telephony agents. Answer and hangup use the same URL:
config.models must include stt_config, tts_config, and llm_config, each with a registered provider and non-secret settings only. Full field reference in Agent configuration.

Phone numbers

Omit agent_id on attach to import a number into inventory only, with no provider link.

Calls, campaigns, and knowledge

The recording and transcript routes are authenticated proxies over MinIO — the runtime stores a minio:// URI and clients never talk to MinIO directly. See Calls and call artifacts and the full REST API reference.

Service layer

Routers stay thin. app/services/ owns the rules: app/rag/ holds the ingest pipeline: pdf_to_text.pychunk_text.pyembed_chunks.pychroma_store.py. app/storage/minio_client.py wraps the MinIO SDK. app/models/schemas.py holds the Pydantic request and response models.

Persistence

Three stores, each with a different job. app/database.py builds the connection URI from MONGODB_HOST, MONGODB_PORT, MONGODB_USER, MONGODB_PASSWORD, and MONGODB_DATABASE. MONGODB_AUTH_SOURCE and MONGODB_AUTH_MECHANISM default to empty strings because FerretDB authenticates with PostgreSQL users over SCRAM-SHA-256. Connections use serverSelectionTimeoutMS=5000. See Data store (FerretDB) and the Data model.

Startup lifecycle

app/main.py uses a FastAPI lifespan context manager rather than event handlers.
  1. connect_to_mongo() creates the MongoClient and runs ping against admin. A ConnectionFailure or ServerSelectionTimeoutError is logged and re-raised, so the process exits rather than serving a broken app.
  2. initialize_database() (app/database_init.py) creates collections and indexes. It is idempotent: _ensure_index swallows “already exists” and “duplicate” errors, so restarts are safe.
  3. On shutdown close_mongo_connection() closes the client.
Log level is DEBUG when settings.DEBUG is true, INFO otherwise. DEBUG is coerced by a validator that accepts 1, true, yes, on and treats anything else as false — a host shell exporting DEBUG=release will not crash settings parsing.

CORS and middleware

CORSMiddleware is the only middleware, configured as:
allow_origins=["*"] together with allow_credentials=True is permissive and suitable for local development only. Restrict origins before exposing the API to the internet — see Security hardening.
Interactive docs are served at /docs (Swagger UI) and /redoc (ReDoc). Both are enabled unconditionally.

Running it standalone

Start only the database layer, then run uvicorn on the host:
Configuration comes from the repository root .env, resolved in app/config.py relative to the file. There is no per-app .env. When running on the host, set MONGODB_HOST=localhost and MONGODB_PORT=27018 to reach the published FerretDB port. The image is python:3.11-slim. It installs gcc, g++, and libgomp1, then apps/api/requirements.txt, and copies apps/providers and apps/telephony alongside the app. Its CMD runs uvicorn without --reload; Compose overrides that with a --reload command and a bind mount for development.

Health

GET /health calls ping_database(), which issues a ping against admin on the existing client. If the client is unset or the ping raises, the response is {"status": "degraded", "database": "down"} — with HTTP 200 either way, so a probe must inspect the body, not the status code. GET / returns the project name, version, and a pointer to /docs.