System Architecture
Components, Integrations, and Data Flow
High-Level Architecture
┌─────────────────────────────────────────────────────────────────────────────┐
│ VITARAVOX ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ PATIENT LAYER │ │
│ │ │ │
│ │ 📞 Patient calls clinic phone number │ │
│ │ (+1-604-XXX-XXXX via Telnyx/VoIP.ms) │ │
│ │ │ │ │
│ └──────────────────────────────┼───────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ VOICE AI LAYER │ │
│ │ (Vapi.ai) │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Deepgram │ │ GPT-4o │ │ Azure │ │ │
│ │ │ Nova-2 │───▶│ LLM │───▶│ TTS │ │ │
│ │ │ (STT) │ │ │ │ (Clara) │ │ │
│ │ └────────────┘ └─────┬──────┘ └────────────┘ │ │
│ │ │ │ │
│ │ Tool Calls (Webhooks) │ │
│ │ │ │ │
│ └────────────────────────────┼─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ MIDDLEWARE LAYER │ │
│ │ (Vitara Platform) │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────────────────┐ │ │
│ │ │ NGINX (9443) │ │ │
│ │ │ SSL + Rate Limiting │ │ │
│ │ └─────────────────────────┬──────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌─────────────────────────▼──────────────────────────────────┐ │ │
│ │ │ Voice Agent (Node.js 3001) │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │
│ │ │ │ 14 API │ │ Clinic │ │ OSCAR │ │ │ │
│ │ │ │ Endpoints │ │ Router │ │ Adapter │ │ │ │
│ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │
│ │ │ │ │ │
│ │ └────────────────────────┬───────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────────┴──────────────┐ │ │
│ │ ▼ ▼ │ │
│ │ ┌─────────────────┐ ┌─────────────────────┐ │ │
│ │ │ Vitara DB │ │ OSCAR EMR │ │ │
│ │ │ (PostgreSQL) │ │ (Per-Clinic) │ │ │
│ │ │ │ │ │ │ │
│ │ │ • Clinic cfg │ │ • Demographics │ │ │
│ │ │ • Hours │ │ • Appointments │ │ │
│ │ │ • Waitlist │ │ • Providers │ │ │
│ │ │ • Call logs │ │ • Schedules │ │ │
│ │ └─────────────────┘ └─────────────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Mermaid: High-Level Architecture
flowchart TB
subgraph PatientLayer["📞 Patient Layer"]
Patient["Patient calls clinic<br/>+1-604-XXX-XXXX"]
end
subgraph VoiceAI["🎙️ Voice AI Layer (Vapi.ai)"]
STT["Deepgram Nova-2<br/>(Speech-to-Text)"]
LLM["GPT-4o<br/>(Understanding)"]
TTS["Azure Clara<br/>(Text-to-Speech)"]
STT --> LLM --> TTS
end
subgraph Middleware["⚙️ Middleware Layer (Vitara Platform)"]
NGINX["NGINX :9443<br/>SSL + Rate Limiting"]
VoiceAgent["Voice Agent<br/>Node.js :3001"]
Endpoints["14 API Endpoints"]
Router["Clinic Router"]
Adapter["OSCAR Adapter"]
NGINX --> VoiceAgent
VoiceAgent --> Endpoints
VoiceAgent --> Router
VoiceAgent --> Adapter
end
subgraph DataLayer["💾 Data Layer"]
VitaraDB[("Vitara DB<br/>PostgreSQL")]
OSCAR[("OSCAR EMR<br/>Per-Clinic")]
end
Patient --> STT
TTS --> Patient
LLM -->|"Tool Calls<br/>(Webhooks)"| NGINX
VoiceAgent --> VitaraDB
Adapter --> OSCAR
style PatientLayer fill:#e0f2fe,stroke:#0284c7
style VoiceAI fill:#fef3c7,stroke:#d97706
style Middleware fill:#f0fdf4,stroke:#16a34a
style DataLayer fill:#fce7f3,stroke:#db2777
Architecture Decisions Review
| Decision |
Choice |
Rationale |
Alternatives Considered |
| Voice AI Platform |
Vapi.ai |
Turn-key voice orchestration; handles STT/LLM/TTS pipeline; 50ms latency target |
Build custom (too complex), Twilio Voice (no native LLM), Amazon Connect (AWS lock-in) |
| LLM |
GPT-4o |
Best-in-class intent understanding; function calling support; multilingual |
Claude (no voice integration), Llama (self-host burden), GPT-3.5 (accuracy concerns) |
| STT |
Deepgram Nova-2 |
Medical vocabulary support; low latency; streaming |
Whisper (higher latency), Google STT (cost), Azure STT (less accurate) |
| TTS |
Azure Clara |
Natural voice; Canadian English; SSML support |
ElevenLabs (cost), Amazon Polly (robotic), Google TTS (less natural) |
| Middleware |
Node.js Express |
Event-driven; large ecosystem; team expertise |
Python FastAPI (fewer libs), Go (learning curve), Java Spring (heavy) |
| Reverse Proxy |
NGINX |
Proven; rate limiting; SSL termination |
Traefik (Kubernetes focus), HAProxy (less features), Caddy (less mature) |
| Database |
PostgreSQL 16 |
ACID compliance; JSONB support; proven at scale |
MySQL (less features), MongoDB (consistency concerns), SQLite (not production) |
Component Details
Telephony Layer
| Component |
Provider |
Purpose |
| Phone Number |
Telnyx or VoIP.ms |
Inbound DID |
| SIP Trunk |
Via Vapi |
Call routing |
Voice AI Layer (Vapi.ai)
| Component |
Technology |
Purpose |
| Speech-to-Text |
Deepgram Nova-2 |
Transcription |
| LLM |
OpenAI GPT-4o |
Understanding + Response |
| Text-to-Speech |
Azure Clara |
Voice synthesis |
| Component |
Technology |
Purpose |
| Reverse Proxy |
NGINX |
SSL, rate limiting |
| API Server |
Node.js + Express |
Webhook handling |
| Database |
PostgreSQL 16 |
Configuration, logs |
EMR Abstraction Layer (v1.4.0)
| Component |
Technology |
Purpose |
| EmrService |
services/EmrService.js |
Integration layer, response translation |
| Feature Flags |
config/featureFlags.js |
Controlled rollout (USE_EMR_ADAPTER) |
| Adapter Interface |
adapters/IEmrAdapter.js |
Unified EMR contract |
| Adapter Factory |
adapters/EmrAdapterFactory.js |
Per-clinic adapter creation |
| OscarUniversalAdapter |
adapters/OscarUniversalAdapter.js |
OSCAR REST Bridge client |
| Transformers |
transformers/*.js |
Data format normalization |
Default: USE_EMR_ADAPTER=false → Legacy oscarRestService (no adapter)
EMR Layer
| Component |
Technology |
Purpose |
| OSCAR EMR |
Per-clinic instance |
Patient data, appointments |
| Integration |
REST Bridge or OAuth 1.0a |
Secure API access |
Network Topology
Internet
│
▼
┌─────────────────┐
│ Cloudflare │ DNS + DDoS protection
│ (DNS) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ OCI Toronto │ Production server
│ (Vitara) │
│ │
│ └─ NGINX:9443 │
│ └─ Node:3001 │
│ └─ Postgres │
└────────┬────────┘
│
│ OAuth 1.0a (HTTPS)
▼
┌─────────────────┐
│ Clinic OSCAR │ Customer EMR
│ (External) │
└─────────────────┘
Mermaid: Network Topology
flowchart TB
Internet["🌐 Internet"]
subgraph CloudFlare["Cloudflare"]
DNS["DNS + DDoS Protection"]
end
subgraph OCI["OCI Toronto (Vitara)"]
NGINX2["NGINX :9443"]
Node["Node.js :3001"]
PG["PostgreSQL"]
end
subgraph ClinicNetwork["Clinic Network"]
OSCAR2[("OSCAR EMR<br/>(External)")]
end
Internet --> DNS
DNS --> NGINX2
NGINX2 --> Node
Node --> PG
Node -->|"OAuth 1.0a<br/>(HTTPS)"| OSCAR2
style CloudFlare fill:#fff7ed,stroke:#ea580c
style OCI fill:#f0fdf4,stroke:#16a34a
style ClinicNetwork fill:#fef2f2,stroke:#dc2626
Network Decisions Review
| Decision |
Choice |
Rationale |
Alternatives Considered |
| DNS Provider |
Cloudflare |
Free tier; DDoS protection; fast propagation; analytics |
Route53 (AWS lock-in), Google DNS (less features), self-hosted (no DDoS) |
| Hosting |
OCI Toronto |
Canadian data residency (PIPEDA); free tier; low latency to BC |
AWS ca-central-1 (cost), Azure Canada (cost), GCP (no Canadian region at time) |
| SSL/TLS |
Let's Encrypt |
Free; auto-renewal; widely trusted |
Paid certs (cost), self-signed (trust issues), Cloudflare origin (complexity) |
| OSCAR Connection |
OAuth 1.0a over HTTPS |
OSCAR's native auth; proven security; per-clinic isolation |
Direct DB (security risk), API key (less secure), SAML (OSCAR doesn't support) |
Multi-Tenancy Model
┌─────────────────────────────────────────────────────────────────────────────┐
│ MULTI-CLINIC ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Vapi Phone Numbers │
│ │
│ +1-604-555-0001 +1-604-555-0002 +1-604-555-0003 │
│ │ │ │ │
│ │ │ │ │
│ └──────────────────────┼──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Vitara │ │
│ │ Middleware │ │
│ │ │ │
│ │ Phone → Clinic │ │
│ │ Router │ │
│ └────────┬────────┘ │
│ │ │
│ ┌─────────────────────┼─────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ Clinic A │ │ Clinic B │ │ Clinic C │ │
│ │ Config │ │ Config │ │ Config │ │
│ │ (Vitara DB) │ │ (Vitara DB) │ │ (Vitara DB) │ │
│ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ OSCAR A │ │ OSCAR B │ │ OSCAR C │ │
│ │ (Clinic EMR) │ │ (Clinic EMR) │ │ (Clinic EMR) │ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Mermaid: Multi-Tenancy Model
flowchart TB
subgraph PhoneNumbers["📱 Vapi Phone Numbers"]
Phone1["+1-604-555-0001"]
Phone2["+1-604-555-0002"]
Phone3["+1-604-555-0003"]
end
subgraph VitaraMiddleware["⚙️ Vitara Middleware"]
Router2["Phone → Clinic<br/>Router"]
end
subgraph ClinicConfigs["📋 Clinic Configurations (Vitara DB)"]
ConfigA["Clinic A Config<br/>emr_live: true"]
ConfigB["Clinic B Config<br/>emr_live: false"]
ConfigC["Clinic C Config<br/>emr_live: true"]
end
subgraph EMRSystems["🏥 EMR Systems"]
OSCARA[("OSCAR A")]
OSCARB[("OSCAR B<br/>(Demo Mode)")]
OSCARC[("OSCAR C")]
end
Phone1 --> Router2
Phone2 --> Router2
Phone3 --> Router2
Router2 --> ConfigA
Router2 --> ConfigB
Router2 --> ConfigC
ConfigA --> OSCARA
ConfigB -.->|"Demo Data"| OSCARB
ConfigC --> OSCARC
style PhoneNumbers fill:#dbeafe,stroke:#2563eb
style VitaraMiddleware fill:#f0fdf4,stroke:#16a34a
style ClinicConfigs fill:#fef3c7,stroke:#d97706
style EMRSystems fill:#fce7f3,stroke:#db2777
Multi-Tenancy Decisions Review
| Decision |
Choice |
Rationale |
Alternatives Considered |
| Tenant Isolation |
Phone number routing |
Simple; Vapi provides caller context; no auth complexity |
Subdomain routing (DNS complexity), API key per clinic (more complex), session-based (stateful) |
| Config Storage |
Centralized Vitara DB |
Single source of truth; easy backup; ACID transactions |
Per-clinic DB (operational burden), Redis (durability concerns), Files (no ACID) |
| EMR Connection |
Per-clinic credentials |
Security isolation; independent scaling; clinic ownership |
Shared service account (security risk), VPN per clinic (network complexity) |
| Demo Mode |
emr_live flag in DB |
Per-clinic control; instant toggle; no code changes |
Env var (all-or-nothing), Feature flag service (overkill), Code branch (risky) |
| Adapter Pattern |
Factory with caching |
5-min TTL balances performance vs config freshness |
New adapter per request (slow), Global singleton (no isolation), IoC container (heavy) |
Request Flow
Inbound Call
1. Patient dials +1-604-555-0001
2. Telnyx routes to Vapi
3. Vapi answers, starts assistant
4. Vapi calls search_patient_by_phone webhook
5. Vitara looks up clinic by phone number
6. Vitara queries clinic's OSCAR for patient
7. Response returned to Vapi
8. Vapi speaks response to patient
Booking Flow
1. Patient: "I'd like to book an appointment"
2. Vapi: Understands intent, calls find_earliest_appointment
3. Vitara: Queries OSCAR for available slots
4. Vitara: Returns earliest slot
5. Vapi: "The earliest is Tuesday at 9:30 AM"
6. Patient: "Yes, book that"
7. Vapi: Calls create_appointment
8. Vitara: Creates appointment in OSCAR
9. Vitara: Logs call in Vitara DB
10. Vapi: Confirms to patient
Security Architecture
┌─────────────────────────────────────────────────────────────────────────────┐
│ SECURITY BOUNDARIES │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ BOUNDARY 1: Internet → NGINX │
│ ───────────────────────────── │
│ • TLS 1.2/1.3 (Let's Encrypt) │
│ • Rate limiting (100 req/min) │
│ • HSTS, security headers │
│ │
│ BOUNDARY 2: Vapi → Vitara │
│ ──────────────────────────── │
│ • Bearer Token (Authorization header) │
│ • Timing-safe token comparison │
│ • Vapi credential system │
│ │
│ BOUNDARY 3: Vitara → OSCAR │
│ ───────────────────────────── │
│ • OAuth 1.0a (HMAC-SHA1) │
│ • Per-clinic credentials │
│ • Encrypted at rest (AES-256) │
│ │
│ BOUNDARY 4: Admin UI → Vitara │
│ ───────────────────────────── │
│ • JWT authentication │
│ • Role-based access (RBAC) │
│ • Audit logging │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Mermaid: Security Architecture
flowchart LR
subgraph Internet["🌐 Internet"]
Caller["Caller"]
Admin["Admin User"]
end
subgraph Boundary1["🔒 Boundary 1: TLS"]
NGINX3["NGINX<br/>TLS 1.2/1.3<br/>Rate Limiting<br/>HSTS"]
end
subgraph Boundary2["🔑 Boundary 2: API Auth"]
VapiAuth["Vapi Webhook<br/>Bearer Token"]
end
subgraph Boundary3["🔐 Boundary 3: Admin Auth"]
JWTAuth["JWT Auth<br/>RBAC Middleware<br/>Audit Logging"]
end
subgraph Boundary4["🏥 Boundary 4: EMR"]
OAuth["OAuth 1.0a<br/>HMAC-SHA1<br/>Per-Clinic Creds"]
end
subgraph Core["⚙️ Voice Agent"]
VoiceAgent2["Node.js<br/>Express"]
end
subgraph External["🏥 Clinic"]
OSCAR3[("OSCAR EMR")]
end
Caller -->|"HTTPS"| NGINX3
Admin -->|"HTTPS"| NGINX3
NGINX3 -->|"Vapi calls"| VapiAuth
NGINX3 -->|"Admin UI"| JWTAuth
VapiAuth --> VoiceAgent2
JWTAuth --> VoiceAgent2
VoiceAgent2 --> OAuth
OAuth -->|"HTTPS"| OSCAR3
style Boundary1 fill:#fef2f2,stroke:#dc2626
style Boundary2 fill:#fff7ed,stroke:#ea580c
style Boundary3 fill:#fef3c7,stroke:#d97706
style Boundary4 fill:#f0fdf4,stroke:#16a34a
Security Decisions Review
| Decision |
Choice |
Rationale |
Alternatives Considered |
| TLS Termination |
NGINX with Let's Encrypt |
Industry standard; free certs; easy renewal |
App-level TLS (performance hit), Cloudflare SSL (proxy mode issues) |
| Vapi Authentication |
Bearer Token |
Vapi's credential system; timing-safe comparison; simple integration |
API Key header (deprecated), mTLS (overkill), IP allowlist (fragile), HMAC (Vapi doesn't send signatures) |
| Admin Authentication |
PassportJS + JWT |
Stateless; scalable; proven library |
Sessions (stateful), OAuth2 (external dependency), Basic Auth (insecure) |
| Password Storage |
bcrypt (cost 12) |
Adaptive hashing; GPU-resistant |
Argon2 (newer but less support), PBKDF2 (faster cracking), SHA256 (too fast) |
| Account Lockout |
5 attempts / 15 min |
Balances security vs usability; industry standard |
Progressive delays (complex), CAPTCHA (accessibility), No lockout (brute force risk) |
| EMR Auth |
OAuth 1.0a |
OSCAR's native protocol; signature-based |
OAuth 2.0 (OSCAR doesn't support), API keys (OSCAR doesn't support) |
| Credential Storage |
Encrypted at rest (AES-256) |
PIPEDA compliance; defense in depth |
Plain text (non-compliant), Vault (complexity), KMS (cloud lock-in) |
| Audit Logging |
Append-only table |
Immutable; compliance-ready; 7-year retention |
File logs (harder to query), External service (latency), None (non-compliant) |
Next Steps