Skip to content

API Endpoints

All Webhook Endpoints (via Vapi tool webhooks) — Updated for v4.0.0


Base URL

Environment URL
Production https://api.vitaravox.ca:9443
Development https://api-dev.vitaravox.ca:9443

Authentication

Vapi webhook endpoints support multi-auth (any one method is sufficient):

Method Header Description
Bearer Token Authorization: Bearer <secret> Primary method. Token matches VAPI_WEBHOOK_SECRET env var
HMAC-SHA256 X-Vapi-Signature: <hmac-sha256-signature> Vapi's built-in webhook signing
API Key X-API-Key: <api-key> Legacy/admin API key

All comparisons use constant-time (crypto.timingSafeEqual) to prevent timing attacks. In development mode (non-production), auth is skipped if no secret is configured.

Admin API endpoints use JWT authentication:

Authorization: Bearer <jwt-token>

Health & Status

GET /health

Health check endpoint. No authentication required.

Response:

{
  "status": "healthy",
  "timestamp": "2026-01-12T15:30:00.000Z",
  "version": "1.1.0",
  "components": {
    "database": { "healthy": true, "latency_ms": 2 },
    "oscar": { "healthy": true, "latency_ms": 45 }
  }
}


Patient Lookup

POST /api/vapi/search-patient-by-phone

Search patient by phone number (caller ID).

Request:

{
  "phone": "+16045551234"
}

Response (Found):

{
  "success": true,
  "patient": {
    "demographicNo": 12345,
    "firstName": "John",
    "lastName": "Smith",
    "phone": "604-555-1234"
  }
}

Response (Not Found):

{
  "success": true,
  "patient": null
}


POST /api/vapi/search-patient

Search patient by name.

Request:

{
  "firstName": "John",
  "lastName": "Smith"
}

Response:

{
  "success": true,
  "patients": [
    {
      "demographicNo": 12345,
      "firstName": "John",
      "lastName": "Smith",
      "dateOfBirth": "1985-03-15"
    }
  ]
}


POST /api/vapi/get-patient

Get full patient details by demographic number.

Request:

{
  "demographicNo": 12345
}

Response:

{
  "success": true,
  "patient": {
    "demographicNo": 12345,
    "firstName": "John",
    "lastName": "Smith",
    "dateOfBirth": "1985-03-15",
    "sex": "M",
    "hin": "9876543210",
    "phone": "604-555-1234",
    "email": "john.smith@email.com",
    "address": {
      "street": "123 Main St",
      "city": "Vancouver",
      "province": "BC",
      "postalCode": "V6A 1A1"
    }
  }
}


Clinic Information

POST /api/vapi/get-clinic-info

Get clinic configuration including registration status.

Request:

{
  "clinicId": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
}

Response:

{
  "success": true,
  "clinic": {
    "id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
    "name": "Downtown Medical Clinic",
    "phone": "604-555-0100",
    "address": "456 Health Ave, Vancouver, BC V6B 2B2",
    "accepting_new_patients": true,
    "waitlist_enabled": true,
    "hours": {
      "monday": { "open": "09:00", "close": "17:00" },
      "tuesday": { "open": "09:00", "close": "17:00" },
      "wednesday": { "open": "09:00", "close": "17:00" },
      "thursday": { "open": "09:00", "close": "17:00" },
      "friday": { "open": "09:00", "close": "17:00" },
      "saturday": { "closed": true },
      "sunday": { "closed": true }
    }
  }
}


POST /api/vapi/get-providers

Get list of providers for a clinic.

Request:

{
  "clinicId": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
}

Response:

{
  "success": true,
  "providers": [
    {
      "providerNo": "100001",
      "displayName": "Dr. Sarah Chen",
      "specialty": "Family Medicine",
      "acceptingNewPatients": true
    },
    {
      "providerNo": "100002",
      "displayName": "Dr. Michael Wong",
      "specialty": "Family Medicine",
      "acceptingNewPatients": false
    }
  ]
}


Appointment Management

POST /api/vapi/find-earliest-appointment

Find the earliest available appointment slot. Returns exactly one slot to prevent decision paralysis. Supports smart filtering and server-side guardrails.

Request:

