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 undersettings.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 inProviderAuth, 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 fromProviderAuth 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.py → chunk_text.py → embed_chunks.py → chroma_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.
connect_to_mongo()creates theMongoClientand runspingagainstadmin. AConnectionFailureorServerSelectionTimeoutErroris logged and re-raised, so the process exits rather than serving a broken app.initialize_database()(app/database_init.py) creates collections and indexes. It is idempotent:_ensure_indexswallows “already exists” and “duplicate” errors, so restarts are safe.- On shutdown
close_mongo_connection()closes the client.
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:
/docs (Swagger UI) and /redoc (ReDoc). Both are enabled unconditionally.
Running it standalone
Start only the database layer, then run uvicorn on the host:.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.
Related
- Workers and orchestrator — the other two containers from this package
- Runtime (apps/runtime) — the only other service that calls this API
- Multi-tenancy and roles
- REST API reference · Endpoints cheatsheet