docs: consolidate all integration guides into single master API_INTEGRATION_GUIDE.md
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# AI Patient Onboarding API — Frontend Integration Guide
|
||||
# AI Patient Onboarding API — Complete Frontend Integration Guide
|
||||
|
||||
**Version**: `1.0.0`
|
||||
**Target Audience**: Frontend Engineers (React / React Native / Vue / Next.js / Mobile)
|
||||
@@ -13,7 +13,9 @@
|
||||
|
||||
## 1. Executive System Overview
|
||||
|
||||
The **AI Patient Onboarding API** is an enterprise multi-modal clinical intake system designed for hospitals, clinics, and digital health platforms. It automates patient registration, symptom intake, medical history collection, document extraction, and specialty triage before a physician consultation.
|
||||
The **AI Patient Onboarding API** is an enterprise multi-modal clinical intake system designed for hospitals, clinics, a
|
||||
|
||||
nd digital health platforms. It automates patient registration, symptom intake, medical history collection, document extraction, and specialty triage before a physician consultation.
|
||||
|
||||
### Key Capabilities for Frontend Applications:
|
||||
1. **Multi-Modal Onboarding**:
|
||||
@@ -60,21 +62,7 @@ The backend enforces production-safe `CORSMiddleware`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Documentation Index
|
||||
|
||||
To explore detailed specifications, refer to the following module guides:
|
||||
|
||||
| Module Guide | Focus Area | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| [`AUTHENTICATION.md`](file:///c:/Users/Lenovo/New%20folder%20%282%29/ai-patient-onboarding/docs/AUTHENTICATION.md) | Security & Headers | Bearer tokens, `X-API-Key`, patient session isolation (IDOR protection). |
|
||||
| [`ONBOARDING_API.md`](file:///c:/Users/Lenovo/New%20folder%20%282%29/ai-patient-onboarding/docs/ONBOARDING_API.md) | Text & Document Intake | Full specification for text chat endpoints, document OCR, and session completion. |
|
||||
| [`VOICE_ONBOARDING_API.md`](file:///c:/Users/Lenovo/New%20folder%20%282%29/ai-patient-onboarding/docs/VOICE_ONBOARDING_API.md) | Voice & Speech APIs | Audio streaming, WebSpeech fallbacks, multilingual Sarvam AI voice turns. |
|
||||
| [`ERROR_HANDLING.md`](file:///c:/Users/Lenovo/New%20folder%20%282%29/ai-patient-onboarding/docs/ERROR_HANDLING.md) | Error Codes & SLAs | HTTP 401, 403, 413, 422, 429 status codes and SLA latency boundaries. |
|
||||
| [`FRONTEND_EXAMPLES.md`](file:///c:/Users/Lenovo/New%20folder%20%282%29/ai-patient-onboarding/docs/FRONTEND_EXAMPLES.md) | TypeScript & React | Copy-pasteable React hooks, MediaRecorder voice capture, and fetch client examples. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Master API Endpoint Directory
|
||||
## 4. Master API Endpoint Directory
|
||||
|
||||
| HTTP Method | Endpoint Path | Auth Required | Rate Limit | Purpose |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
@@ -90,18 +78,387 @@ To explore detailed specifications, refer to the following module guides:
|
||||
|
||||
---
|
||||
|
||||
## 6. Response Time & Timing Metadata
|
||||
## 5. Authentication & Patient Security
|
||||
|
||||
All interactive conversation endpoints append performance metadata to help frontend applications measure latency:
|
||||
### Authentication Mechanisms:
|
||||
|
||||
#### Method A: HTTP Bearer Token (Standard)
|
||||
```http
|
||||
Authorization: Bearer <ACCESS_TOKEN>
|
||||
```
|
||||
|
||||
#### Method B: `X-API-Key` Custom Header
|
||||
```http
|
||||
X-API-Key: <API_KEY>
|
||||
```
|
||||
|
||||
### Supported Access Tokens:
|
||||
1. **Service Secret API Key**: `skyheal_secure_api_key_2026` (configurable via `API_SECRET_KEY` in server environment).
|
||||
2. **Patient Development Tokens**: `test_token_<PATIENT_ID>` (e.g. `test_token_PAT-10023`). Automatically establishes patient identity context.
|
||||
|
||||
### Patient Isolation & IDOR Protection Rules:
|
||||
1. When starting a session (`/v1/onboarding/start`), `patient.id` is bound as session owner (`session.patient_id`).
|
||||
2. Subsequent requests verify caller's authenticated `patient_id` matches session owner.
|
||||
3. Access attempts by other patients return **`HTTP 403 Forbidden`**.
|
||||
|
||||
---
|
||||
|
||||
## 6. Text Onboarding API Specifications
|
||||
|
||||
### 6.1. POST `/v1/onboarding/start`
|
||||
|
||||
Starts a new text-based clinical onboarding intake session.
|
||||
|
||||
#### Request Headers:
|
||||
```http
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer test_token_PAT-10023
|
||||
```
|
||||
|
||||
#### Request Body (`StartOnboardingRequest`):
|
||||
```json
|
||||
{
|
||||
"patient": {
|
||||
"id": "PAT-10023",
|
||||
"name": "Kavitha Ramesh",
|
||||
"age": 35,
|
||||
"gender": "female",
|
||||
"phone": "+919876543210",
|
||||
"abha_id": "91-1234-5678-9012",
|
||||
"insurance_scheme": "PMJAY",
|
||||
"dietary_habits": "Vegetarian"
|
||||
},
|
||||
"category": {
|
||||
"type": "general",
|
||||
"code": "general_medicine",
|
||||
"name": "General Medicine"
|
||||
},
|
||||
"language": "en"
|
||||
}
|
||||
```
|
||||
|
||||
#### Field Descriptions:
|
||||
|
||||
| Field | Type | Required | Validation Rules | Description |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `patient.id` | string | **Yes** | Min length 1 | Unique patient identifier |
|
||||
| `patient.name` | string | No | Optional | Full name of patient |
|
||||
| `patient.age` | integer | No | $0 \le \text{age} \le 120$ | Patient age in years |
|
||||
| `patient.gender` | string | No | Optional | Gender identity ("male", "female", "other") |
|
||||
| `category.type` | string | **Yes** | `"general"` or `"speciality"` | Onboarding workflow type |
|
||||
| `category.code` | string | **Yes** | Min length 1 | Specialty classification code |
|
||||
| `category.name` | string | **Yes** | Min length 1 | Category display name |
|
||||
| `language` | string | No | Default `"en"` | Preferred intake language |
|
||||
|
||||
#### Successful Response (`200 OK` - `StartOnboardingResponse`):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"session_id": "ONB-8F12A9C4",
|
||||
"patient_id": "PAT-10023",
|
||||
"category": {
|
||||
"type": "general",
|
||||
"code": "general_medicine",
|
||||
"name": "General Medicine"
|
||||
},
|
||||
"language": "en",
|
||||
"status": "in_progress",
|
||||
"stage": "symptoms",
|
||||
"message": "Hello Kavitha Ramesh! I am your clinical onboarding assistant. What symptoms are you experiencing today?"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6.2. POST `/v1/onboarding/{session_id}/message`
|
||||
|
||||
Sends a patient chat response to the AI intake orchestrator.
|
||||
|
||||
#### Request Body (`SendMessageRequest`):
|
||||
```json
|
||||
{
|
||||
"message": "I have had severe chest tightness and shortness of breath for 2 days."
|
||||
}
|
||||
```
|
||||
|
||||
#### Successful Response (`200 OK` - `SendMessageResponse`):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"session_id": "ONB-8F12A9C4",
|
||||
"stage": "medical_history",
|
||||
"message": "Thank you for sharing. Does the chest tightness radiate to your arm or jaw, and do you have any history of high blood pressure?",
|
||||
"status": "in_progress",
|
||||
"response_time_ms": 1529.74,
|
||||
"response_time_sec": 1.53
|
||||
}
|
||||
```
|
||||
|
||||
- `response_time_ms`: Backend processing duration in milliseconds (floating-point number).
|
||||
- `response_time_sec`: Backend processing duration in seconds (rounded to 3 decimal places).
|
||||
---
|
||||
|
||||
### 6.3. POST `/v1/onboarding/{session_id}/complete`
|
||||
|
||||
Finalizes the intake session and outputs the structured clinical EHR payload.
|
||||
|
||||
#### Request Body (`CompleteOnboardingRequest`):
|
||||
```json
|
||||
{
|
||||
"confirmed": true
|
||||
}
|
||||
```
|
||||
|
||||
#### Successful Response (`200 OK` - `CompleteOnboardingResponse`):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"session_id": "ONB-8F12A9C4",
|
||||
"status": "completed",
|
||||
"result": {
|
||||
"patient_id": "PAT-10023",
|
||||
"abha_id": "91-1234-5678-9012",
|
||||
"phone": "+919876543210",
|
||||
"insurance_scheme": "PMJAY",
|
||||
"category": {
|
||||
"type": "general",
|
||||
"code": "general_medicine",
|
||||
"name": "General Medicine"
|
||||
},
|
||||
"chief_complaint": "Chest tightness and shortness of breath for 2 days",
|
||||
"symptoms": [
|
||||
{
|
||||
"name": "Chest tightness",
|
||||
"duration": "2 days",
|
||||
"severity": "severe",
|
||||
"description": "Triggered during exertion"
|
||||
}
|
||||
],
|
||||
"medical_history": ["Prehypertension"],
|
||||
"medications": ["Metformin 500mg"],
|
||||
"allergies": ["Penicillin"],
|
||||
"dietary_habits": "Vegetarian",
|
||||
"additional_information": "Patient confirmed details.",
|
||||
"language": "en",
|
||||
"onboarding_completed": true
|
||||
},
|
||||
"message": "Onboarding successfully completed and saved to patient record.",
|
||||
"response_time_ms": 4075.41,
|
||||
"response_time_sec": 4.08
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Voice Onboarding API Specifications
|
||||
|
||||
### 7.1. POST `/v1/voice-onboarding/start`
|
||||
|
||||
Starts a voice-first onboarding session with Sarvam AI multilingual speech engine.
|
||||
|
||||
#### Supported Languages:
|
||||
`en-IN` (Indian English), `ta-IN` (Tamil), `hi-IN` (Hindi), `te-IN` (Telugu), `kn-IN` (Kannada), `ml-IN` (Malayalam), `mr-IN` (Marathi).
|
||||
|
||||
#### Request Body:
|
||||
```json
|
||||
{
|
||||
"patient": {
|
||||
"id": "PAT-VOICE-1",
|
||||
"name": "Ramesh G",
|
||||
"age": 34,
|
||||
"gender": "male"
|
||||
},
|
||||
"category": {
|
||||
"type": "general",
|
||||
"code": "general_medicine",
|
||||
"name": "General Medicine"
|
||||
},
|
||||
"language": "ta-IN"
|
||||
}
|
||||
```
|
||||
|
||||
#### Successful Response (`200 OK`):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"session_id": "VOICE-D3CAEA5C",
|
||||
"patient_id": "PAT-VOICE-1",
|
||||
"category": {
|
||||
"type": "general",
|
||||
"code": "general_medicine",
|
||||
"name": "General Medicine"
|
||||
},
|
||||
"language": "ta-IN",
|
||||
"status": "in_progress",
|
||||
"stage": "greeting",
|
||||
"greeting_text": "வணக்கம் Ramesh G, CureSelect மருத்துவமனைக்கு வரவேற்கிறோம். இன்று உங்களுக்கு என்ன சுகவீனம் உள்ளது?",
|
||||
"audio_url": "/v1/voice-onboarding/audio/aud_a1b2c3d4e5f6",
|
||||
"response_time_ms": 78.32,
|
||||
"response_time_sec": 0.08
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.2. POST `/v1/voice-onboarding/{session_id}/turn`
|
||||
|
||||
Processes a conversational voice turn using audio recording or text fallback.
|
||||
|
||||
#### Multipart Form Data:
|
||||
- `audio`: Binary audio recording file (`WAV`, `WEBM`, `MP3`, `OGG` up to 10MB).
|
||||
- `text_fallback`: String text transcript (used if WebSpeech STT processed client-side).
|
||||
|
||||
#### Successful Response (`200 OK`):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"session_id": "VOICE-D3CAEA5C",
|
||||
"patient_transcript": "எனக்குகடந்த 3 நாளா நெஞ்சு பாரமா இருக்குங்க.",
|
||||
"ai_text": "நன்றி. இந்த நெஞ்சு பாரம் உடற்பயிற்சி செய்யும் போது அதிகமாகிறதா?",
|
||||
"stage": "symptoms",
|
||||
"completed": false,
|
||||
"audio_url": "/v1/voice-onboarding/audio/aud_f9e8d7c6b5a4",
|
||||
"audio_base64": null,
|
||||
"suggested_specialty": {
|
||||
"specialty": "Cardiology",
|
||||
"reason": "Chest tightness reported"
|
||||
},
|
||||
"response_time_ms": 1412.37,
|
||||
"response_time_sec": 1.41
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.3. GET `/v1/voice-onboarding/audio/{audio_id}`
|
||||
|
||||
Streams generated TTS audio for browser or mobile playback.
|
||||
|
||||
```javascript
|
||||
const audio = new Audio(`${BASE_URL}/v1/voice-onboarding/audio/${audioId}`);
|
||||
await audio.play();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Medical Document Analysis API
|
||||
|
||||
### POST `/v1/documents/{session_id}/upload`
|
||||
|
||||
Uploads a medical report / prescription for vision extraction.
|
||||
|
||||
- **Content-Type**: `multipart/form-data`
|
||||
- **File Limit**: Max `10MB` (`10,485,760 bytes`).
|
||||
- **Allowed Formats**: `image/jpeg`, `image/png`, `image/webp`, `application/pdf`.
|
||||
|
||||
#### Successful Response (`200 OK`):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"session_id": "ONB-8F12A9C4",
|
||||
"document_type": "lab_report",
|
||||
"summary": "Report indicates elevated HbA1c (7.2%) and stage 1 hypertension (130/85 mmHg).",
|
||||
"findings": [
|
||||
{
|
||||
"test": "HbA1c",
|
||||
"value": "7.2",
|
||||
"unit": "%",
|
||||
"interpretation": "Elevated"
|
||||
}
|
||||
],
|
||||
"medications_found": ["Metformin 500mg"],
|
||||
"allergies_found": ["Penicillin"],
|
||||
"message": "Medical document analyzed and integrated into onboarding session."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Error Handling & Status Codes
|
||||
|
||||
| HTTP Status Code | Meaning | Cause & Resolution |
|
||||
| :--- | :--- | :--- |
|
||||
| `400 Bad Request` | Bad Request | Invalid category or unsupported file format. |
|
||||
| `401 Unauthorized` | Unauthorized | Missing or invalid `Authorization: Bearer` or `X-API-Key` header. |
|
||||
| `403 Forbidden` | Forbidden (IDOR) | Authenticated `patient_id` does not match session owner. |
|
||||
| `404 Not Found` | Not Found | Session ID does not exist in backend database. |
|
||||
| `413 Payload Too Large` | File Too Large | Uploaded document exceeds 10MB limit. |
|
||||
| `422 Validation Error` | Pydantic Error | Request body violates schema rules (e.g. `age < 0`). |
|
||||
| `429 Too Many Requests` | Rate Limited | Exceeded rate limit bucket (30 req/min). Check `Retry-After` header. |
|
||||
| `500 Internal Error` | Server Error | Unhandled backend failure. |
|
||||
|
||||
---
|
||||
|
||||
## 10. Frontend React & TypeScript Implementation Examples
|
||||
|
||||
### React API Client (`src/api/onboardingClient.ts`)
|
||||
|
||||
```typescript
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
|
||||
|
||||
export interface PatientContext {
|
||||
id: string;
|
||||
name?: string;
|
||||
age?: number;
|
||||
gender?: string;
|
||||
phone?: string;
|
||||
abha_id?: string;
|
||||
}
|
||||
|
||||
export interface CategoryContext {
|
||||
type: 'general' | 'speciality';
|
||||
code: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
async function fetchApi<T>(endpoint: string, options: RequestInit = {}, patientId: string): Promise<T> {
|
||||
const token = `test_token_${patientId}`;
|
||||
const headers = new Headers(options.headers || {});
|
||||
headers.set('Authorization', `Bearer ${token}`);
|
||||
if (!(options.body instanceof FormData)) {
|
||||
headers.set('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}${endpoint}`, { ...options, headers });
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.json().catch(() => ({ detail: 'Network response error' }));
|
||||
throw new Error(errorBody.detail || `HTTP Error ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function startOnboarding(patient: PatientContext, category: CategoryContext, language: string = 'en') {
|
||||
return fetchApi<any>('/v1/onboarding/start', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ patient, category, language }),
|
||||
}, patient.id);
|
||||
}
|
||||
|
||||
export async function sendMessage(sessionId: string, patientId: string, message: string) {
|
||||
return fetchApi<any>(`/v1/onboarding/${sessionId}/message`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ message }),
|
||||
}, patientId);
|
||||
}
|
||||
|
||||
export async function completeOnboarding(sessionId: string, patientId: string, confirmed: boolean = true) {
|
||||
return fetchApi<any>(`/v1/onboarding/${sessionId}/complete`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ confirmed }),
|
||||
}, patientId);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Frontend Integration Checklist
|
||||
|
||||
```text
|
||||
[ ] Configure API_BASE_URL in environment (VITE_API_BASE_URL=http://localhost:8000)
|
||||
[ ] Verify CORS preflight request from localhost:3000 / localhost:5173
|
||||
[ ] Attach Authorization: Bearer test_token_<PATIENT_ID> header
|
||||
[ ] Call POST /v1/onboarding/start and store session_id
|
||||
[ ] Multi-turn chat message sending via POST /v1/onboarding/{session_id}/message
|
||||
[ ] Handle 401 Unauthorized & 403 Forbidden errors
|
||||
[ ] Upload medical documents via POST /v1/documents/{session_id}/upload (10MB max)
|
||||
[ ] Call POST /v1/onboarding/{session_id}/complete to retrieve final PatientOnboardingResult
|
||||
[ ] Test voice onboarding audio streaming via GET /v1/voice-onboarding/audio/{audio_id}
|
||||
```
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
# Authentication & Patient Security Specification
|
||||
|
||||
**Module**: API Security & Identity Management
|
||||
**Implementation Source**: [`app/core/security.py`](file:///c:/Users/Lenovo/New%20folder%20%282%29/ai-patient-onboarding/app/core/security.py)
|
||||
|
||||
---
|
||||
|
||||
## 1. Authentication Mechanisms
|
||||
|
||||
The API supports two authentication methods for frontend applications and client services:
|
||||
|
||||
### Method A: HTTP Bearer Token (Standard)
|
||||
Send the access token in the `Authorization` request header:
|
||||
```http
|
||||
Authorization: Bearer <ACCESS_TOKEN>
|
||||
```
|
||||
|
||||
### Method B: `X-API-Key` Custom Header
|
||||
Send the API key in the custom `X-API-Key` header:
|
||||
```http
|
||||
X-API-Key: <API_KEY>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Supported Access Tokens
|
||||
|
||||
### 1. Service Secret API Key
|
||||
For service-to-service integration or administrative access:
|
||||
- **Default Key**: `skyheal_secure_api_key_2026` (configurable via `API_SECRET_KEY` in server environment).
|
||||
|
||||
### 2. Patient Development / Testing Tokens
|
||||
For patient-specific web or mobile client authentication:
|
||||
- **Format**: `test_token_<PATIENT_ID>`
|
||||
- **Example**: `test_token_PAT-10023`
|
||||
|
||||
When `test_token_<PATIENT_ID>` is passed, the backend automatically establishes the patient context with `patient_id = "<PATIENT_ID>"`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Patient Isolation & IDOR Protection Rules
|
||||
|
||||
The backend enforces strict **Broken Object Level Authorization (IDOR) Protection**:
|
||||
1. When starting an onboarding session (`/v1/onboarding/start` or `/v1/voice-onboarding/start`), the `patient.id` provided in the JSON body is bound as the session owner (`session.patient_id`).
|
||||
2. Subsequent requests to `/message`, `/turn`, `/upload`, or `/complete` verify that the caller's authenticated `patient_id` matches the session owner.
|
||||
3. If Client B attempts to send a message to Client A's session, the server immediately returns **`HTTP 403 Forbidden`**:
|
||||
```json
|
||||
{
|
||||
"detail": "Forbidden: You do not have authorization to access or modify this patient onboarding session."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Authentication Error Responses
|
||||
|
||||
### Missing Credentials (HTTP 401)
|
||||
Triggered when no `Authorization` or `X-API-Key` header is present:
|
||||
```http
|
||||
HTTP/1.1 401 Unauthorized
|
||||
WWW-Authenticate: Bearer
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"detail": "Authentication credentials required. Please provide X-API-Key or Bearer token."
|
||||
}
|
||||
```
|
||||
|
||||
### Invalid Token (HTTP 401)
|
||||
Triggered when an invalid token string is provided:
|
||||
```http
|
||||
HTTP/1.1 401 Unauthorized
|
||||
WWW-Authenticate: Bearer
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"detail": "Invalid authentication token or API key."
|
||||
}
|
||||
```
|
||||
@@ -1,58 +0,0 @@
|
||||
# Error Handling & SLA Reference
|
||||
|
||||
**Module**: API Error Contracts & Status Codes
|
||||
|
||||
---
|
||||
|
||||
## 1. Standard Error Payload Format
|
||||
|
||||
All HTTP error responses (`4xx` and `5xx`) follow FastAPI JSON error contracts:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Descriptive human-readable error message explaining why the request failed."
|
||||
}
|
||||
```
|
||||
|
||||
### Unhandled Internal Server Errors (HTTP 500)
|
||||
For unhandled exceptions, the global exception handler masks Python tracebacks and returns a sanitized JSON payload:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "INTERNAL_SERVER_ERROR",
|
||||
"message": "An unexpected internal server error occurred. Please contact system administration."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Master Error Status Code Guide
|
||||
|
||||
| HTTP Status Code | Name | Cause | Frontend Action |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `400 Bad Request` | Bad Request | Invalid category, unparseable JSON, or disallowed document format (e.g. `.exe`). | Validate input parameters before sending request. |
|
||||
| `401 Unauthorized` | Unauthorized | Missing or invalid `Authorization: Bearer` or `X-API-Key` header. | Redirect to login or attach valid patient token `test_token_<PATIENT_ID>`. |
|
||||
| `403 Forbidden` | Forbidden (IDOR) | Authenticated `patient_id` does not match session owner. | Ensure patient accesses only their own session. |
|
||||
| `404 Not Found` | Not Found | Session ID does not exist in backend database or audio ID expired. | Start a fresh onboarding session via `/v1/onboarding/start`. |
|
||||
| `413 Payload Too Large` | File Too Large | Uploaded medical document exceeds 10MB (`10,485,760 bytes`). | Compress or resize file before uploading. |
|
||||
| `422 Validation Error` | Pydantic Error | Request body violates schema rules (e.g. `age < 0` or missing required field). | Display field validation message to user. |
|
||||
| `429 Too Many Requests` | Rate Limited | Exceeded rate limit bucket (30 req/min for onboarding; 10 req/min for uploads). | Respect `Retry-After: <seconds>` response header and back off. |
|
||||
| `500 Internal Error` | Server Error | Unhandled backend logic or database failure. | Show user retry alert and report issue. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Rate Limit Headers
|
||||
|
||||
When `HTTP 429` is returned, inspect the response header:
|
||||
```http
|
||||
HTTP/1.1 429 Too Many Requests
|
||||
Retry-After: 59
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"detail": "Rate limit exceeded (30 requests per 60s). Please try again later."
|
||||
}
|
||||
```
|
||||
`Retry-After` specifies the exact number of seconds the frontend should pause before retrying.
|
||||
@@ -1,255 +0,0 @@
|
||||
# Frontend Developer Integration Examples (React & TypeScript)
|
||||
|
||||
**Language**: TypeScript / JavaScript (React 18+)
|
||||
|
||||
---
|
||||
|
||||
## 1. Complete API Client (`src/api/client.ts`)
|
||||
|
||||
```typescript
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
|
||||
|
||||
export interface PatientContext {
|
||||
id: string;
|
||||
name?: string;
|
||||
age?: number;
|
||||
gender?: string;
|
||||
phone?: string;
|
||||
abha_id?: string;
|
||||
insurance_scheme?: string;
|
||||
dietary_habits?: string;
|
||||
}
|
||||
|
||||
export interface CategoryContext {
|
||||
type: 'general' | 'speciality';
|
||||
code: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface StartOnboardingResponse {
|
||||
success: boolean;
|
||||
session_id: string;
|
||||
patient_id: string;
|
||||
category: CategoryContext;
|
||||
language: string;
|
||||
status: string;
|
||||
stage: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface SendMessageResponse {
|
||||
success: boolean;
|
||||
session_id: string;
|
||||
stage: string;
|
||||
message: string;
|
||||
status: string;
|
||||
response_time_ms?: number;
|
||||
response_time_sec?: number;
|
||||
}
|
||||
|
||||
export interface CompleteOnboardingResponse {
|
||||
success: boolean;
|
||||
session_id: string;
|
||||
status: string;
|
||||
result?: any;
|
||||
message: string;
|
||||
response_time_ms?: number;
|
||||
response_time_sec?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic Fetch Wrapper with Authorization & Error Handling
|
||||
*/
|
||||
async function fetchApi<T>(endpoint: string, options: RequestInit = {}, patientId: string): Promise<T> {
|
||||
const token = `test_token_${patientId}`;
|
||||
const headers = new Headers(options.headers || {});
|
||||
headers.set('Authorization', `Bearer ${token}`);
|
||||
if (!(options.body instanceof FormData)) {
|
||||
headers.set('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
||||
...options,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.json().catch(() => ({ detail: 'Network response error' }));
|
||||
throw new Error(errorBody.detail || `HTTP Error ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Text Onboarding Session
|
||||
*/
|
||||
export async function startOnboarding(
|
||||
patient: PatientContext,
|
||||
category: CategoryContext,
|
||||
language: string = 'en'
|
||||
): Promise<StartOnboardingResponse> {
|
||||
return fetchApi<StartOnboardingResponse>(
|
||||
'/v1/onboarding/start',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ patient, category, language }),
|
||||
},
|
||||
patient.id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Intake Chat Message
|
||||
*/
|
||||
export async function sendMessage(
|
||||
sessionId: string,
|
||||
patientId: string,
|
||||
message: string
|
||||
): Promise<SendMessageResponse> {
|
||||
return fetchApi<SendMessageResponse>(
|
||||
`/v1/onboarding/${sessionId}/message`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ message }),
|
||||
},
|
||||
patientId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload Medical Document (Report / Prescription)
|
||||
*/
|
||||
export async function uploadDocument(
|
||||
sessionId: string,
|
||||
patientId: string,
|
||||
file: File
|
||||
): Promise<any> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
return fetchApi<any>(
|
||||
`/v1/documents/${sessionId}/upload`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
},
|
||||
patientId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize Session & Extract Structured EHR
|
||||
*/
|
||||
export async function completeOnboarding(
|
||||
sessionId: string,
|
||||
patientId: string,
|
||||
confirmed: boolean = true
|
||||
): Promise<CompleteOnboardingResponse> {
|
||||
return fetchApi<CompleteOnboardingResponse>(
|
||||
`/v1/onboarding/${sessionId}/complete`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ confirmed }),
|
||||
},
|
||||
patientId
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Voice Onboarding Hook (`src/hooks/useVoiceOnboarding.ts`)
|
||||
|
||||
```typescript
|
||||
import { useState, useRef } from 'react';
|
||||
|
||||
export function useVoiceOnboarding(patientId: string) {
|
||||
const [sessionId, setSessionId] = useState<string | null>(null);
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [isPlayingAudio, setIsPlayingAudio] = useState(false);
|
||||
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
|
||||
const audioChunksRef = useRef<Blob[]>([]);
|
||||
|
||||
const startVoiceSession = async (patient: any, language: string = 'en-IN') => {
|
||||
const res = await fetch('http://localhost:8000/v1/voice-onboarding/start', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer test_token_${patientId}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
patient,
|
||||
category: { type: 'general', code: 'general_medicine', name: 'General Medicine' },
|
||||
language,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
setSessionId(data.session_id);
|
||||
if (data.audio_url) {
|
||||
playAudioResponse(data.audio_url);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
const playAudioResponse = (audioUrl: string) => {
|
||||
setIsPlayingAudio(true);
|
||||
const audio = new Audio(`http://localhost:8000${audioUrl}`);
|
||||
audio.onended = () => setIsPlayingAudio(false);
|
||||
audio.onerror = () => setIsPlayingAudio(false);
|
||||
audio.play();
|
||||
};
|
||||
|
||||
const startRecording = async () => {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
const mediaRecorder = new MediaRecorder(stream);
|
||||
mediaRecorderRef.current = mediaRecorder;
|
||||
audioChunksRef.current = [];
|
||||
|
||||
mediaRecorder.ondataavailable = (event) => {
|
||||
if (event.data.size > 0) audioChunksRef.current.push(event.data);
|
||||
};
|
||||
|
||||
mediaRecorder.start();
|
||||
setIsRecording(true);
|
||||
};
|
||||
|
||||
const stopRecordingAndSend = async () => {
|
||||
if (!mediaRecorderRef.current || !sessionId) return;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
mediaRecorderRef.current!.onstop = async () => {
|
||||
setIsRecording(false);
|
||||
const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/wav' });
|
||||
const formData = new FormData();
|
||||
formData.append('audio', audioBlob, 'speech.wav');
|
||||
|
||||
const res = await fetch(`http://localhost:8000/v1/voice-onboarding/${sessionId}/turn`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer test_token_${patientId}`,
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const turnData = await res.json();
|
||||
if (turnData.audio_url) {
|
||||
playAudioResponse(turnData.audio_url);
|
||||
}
|
||||
resolve(turnData);
|
||||
};
|
||||
|
||||
mediaRecorderRef.current!.stop();
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
sessionId,
|
||||
isRecording,
|
||||
isPlayingAudio,
|
||||
startVoiceSession,
|
||||
startRecording,
|
||||
stopRecordingAndSend,
|
||||
};
|
||||
}
|
||||
```
|
||||
@@ -1,194 +0,0 @@
|
||||
# Text Onboarding & Document API Reference
|
||||
|
||||
**Implementation Sources**: [`app/api/onboarding.py`](file:///c:/Users/Lenovo/New%20folder%20%282%29/ai-patient-onboarding/app/api/onboarding.py), [`app/api/documents.py`](file:///c:/Users/Lenovo/New%20folder%20%282%29/ai-patient-onboarding/app/api/documents.py)
|
||||
|
||||
---
|
||||
|
||||
## 1. POST `/v1/onboarding/start`
|
||||
|
||||
Starts a new text-based clinical onboarding intake session.
|
||||
|
||||
### Request Headers:
|
||||
```http
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer test_token_PAT-10023
|
||||
```
|
||||
|
||||
### Request Body (`StartOnboardingRequest`):
|
||||
```json
|
||||
{
|
||||
"patient": {
|
||||
"id": "PAT-10023",
|
||||
"name": "Kavitha Ramesh",
|
||||
"age": 35,
|
||||
"gender": "female",
|
||||
"phone": "+919876543210",
|
||||
"abha_id": "91-1234-5678-9012",
|
||||
"insurance_scheme": "PMJAY",
|
||||
"dietary_habits": "Vegetarian"
|
||||
},
|
||||
"category": {
|
||||
"type": "general",
|
||||
"code": "general_medicine",
|
||||
"name": "General Medicine"
|
||||
},
|
||||
"language": "en"
|
||||
}
|
||||
```
|
||||
|
||||
### Field Descriptions:
|
||||
|
||||
| Field | Type | Required | Validation Rules | Description |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `patient.id` | string | **Yes** | Min length 1 | Unique patient identifier |
|
||||
| `patient.name` | string | No | Optional | Full name of patient |
|
||||
| `patient.age` | integer | No | $0 \le \text{age} \le 120$ | Patient age in years |
|
||||
| `patient.gender` | string | No | Optional | Gender identity ("male", "female", "other") |
|
||||
| `category.type` | string | **Yes** | `"general"` or `"speciality"` | Onboarding workflow type |
|
||||
| `category.code` | string | **Yes** | Min length 1 | Specialty classification code |
|
||||
| `category.name` | string | **Yes** | Min length 1 | Human-readable category display name |
|
||||
| `language` | string | No | Default `"en"` | Preferred intake language |
|
||||
|
||||
### Successful Response (`200 OK` - `StartOnboardingResponse`):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"session_id": "ONB-8F12A9C4",
|
||||
"patient_id": "PAT-10023",
|
||||
"category": {
|
||||
"type": "general",
|
||||
"code": "general_medicine",
|
||||
"name": "General Medicine"
|
||||
},
|
||||
"language": "en",
|
||||
"status": "in_progress",
|
||||
"stage": "symptoms",
|
||||
"message": "Hello Kavitha Ramesh! I am your clinical onboarding assistant. What symptoms are you experiencing today?"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. POST `/v1/onboarding/{session_id}/message`
|
||||
|
||||
Sends a patient chat response to the AI intake orchestrator.
|
||||
|
||||
### Request Headers:
|
||||
```http
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer test_token_PAT-10023
|
||||
```
|
||||
|
||||
### Request Body (`SendMessageRequest`):
|
||||
```json
|
||||
{
|
||||
"message": "I have had severe chest tightness and shortness of breath for 2 days."
|
||||
}
|
||||
```
|
||||
|
||||
### Successful Response (`200 OK` - `SendMessageResponse`):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"session_id": "ONB-8F12A9C4",
|
||||
"stage": "medical_history",
|
||||
"message": "Thank you for sharing. Does the chest tightness radiate to your arm or jaw, and do you have any history of high blood pressure?",
|
||||
"status": "in_progress",
|
||||
"response_time_ms": 1529.74,
|
||||
"response_time_sec": 1.53
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. POST `/v1/documents/{session_id}/upload`
|
||||
|
||||
Uploads a medical document (lab report, prescription, discharge summary) for vision extraction.
|
||||
|
||||
### Request Headers:
|
||||
```http
|
||||
Content-Type: multipart/form-data
|
||||
Authorization: Bearer test_token_PAT-10023
|
||||
```
|
||||
|
||||
### Form Data Parameters:
|
||||
- `file`: Binary file upload (**Allowed formats**: `image/jpeg`, `image/png`, `image/webp`, `application/pdf`). **Maximum size**: `10MB` (`10,485,760 bytes`).
|
||||
|
||||
### Successful Response (`200 OK` - `DocumentExtractionResponse`):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"session_id": "ONB-8F12A9C4",
|
||||
"document_type": "lab_report",
|
||||
"summary": "Report indicates elevated HbA1c (7.2%) and stage 1 hypertension (130/85 mmHg).",
|
||||
"findings": [
|
||||
{
|
||||
"test": "HbA1c",
|
||||
"value": "7.2",
|
||||
"unit": "%",
|
||||
"interpretation": "Elevated"
|
||||
},
|
||||
{
|
||||
"test": "Blood Pressure",
|
||||
"value": "130/85",
|
||||
"unit": "mmHg",
|
||||
"interpretation": "Stage 1"
|
||||
}
|
||||
],
|
||||
"medications_found": ["Metformin 500mg"],
|
||||
"allergies_found": ["Penicillin"],
|
||||
"message": "Medical document analyzed and integrated into onboarding session."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. POST `/v1/onboarding/{session_id}/complete`
|
||||
|
||||
Finalizes the intake session and outputs the structured clinical EHR payload.
|
||||
|
||||
### Request Body (`CompleteOnboardingRequest`):
|
||||
```json
|
||||
{
|
||||
"confirmed": true
|
||||
}
|
||||
```
|
||||
|
||||
### Successful Response (`200 OK` - `CompleteOnboardingResponse`):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"session_id": "ONB-8F12A9C4",
|
||||
"status": "completed",
|
||||
"result": {
|
||||
"patient_id": "PAT-10023",
|
||||
"abha_id": "91-1234-5678-9012",
|
||||
"phone": "+919876543210",
|
||||
"insurance_scheme": "PMJAY",
|
||||
"category": {
|
||||
"type": "general",
|
||||
"code": "general_medicine",
|
||||
"name": "General Medicine"
|
||||
},
|
||||
"chief_complaint": "Chest tightness and shortness of breath for 2 days",
|
||||
"symptoms": [
|
||||
{
|
||||
"name": "Chest tightness",
|
||||
"duration": "2 days",
|
||||
"severity": "severe",
|
||||
"description": "Triggered during exertion"
|
||||
}
|
||||
],
|
||||
"medical_history": ["Prehypertension"],
|
||||
"medications": ["Metformin 500mg"],
|
||||
"allergies": ["Penicillin"],
|
||||
"dietary_habits": "Vegetarian",
|
||||
"additional_information": "Patient confirmed details.",
|
||||
"language": "en",
|
||||
"onboarding_completed": true
|
||||
},
|
||||
"message": "Onboarding successfully completed and saved to patient record.",
|
||||
"response_time_ms": 4075.41,
|
||||
"response_time_sec": 4.08
|
||||
}
|
||||
```
|
||||
@@ -1,149 +0,0 @@
|
||||
# Voice Onboarding & Audio API Reference
|
||||
|
||||
**Implementation Sources**: [`app/api/voice_onboarding.py`](file:///c:/Users/Lenovo/New%20folder%20%282%29/ai-patient-onboarding/app/api/voice_onboarding.py), [`app/services/voice_service.py`](file:///c:/Users/Lenovo/New%20folder%20%282%29/ai-patient-onboarding/app/services/voice_service.py)
|
||||
|
||||
---
|
||||
|
||||
## 1. POST `/v1/voice-onboarding/start`
|
||||
|
||||
Starts a voice-first onboarding session with Sarvam AI multilingual speech engine.
|
||||
|
||||
### Request Body (`StartVoiceOnboardingRequest`):
|
||||
```json
|
||||
{
|
||||
"patient": {
|
||||
"id": "PAT-VOICE-1",
|
||||
"name": "Ramesh G",
|
||||
"age": 34,
|
||||
"gender": "male"
|
||||
},
|
||||
"category": {
|
||||
"type": "general",
|
||||
"code": "general_medicine",
|
||||
"name": "General Medicine"
|
||||
},
|
||||
"language": "ta-IN"
|
||||
}
|
||||
```
|
||||
|
||||
### Supported Language Codes:
|
||||
- `en-IN` (Indian English)
|
||||
- `ta-IN` (Tamil)
|
||||
- `hi-IN` (Hindi)
|
||||
- `te-IN` (Telugu)
|
||||
- `kn-IN` (Kannada)
|
||||
- `ml-IN` (Malayalam)
|
||||
- `mr-IN` (Marathi)
|
||||
|
||||
### Successful Response (`200 OK` - `StartVoiceOnboardingResponse`):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"session_id": "VOICE-D3CAEA5C",
|
||||
"patient_id": "PAT-VOICE-1",
|
||||
"category": {
|
||||
"type": "general",
|
||||
"code": "general_medicine",
|
||||
"name": "General Medicine"
|
||||
},
|
||||
"language": "ta-IN",
|
||||
"status": "in_progress",
|
||||
"stage": "greeting",
|
||||
"greeting_text": "வணக்கம் Ramesh G, CureSelect மருத்துவமனைக்கு வரவேற்கிறோம். இன்று உங்களுக்கு என்ன சுகவீனம் உள்ளது?",
|
||||
"audio_url": "/v1/voice-onboarding/audio/aud_a1b2c3d4e5f6",
|
||||
"response_time_ms": 78.32,
|
||||
"response_time_sec": 0.08
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. POST `/v1/voice-onboarding/{session_id}/turn`
|
||||
|
||||
Processes a conversational voice turn using audio recording or text fallback.
|
||||
|
||||
### Request Headers:
|
||||
```http
|
||||
Content-Type: multipart/form-data
|
||||
Authorization: Bearer test_token_PAT-VOICE-1
|
||||
```
|
||||
|
||||
### Multipart Form Data Parameters:
|
||||
- `audio`: Binary audio recording file (`WAV`, `WEBM`, `MP3`, `OGG` up to 10MB).
|
||||
- `text_fallback`: String text transcript (used if WebSpeech STT processed client-side or audio is unprovided).
|
||||
|
||||
*Note: At least one of `audio` or `text_fallback` must be provided.*
|
||||
|
||||
### Successful Response (`200 OK` - `VoiceTurnResponse`):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"session_id": "VOICE-D3CAEA5C",
|
||||
"patient_transcript": "எனக்கு கடந்த 3 நாளா நெஞ்சு பாரமா இருக்குங்க.",
|
||||
"ai_text": "நன்றி. இந்த நெஞ்சு பாரம் உடற்பயிற்சி செய்யும் போது அதிகமாகிறதா?",
|
||||
"stage": "symptoms",
|
||||
"completed": false,
|
||||
"audio_url": "/v1/voice-onboarding/audio/aud_f9e8d7c6b5a4",
|
||||
"audio_base64": null,
|
||||
"suggested_specialty": {
|
||||
"specialty": "Cardiology",
|
||||
"reason": "Chest tightness reported"
|
||||
},
|
||||
"response_time_ms": 1412.37,
|
||||
"response_time_sec": 1.41
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. GET `/v1/voice-onboarding/audio/{audio_id}`
|
||||
|
||||
Streams generated TTS audio for browser or mobile playback.
|
||||
|
||||
### Request Headers:
|
||||
No authentication required for audio file streaming.
|
||||
|
||||
### Response Headers:
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: audio/wav (or audio/mpeg)
|
||||
Accept-Ranges: bytes
|
||||
Content-Length: 48290
|
||||
```
|
||||
|
||||
### Frontend Audio Playback snippet:
|
||||
```javascript
|
||||
const audio = new Audio(`${BASE_URL}/v1/voice-onboarding/audio/${audioId}`);
|
||||
await audio.play();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. POST `/v1/voice-onboarding/{session_id}/complete`
|
||||
|
||||
Finalizes a voice onboarding session.
|
||||
|
||||
### Request Body:
|
||||
```json
|
||||
{
|
||||
"confirmed": true
|
||||
}
|
||||
```
|
||||
|
||||
### Successful Response (`200 OK`):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"session_id": "VOICE-D3CAEA5C",
|
||||
"status": "completed",
|
||||
"result": {
|
||||
"patient_id": "PAT-VOICE-1",
|
||||
"chief_complaint": "Chest tightness for 3 days",
|
||||
"language": "ta-IN",
|
||||
"onboarding_completed": true
|
||||
},
|
||||
"message": "Voice onboarding successfully saved to patient record.",
|
||||
"response_time_ms": 4121.79,
|
||||
"response_time_sec": 4.12
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user