Skip to content

System Architecture

Components, Integrations, and Data Flow

Last Updated: February 15, 2026 (v4.0.0 — SOAP adapter live, onboarding redesign)


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)                                    │  │
│   │                                                                       │  │
│   │    ┌────────────┐    ┌────────────┐    ┌────────────┐               │  │
│   │    │ STT        │    │  GPT-4o    │    │   TTS      │               │  │
│   │    │ (per-lang) │───▶│   LLM      │───▶│ (per-lang) │               │  │
│   │    │            │    │            │    │            │               │  │
│   │    └────────────┘    └─────┬──────┘    └────────────┘               │  │
│   │                            │                                         │  │
│   │                     Tool Calls (Webhooks)                            │  │
│   │                            │                                         │  │
│   └────────────────────────────┼─────────────────────────────────────────┘  │
│                                │                                             │
│                                ▼                                             │
│   ┌──────────────────────────────────────────────────────────────────────┐  │
│   │                      MIDDLEWARE LAYER                                │  │
│   │                      (Vitara Platform)                               │  │
│   │                                                                       │  │
│   │    ┌────────────────────────────────────────────────────────────┐   │  │
│   │    │                    NGINX (9443)                            │   │  │
│   │    │              SSL + Rate Limiting                           │   │  │
│   │    └─────────────────────────┬──────────────────────────────────┘   │  │
│   │                              │                                       │  │
│   │    ┌─────────────────────────▼──────────────────────────────────┐   │  │
│   │    │               Voice Agent (Node.js 3002)                   │   │  │
│   │    │                                                             │   │  │
│   │    │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │   │  │
│   │    │  │  26 Vapi│  │   Clinic    │  │   OSCAR     │        │   │  │
│   │    │  │  Handlers│  │   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["STT<br/>(AssemblyAI / Deepgram)"]
        LLM["GPT-4o<br/>(Understanding)"]
        TTS["TTS<br/>(ElevenLabs / Azure)"]
        STT --> LLM --> TTS
    end

    subgraph Middleware["⚙️ Middleware Layer (Vitara Platform)"]
        NGINX["NGINX :9443<br/>SSL + Rate Limiting"]
        VoiceAgent["Voice Agent<br/>Node.js :3002"]
        Endpoints["26 Vapi Tool Handlers"]
        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 AssemblyAI Universal (v2.3) / Deepgram per-lang (v3.0) Bilingual detection (v2.3); per-language accuracy (v3.0) Whisper (higher latency), Google STT (cost), Deepgram multi (forces EN on ZH)
TTS ElevenLabs (EN) / Azure Xiaoxiao (ZH in v3.0) Natural English voice; native Mandarin quality Amazon Polly (robotic), Google TTS (less natural), ElevenLabs turbo_v2_5 (EN-only)
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 v2.3.0 (Production) v3.0 (Deployed)
Speech-to-Text AssemblyAI Universal (all agents) Router: AssemblyAI Universal; EN: Deepgram nova-2 en; ZH: Deepgram nova-2 zh
LLM OpenAI GPT-4o OpenAI GPT-4o
Text-to-Speech ElevenLabs multilingual_v2 (all agents) EN: ElevenLabs multilingual_v2; ZH: Azure zh-CN-XiaoxiaoNeural
Squad Size 6 assistants 9 assistants (Router + 4 roles x 2 langs)

Middleware Layer (Vitara Platform)

Component Technology Purpose
Reverse Proxy NGINX SSL termination, rate limiting
API Server Node.js + Express + TypeScript Webhook handling, admin API
ORM Prisma (13 models) Database access, migrations
Database PostgreSQL 16 Configuration, logs, audit trail
Process Manager PM2 Auto-restart, graceful shutdown

EMR Abstraction Layer (v4.0 — IEmrAdapter + SOAP)

Component Technology Purpose
IEmrAdapter adapters/IEmrAdapter.ts Canonical EMR interface (ADR-003)
EmrAdapterFactory adapters/EmrAdapterFactory.ts Per-clinic adapter creation with 5-min TTL cache
OscarSoapAdapter adapters/OscarSoapAdapter.ts ACTIVE — Direct OSCAR SOAP/WS-Security (v4.0)
OscarBridgeAdapter adapters/OscarBridgeAdapter.ts DEPRECATED — Legacy REST Bridge client (dev only)

