Everything a private video visit needs, engineered deliberately.
Every capability below exists because a real consultation needs it — and nothing exists just because a platform usually has it. Grouped by where it sits in the lifecycle of a call.
Rooms & access
The core contractPrivate rooms, no auto-create
LiveKit runs with auto_create: false. A room must be created through POST /rooms before anyone can join — a forged, LiveKit-valid token for a never-created room is refused, verified against a live server, not just mocks.
Role-based join tokens
doctor, patient, nurse, staff, and interpreter get full publish/subscribe/data grants. observer gets subscribe only. Grants are derived server-side from the role — a caller can never ask for more than it is entitled to.
Real room lifecycle
CREATED → ACTIVE → EXPIRED → ENDED, derived from provider truth on every read — never a stale stored copy. EXPIRED outranks ACTIVE: a room past its deadline is expired even with people still connected.
Automatic cleanup sweep
An in-process interval sweep evicts expired rooms with no queue and no Redis dependency. Every instance sweeps; racing is expected and harmless — the room is gone either way.
Live participant snapshots
GET /rooms/{roomName}/participants returns who is connected right now, with join state (joining / joined / active / disconnected) read straight from the provider.
Recording & compliance
Cloud recording without touching bytesCloud recording via LiveKit Egress
One call starts a room-composite recording, uploaded straight to S3-compatible storage. VonLinkage never proxies or stores the bytes — same invariant that keeps media out of this process entirely.
Presigned downloads
A short-lived, presigned URL lets the caller fetch the file directly from storage once a recording is COMPLETED. No proxying, no bandwidth cost on this service.
Integrity verification
A background sweep confirms every finished recording actually exists in storage at the size Egress reported. Mismatches retry up to a cap, then surface as a terminal, human-reviewable failure.
Durable recording history
Egress ages finished jobs out of its own memory; a Postgres archive is the only way recording history survives that. GET keeps answering correctly whether or not Egress still remembers the job.
Opt-in retention purge
Off by default. When RECORDING_RETENTION_DAYS is set, files past that window are permanently deleted and the deletion is recorded — erasing clinical audio is a policy decision, never a silent default.
Transcription & integrations
What happens after the callBatch medical transcription
Finished recordings are transcribed with AWS Transcribe Medical — full text, per-segment timing, and confidence scores, available from one authenticated endpoint.
Speaker diarization
Segments carry vendor-assigned speaker labels (spk_0, spk_1). A caller that needs "who said this" correlates labels with the room’s participant list — no per-participant audio is fabricated to fake that mapping.
Signed outbound webhooks
recording.completed, recording.failed, transcript.completed, and transcript.failed are pushed to a single configured receiver, HMAC-signed over the exact request body.
Delivery retries & idempotency
A stable delivery id lets receivers dedupe retries. Failed deliveries retry on a fixed interval up to a configured attempt cap, then stop — no silent infinite retry storms.
No PHI over webhooks
Payloads carry identifiers and status only — never transcript text, never clinical content. Receivers call back into the authenticated API for anything more.
Architecture & operations
Why it stays easy to runProvider-abstracted by design
Controllers and services depend only on ports (RoomProvider, TokenProvider, StorageProvider…). Only the adapters package knows LiveKit exists — an architecture rule enforced by an executable spec, not just a convention.
Health & readiness probes
Liveness never touches a dependency; readiness round-trips LiveKit and returns 503 when degraded — so orchestrators can tell "up" from "actually working" apart.
Correlation ids everywhere
x-correlation-id is generated when absent, echoed on every response, and appears in every log line for that request — quote it in a bug report and the trace is right there.
One docker compose up
The whole stack — API, LiveKit, Coturn — comes up with zero manual configuration. Committed defaults are wired together end to end.
Verified against a live server
Beyond unit and e2e suites, an integration suite boots real LiveKit, Postgres, and S3 adapters — nothing mocked — and has already caught real bugs a mocked suite couldn’t.