{
  "providerId": "100001",
  "providerName": "Dr. Chen",
  "startDate": "2026-02-06",
  "endDate": "2026-02-20",
  "timeOfDay": "morning",
  "excludeDates": ["2026-02-07", "2026-02-10"]
}

Parameter Type Required Description
providerId string No Provider ID or "any" for any provider
providerName string No Human name (e.g. "Dr. Chen"). Server fuzzy-matches against provider list, strips "Dr." prefix
startDate string No ISO date. Server clamps past dates to today (GPT-4o doesn't know current date)
endDate string No ISO date. Limits search window
timeOfDay string No "morning", "afternoon", or "evening" — filters by time range
excludeDates string[] No Dates to skip (used when patient rejects a slot, then searches again)

Server-side behaviors: - If startDate is in the past, clamps to today with a warning log - If providerName is provided but providerId is not, resolves name via fuzzy matching - Non-numeric providerId values ("any", "任何") treated as "search all providers" - Returns exactly 1 slot (the earliest match)

Response:

{
  "success": true,
  "appointment": {
    "date": "2026-02-07",
    "time": "09:30",
    "duration": 15,
    "provider": {
      "providerNo": "100001",
      "displayName": "Dr. Sarah Chen"
    }
  }
}


POST /api/vapi/check-appointments

Check available appointments or patient's existing appointments.

Request (Find Available):

{
  "providerId": "100001",
  "startDate": "2026-01-13",
  "endDate": "2026-01-20",
  "findAvailable": true
}

Request (Patient's Appointments — v3.0 uses demographicId):

{
  "demographicId": 12345,
  "startDate": "2026-02-10",
  "endDate": "2026-08-10",
  "findAvailable": false
}

Dual parameter naming

Both demographicId and demographicNo/patientId are accepted. The server checks for demographicId first, then falls back to patientId and demographicNo.

Response (Available):

{
  "success": true,
  "appointments": [
    {
      "date": "2026-01-14",
      "time": "09:30",
      "duration": 15,
      "provider": { "providerNo": "100001", "displayName": "Dr. Sarah Chen" }
    },
    {
      "date": "2026-01-14",
      "time": "10:00",
      "duration": 15,
      "provider": { "providerNo": "100001", "displayName": "Dr. Sarah Chen" }
    }
  ]
}


POST /api/vapi/create-appointment

Book a new appointment. Accepts both legacy and v3.0 parameter names.

Request (v3.0 — preferred):

{
  "demographicId": 12345,
  "providerId": "100001",
  "startTime": "2026-02-14T09:30:00",
  "appointmentType": "B",
  "reason": "Prescription refill",
  "language": "en"
}

Request (legacy — still accepted):

{
  "demographicNo": 12345,
  "providerNo": "100001",
  "date": "2026-02-14",
  "startTime": "09:30",
  "duration": 15,
  "appointmentType": "B",
  "reason": "Prescription refill",
  "notes": "Booked via VitaraVox"
}

Parameter Type Required v3.0 Name Legacy Name Description
demographicId number Yes demographicId demographicNo, patientId Patient demographic number
providerId string Yes providerId providerNo Provider number
startTime string Yes startTime appointmentTime, date+startTime ISO 8601 datetime (YYYY-MM-DDTHH:MM:00) or bare time
appointmentType string No appointmentType same B (general), 2 (follow-up), 3 (concern), P (prescription). Server validates against ['B','2','3','P'] — invalid values default to 'B'
reason string No reason same Visit reason from patient
language string No language en or zh. Cached for call log via callMetadataCache

Server-side behaviors (v3.0):

  • Accepts both demographicId and patientId/demographicNo (first non-null wins)
  • Robust time parsing: ISO 8601, date+time, bare time formats
  • appointmentType validated against ['B', '2', '3', 'P'] — invalid defaults to 'B'
  • language and demographicId cached via setCallMetadata for end-of-call log

Response:

{
  "success": true,
  "appointment": {
    "appointmentNo": 99001,
    "demographicNo": 12345,
    "providerNo": "100001",
    "date": "2026-02-14",
    "startTime": "09:30",
    "duration": 15,
    "status": "confirmed"
  }
}


POST /api/vapi/update-appointment

Reschedule an existing appointment. v3.0 uses appointmentId + newStartTime + newProviderId.

Request (v3.0 — preferred):

{
  "appointmentId": 99001,
  "newStartTime": "2026-02-15T14:00:00",
  "newProviderId": "100002"
}

Request (legacy — still accepted):

{
  "appointmentNo": 99001,
  "newDate": "2026-02-15",
  "newStartTime": "14:00"
}

Parameter Type Required v3.0 Name Legacy Name Description
appointmentId number Yes appointmentId appointmentNo Existing appointment ID to reschedule
newStartTime string Yes newStartTime newDate+newStartTime ISO 8601 datetime for new slot
newProviderId string No newProviderId New provider ID if changing doctor

Server-side behavior: Books the new appointment first, then cancels the old one (safer than cancel-first).

Response:

{
  "success": true,
  "appointment": {
    "appointmentNo": 99001,
    "date": "2026-02-15",
    "startTime": "14:00",
    "status": "rescheduled"
  }
}


POST /api/vapi/cancel-appointment

Cancel an appointment.

Request (v3.0):

{
  "appointmentId": 99001,
  "reason": "patient requested"
}

Request (legacy):

{
  "appointmentNo": 99001,
  "reason": "Patient requested"
}

Parameter Type Required v3.0 Name Legacy Name Description
appointmentId number Yes appointmentId appointmentNo Appointment to cancel
reason string No reason same Cancellation reason

Response:

{
  "success": true,
  "message": "Appointment cancelled",
  "appointmentNo": 99001
}


Registration & Waitlist

POST /api/vapi/register-new-patient

Register a new patient in OSCAR EMR. v3.0 adds healthCardType, language, and flattened address fields.

Request (v3.0 — preferred):

{
  "firstName": "Jane",
  "lastName": "Doe",
  "dateOfBirth": "1990-05-20",
  "gender": "F",
  "phone": "6045559876",
  "address": "789 Oak St",
  "city": "Vancouver",
  "postalCode": "V6C 3C3",
  "province": "BC",
  "healthCardNumber": "9123456789",
  "healthCardType": "BC",
  "language": "en",
  "email": "jane.doe@email.com"
}

Request (legacy — still accepted):

{
  "firstName": "Jane",
  "lastName": "Doe",
  "dateOfBirth": "1990-05-20",
  "sex": "F",
  "hin": "9123456789",
  "phone": "604-555-9876",
  "email": "jane.doe@email.com",
  "address": {
    "street": "789 Oak St",
    "city": "Vancouver",
    "province": "BC",
    "postalCode": "V6C 3C3"
  }
}

Parameter Type Required Description
firstName string Yes Patient first name
lastName string Yes Patient last name
dateOfBirth string Yes YYYY-MM-DD format
gender / sex string Yes M, F, or O (both field names accepted)
phone string Yes 10-digit phone number
address string No Street address (v3.0 flat) or object (legacy)
city string No City
postalCode string No Postal code
province string No Province (default BC)
healthCardNumber / hin string No Health card number
healthCardType string No BC, OUT_OF_PROVINCE, or PRIVATE. Maps to OSCAR hcType field
language string No en or zh. Cached for call log
email string No Email address

Server-side behaviors (v3.0):

  • healthCardType mapped to OSCAR hcType values: BC -> BC, OUT_OF_PROVINCE -> OT, PRIVATE -> PR
  • language and demographicId cached via setCallMetadata as fallback for call log
  • Gender normalized: male/M -> M, female/F -> F, other/O -> O

Response:

{
  "success": true,
  "patient": {
    "demographicNo": 12346,
    "firstName": "Jane",
    "lastName": "Doe"
  },
  "message": "Patient registered successfully"
}


POST /api/vapi/add-to-waitlist

Add patient to new patient waitlist.

Request:

{
  "clinicId": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
  "firstName": "Jane",
  "lastName": "Doe",
  "phone": "604-555-9876",
  "email": "jane.doe@email.com",
  "notes": "Interested in family medicine"
}

Response:

{
  "success": true,
  "waitlistId": "w-12345",
  "position": 15,
  "message": "Added to waitlist"
}


Call Management

POST /api/vapi/transfer-call

Transfer call to clinic staff.

Request:

{
  "reason": "medical_question",
  "context": "Patient asking about medication interactions"
}

Response:

{
  "success": true,
  "action": "transfer",
  "destination": "+16045550100"
}


POST /api/vapi/log-call-metadata

Log call metadata for analytics. In v3.0, this is called by every role agent (Booking, Modification, Registration) instead of a separate Confirmation agent.

Request (v3.0):

{
  "callOutcome": "booked",
  "language": "en",
  "demographicId": 12345,
  "appointmentId": 99001
}

Parameter Type Required Description
callOutcome string Yes booked, rescheduled, cancelled, registered, waitlisted, no_action, clinic_info, transferred
language string No en or zh
demographicId number No Patient demographic number
appointmentId number No Related appointment ID

Server-side behavior (v3.0 — callMetadataCache):

The log_call_metadata tool caches its data in an in-memory Map keyed by callId:

setCallMetadata(callId, { language, outcome, demographicId, appointmentId })

When the end-of-call-report webhook fires, saveCallLog calls getCallMetadata(callId) and merges the cached data into the database record. This bridges the gap because Vapi's end-of-call-report does not include tool call results.

Fallback caching: create_appointment and register_new_patient also call setCallMetadata with language and demographicId — ensuring the call log has this data even if the LLM never calls log_call_metadata.

Response:

{
  "success": true,
  "message": "Call metadata logged"
}


Error Responses

All endpoints return consistent error format:

{
  "success": false,
  "error": {
    "code": "PATIENT_NOT_FOUND",
    "message": "No patient found with the provided criteria"
  }
}

Error Codes

Code HTTP Status Description
PATIENT_NOT_FOUND 404 Patient not found in EMR
APPOINTMENT_NOT_FOUND 404 Appointment not found
NO_AVAILABILITY 404 No appointments available
INVALID_REQUEST 400 Invalid request parameters
OSCAR_UNAVAILABLE 503 OSCAR EMR unreachable
UNAUTHORIZED 401 Invalid or missing API key
RATE_LIMITED 429 Too many requests
INTERNAL_ERROR 500 Internal server error

Rate Limits

Endpoint Limit Burst
General API 100/min 20
Booking (create-appointment) 20/min 5
Health check Unlimited -

Admin API (v1.5.0+)

The Admin API provides endpoints for clinic and user management. All admin endpoints require JWT authentication.

Authentication

Authorization: Bearer <jwt-token>

Response Format

All admin endpoints return a consistent format:

{
  "success": true,
  "data": [...],
  "pagination": {
    "page": 1,
    "pageSize": 10,
    "total": 25,
    "totalPages": 3
  }
}

Auth Endpoints

Endpoint Method Description
/api/auth/login POST User login, returns JWT tokens
/api/auth/logout POST User logout
/api/auth/refresh POST Refresh access token
/api/auth/change-password POST Change own password
/api/auth/me GET Get current user info

Admin - Clinics (Vitara Admin only)

Endpoint Method Description
/api/admin/clinics GET List clinics (paginated)
/api/admin/clinics/stats GET Clinic statistics
/api/admin/clinics POST Create clinic
/api/admin/clinics/:id GET Get clinic details
/api/admin/clinics/:id PUT Update clinic
/api/admin/clinics/:id DELETE Delete clinic
/api/admin/clinics/:id/emr-config PUT Update EMR config
/api/admin/clinics/:id/go-live POST Set EMR live status

Admin - Users (Vitara Admin only)

Endpoint Method Description
/api/admin/users GET List users (paginated)
/api/admin/users POST Create user
/api/admin/users/:id GET Get user details
/api/admin/users/:id PUT Update user
/api/admin/users/:id DELETE Delete user
/api/admin/users/:id/reset-password POST Reset password
/api/admin/users/:id/unlock POST Unlock account

Admin - Audit Logs (Vitara Admin only)

Endpoint Method Description
/api/admin/audit-logs GET List audit logs (paginated)
/api/admin/audit-logs/summary GET Activity summary

Clinic Manager - Self-Service

Endpoint Method Description
/api/clinic/me GET Get own clinic
/api/clinic/settings GET Get clinic settings (getClinicSettings)
/api/clinic/stats GET Get clinic statistics
/api/clinic/calls GET Get recent calls
/api/clinic/providers GET Get clinic providers
/api/clinic/schedule GET Get schedule settings
/api/clinic/emr GET Get EMR configuration
/api/clinic/emr/test POST Test OSCAR connection
/api/clinic/emr/sync POST Sync from OSCAR
/api/clinic/providers/sync POST Sync providers from OSCAR
/api/clinic/analytics GET Get call analytics
/api/clinic/call-history GET Get paginated call history
/api/clinic/waitlist GET Get waitlist entries
/api/clinic/waitlist/stats GET Get waitlist statistics

Clinic Manager - Onboarding (v4.0.0+)

Endpoint Method Description
/api/clinic/onboarding GET Get onboarding progress
/api/clinic/onboarding PUT Update onboarding step flags
/api/clinic/onboarding/validate GET Run pre-launch validation checks
/api/clinic/onboarding/go-live POST Activate clinic (blocked if checks fail)
/api/clinic/onboarding/complete POST Complete onboarding (notifies admin)
/api/clinic/onboarding/test-slots POST Test schedule slot retrieval (validation step)
/api/clinic/onboarding/test-patient-search POST Test patient search (validation step)
/api/clinic/oscar-config GET Get OSCAR config (schedule codes, appointment types)
/api/clinic/oscar-config/pull POST Pull config from OSCAR

Admin - Provisioning (v4.0.0+)

Endpoint Method Description
/api/admin/clinics/pending-activation GET List clinics awaiting activation
/api/admin/clinics/:id/provisioning GET Get provisioning status for a clinic
/api/admin/clinics/:id/activate PUT Activate clinic (requires phone assigned)

Admin - Vapi Management (v2.1.0+)

Endpoint Method Description
/api/admin/vapi/mappings GET List all clinic-assistant mappings
/api/admin/clinics/:id/vapi PUT Update Vapi config for a clinic
/api/admin/clinics/:id/vapi/test POST Test Vapi connection
/api/admin/clinics/:id/vapi DELETE Remove Vapi config from clinic

Example: Get Vapi Mappings Response

{
  "success": true,
  "data": [
    {
      "clinicId": "vitaravox-demo",
      "clinicName": "VitaraVox Demo Clinic",
      "vapiPhone": "+1 (604) 555-8888",
      "vapiAssistantId": "45f98810-2ff3-4c25-9cf0-54a2d2822d37",
      "vapiSquadId": null,
      "vapiConnected": true,
      "oscarConnected": true,
      "status": "active"
    }
  ]
}

Example: Update Vapi Config

PUT /api/admin/clinics/vitaravox-demo/vapi
{
  "vapiAssistantId": "45f98810-2ff3-4c25-9cf0-54a2d2822d37",
  "vapiPhone": "+1 (604) 555-8888"
}


v3.0 Server-Side Changes Summary

Parameter Naming (Dual Support)

v3.0 Vapi tools use demographicId, appointmentId, providerId, startTime, etc. The server accepts both v3.0 and legacy parameter names for backward compatibility:

v3.0 Name Legacy Names Also Accepted
demographicId patientId, demographicNo
appointmentId appointmentNo
providerId providerNo
startTime (ISO 8601) date + startTime, appointmentTime
newStartTime (ISO 8601) newDate + newStartTime
newProviderId — (new in v3.0)
healthCardType — (new in v3.0)
language — (new in v3.0)
callOutcome outcome

Call Metadata Cache

The callMetadataCache is an in-memory Map<string, CallMetadata> that bridges tool-call webhooks and end-of-call-report webhooks:

Function Purpose
setCallMetadata(callId, data) Store metadata during tool calls
getCallMetadata(callId) Retrieve in saveCallLog for merging into DB

Set by: log_call_metadata, create_appointment, register_new_patient Read by: saveCallLog (on end-of-call-report webhook)

Appointment Type Validation

Server validates appointmentType against ['B', '2', '3', 'P']. Invalid values silently default to 'B'.

Clinic Resolution (v3.0 Squad Support)

Webhook requests resolve the clinic through:

metadata.clinicId → assistantId → squadId → phoneNumber

The clinic_config table now has both vapi_squad_id (v2.3.0) and vapi_squad_id_v3 (v3.0) columns. The resolver checks both:

WHERE vapi_squad_id = $1 OR vapi_squad_id_v3 = $1

Next Steps