v4.0 change (2026-02-14): SOAP adapter is now the active production path. Connects directly to OSCAR's 3 SOAP endpoints (ScheduleService, DemographicService, ProviderService) using WS-Security UsernameToken authentication. No middleman, no sidecar on customer OSCAR instances. The REST Bridge adapter is retained for dev/fallback only. Factory selects adapter based on emr_type in clinic_config (oscar-soap or oscar-universal).

EMR Layer

Component Technology Purpose
OSCAR EMR Per-clinic instance (AWS ca-central-1 for dev) Patient data, appointments
Integration SOAP/WS-Security (active) or REST Bridge (deprecated) Secure API access via 3 WSDL endpoints

Network Topology

Internet
┌─────────────────┐
│   Cloudflare    │  DNS + DDoS protection
│   (DNS)         │
└────────┬────────┘
┌─────────────────┐
│  OCI Toronto    │  Production server
│  (Vitara)       │
│                 │
│  └─ NGINX:9443  │
│  └─ Node:3002   │
│  └─ Postgres    │
└────────┬────────┘
         │ SOAP/WS-Security (HTTPS :8443)
┌─────────────────┐
│  OSCAR EMR      │  AWS ca-central-1 (dev)
│  (Per-Clinic)   │  Customer instances in production
└─────────────────┘

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 :3002"]
        PG["PostgreSQL"]
    end

    subgraph ClinicNetwork["Clinic Network"]
        OSCAR2[("OSCAR EMR<br/>(External)")]
    end

    Internet --> DNS
    DNS --> NGINX2
    NGINX2 --> Node
    Node --> PG
    Node -->|"SOAP/WS-Security<br/>(HTTPS :8443)"| 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 SOAP/WS-Security (v4.0) OSCAR's native SOAP API ships with every version since OSCAR 12; no sidecar needed on customer instances; PHIPA/PIPA compliant REST Bridge (requires sidecar — compliance risk), OAuth 1.0a REST (not universally available), Direct DB (security risk)

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                                                  │
│  ────────────────────────────                                               │
│  • HMAC-SHA256 signature (primary, mandatory in production)                 │
│  • API Key / Bearer Token (fallback)                                        │
│  • 5-minute replay window                                                   │
│                                                                              │
│  BOUNDARY 3: Vitara → OSCAR                                                 │
│  ─────────────────────────────                                              │
│  • SOAP/WS-Security UsernameToken (production path)                         │
│  • OAuth 1.0a HMAC-SHA1 (legacy bridge path)                               │
│  • Per-clinic credentials                                                   │
│  • Encrypted at rest (AES-256-GCM)                                          │
│                                                                              │
│  BOUNDARY 4: Admin UI → Vitara                                              │
│  ─────────────────────────────                                              │
│  • JWT authentication (access + refresh tokens)                             │
│  • Role-based access (VITARA_ADMIN, CLINIC_MANAGER)                        │
│  • Audit logging (append-only)                                              │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

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: Webhook Auth"]
        VapiAuth["HMAC-SHA256<br/>5-min Replay Window<br/>API Key Fallback"]
    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 HMAC-SHA256 (primary) Vapi sends x-vapi-signature + x-vapi-timestamp; 5-min replay window; fail-closed in production Bearer Token only (no replay protection), API Key only (static secret), mTLS (overkill), IP allowlist (fragile)
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 AES-256-GCM encryption at rest PIPEDA compliance; lib/crypto.ts; ENCRYPTION_KEY required in production Plain text (non-compliant), Vault (complexity), KMS (cloud lock-in)
Audit Logging Append-only Prisma model Immutable; compliance-ready; middleware auto-captures mutations File logs (harder to query), External service (latency), None (non-compliant)
Input Validation Zod schemas with .strict() 18 schemas; fail-fast on invalid input; TypeScript type inference Joi (less TS integration), Manual checks (error-prone), None (injection risk)
PHI Log Protection redactPhi() helper PHI keys replaced with [REDACTED] before logging; PIPEDA compliance Pino redact paths (limited), No logging (no debugging), Full logging (PHI exposure)

Next Steps