Intake Summary Webhook
Overview
Section titled “Overview”The Intake Summary Webhook enables your healthcare facility to automatically receive AI-generated patient summaries from HAMI as patients complete their intake sessions. HAMI will POST structured summary data to your designated endpoint in real-time, ensuring your system always has the latest patient information without requiring manual data retrieval.
This push-based integration provides real-time updates and progressive enhancement as patients interact with HAMI multiple times.
Authentication
Section titled “Authentication”Your endpoint must validate incoming requests using API key authentication in the request headers.
X-API-Key: your_api_key_hereEndpoint Requirements
Section titled “Endpoint Requirements”Your Endpoint URL: https://your-facility-api.com/api/ai-summary
Method: POST
Content-Type: application/json
Requirements:
- Must be publicly accessible via HTTPS
- Must validate the
X-API-Keyheader - Should respond within 5 seconds to avoid timeouts
- Must handle idempotent requests (duplicate postings may occur)
Request Schema
Section titled “Request Schema”Headers
Section titled “Headers”| Header | Type | Required | Description |
|---|---|---|---|
X-API-Key | string | Yes | Your facility’s API authentication key (provided to HAMI) |
Content-Type | string | Yes | Always application/json |
Request Body
Section titled “Request Body”HAMI will POST a JSON payload containing appointment information, patient demographics, and the AI-generated intake summary.
Payload Structure
Section titled “Payload Structure”{ appointmentId: string; // Your facility's appointment identifier dateCreated: string; // ISO 8601 timestamp when summary was generated intakeSummary: { "Patient Information": { "Date of Birth/Age": string; "Gender": string; }; "Chief Complaint": string; "History of Present Illness": { "Onset and Duration": string; "Associated Symptoms": string; "Aggravating and Alleviating Factors": string; "Severity of Symptoms": string; }; "Summary": string; // Comprehensive clinical narrative "Past Medical History": { "Chronic Illnesses": string; "Previous Hospitalizations": string; "Surgeries": string; "Allergies": string; }; "Family History": string; "Social History": { "Social Support": string; "Occupation": string; "Lifestyle Factors": string; "Exercise and Diet": string; }; "Medications": { "Current Medications": Array<{ "Name": string; "Brand Name": string; "Generic Name": string; "Dosage": string; "Frequency": string; "Formulation": string; "Route": string; "Duration": string; }>; }; "Review of Systems": { "General": string; "Cardiovascular": string; "Respiratory": string; "Gastrointestinal": string; "Genitourinary": string; "Musculoskeletal": string; "Neurological": string; "Mental Health Screening": string; "Psychiatric": string; "Endocrine": string; "Dermatological": string; }; };}Example Payload
Section titled “Example Payload”{ "appointmentId": "2308", "dateCreated": "2025-09-22T07:18:55.914786Z", "intakeSummary": { "Patient Information": { "Date of Birth/Age": "18", "Gender": "male" }, "Chief Complaint": "flu, fever, cold", "History of Present Illness": { "Onset and Duration": "symptoms started 3 days ago, fever started 2 days ago", "Associated Symptoms": "cold", "Aggravating and Alleviating Factors": "Unremarkable", "Severity of Symptoms": "fever at 102 Celsius" }, "Summary": "The patient is an 18-year-old male with no known comorbidities or allergies.\n\nThe patient presents with a fever, flu, and cold, reporting a temperature of 102 degrees Celsius. Symptoms began three days ago, with the fever starting two days ago.\n\nThe patient has no known allergies, no comorbidities such as diabetes or high blood pressure, and has no history of surgeries.\n\nThe patient is currently taking Panadol Extra, 30 mg, twice a day for the fever and cold symptoms. He is not taking any other medications.\n\nThere is no available information on the patient's family history, social history, lifestyle, diet, habits, or addictions.\n\nReview of systems is unremarkable.", "Past Medical History": { "Chronic Illnesses": "Unremarkable", "Previous Hospitalizations": "Unremarkable", "Surgeries": "No surgeries", "Allergies": "No known allergies" }, "Family History": "Not asked", "Social History": { "Social Support": "Not asked", "Occupation": "Not asked", "Lifestyle Factors": "Not asked", "Exercise and Diet": "Not asked" }, "Medications": { "Current Medications": [ { "Name": "Panadol Extra", "Brand Name": "Panadol Extra", "Generic Name": "", "Dosage": "30mg", "Frequency": "Twice a day", "Formulation": "tablet", "Route": "oral", "Duration": "since symptoms started" } ] }, "Review of Systems": { "General": "fever, cold", "Cardiovascular": "Not asked", "Respiratory": "Not asked", "Gastrointestinal": "Not asked", "Genitourinary": "Not asked", "Musculoskeletal": "Not asked", "Neurological": "Not asked", "Mental Health Screening": "Score not applicable", "Psychiatric": "Not asked", "Endocrine": "Not asked", "Dermatological": "Not asked" } }}Response Schema
Section titled “Response Schema”Your endpoint should return appropriate HTTP status codes to indicate the result of processing.
Success Response (200 OK)
Section titled “Success Response (200 OK)”Return 200 OK when the summary is successfully received and processed.
{ "success": true, "message": "Summary received and processed successfully"}Error Responses
Section titled “Error Responses”| Status Code | Description | When to Return |
|---|---|---|
200 OK | Success | Summary successfully received and processed |
400 Bad Request | Invalid payload | Payload format is invalid or missing required fields |
401 Unauthorized | Authentication failed | Invalid or missing X-API-Key header |
422 Unprocessable Entity | Validation failed | Payload is valid JSON but fails business validation |
500 Internal Server Error | Processing error | Internal error occurred while processing the summary |
Implementation Examples
Section titled “Implementation Examples”import express from 'express';
const app = express();app.use(express.json());
// Middleware to validate API keyfunction validateApiKey(req, res, next) { const apiKey = req.headers['x-api-key']; const validApiKey = process.env.HAMI_API_KEY;
if (!apiKey || apiKey !== validApiKey) { return res.status(401).json({ success: false, message: 'Invalid or missing API key' }); }
next();}
// Webhook endpointapp.post('/api/ai-summary', validateApiKey, async (req, res) => { try { const { appointmentId, dateCreated, intakeSummary } = req.body;
// Validate required fields if (!appointmentId || !intakeSummary) { return res.status(400).json({ success: false, message: 'Missing required fields: appointmentId or intakeSummary' }); }
// Find the appointment in your system const appointment = await findAppointmentById(appointmentId);
if (!appointment) { return res.status(422).json({ success: false, message: `Appointment ${appointmentId} not found` }); }
// Store the intake summary await storeIntakeSummary({ appointmentId, dateCreated, summary: intakeSummary, receivedAt: new Date() });
// Update appointment record await updateAppointmentWithSummary(appointmentId, intakeSummary);
// Log the successful reception console.log(`Intake summary received for appointment ${appointmentId}`);
res.status(200).json({ success: true, message: 'Summary received and processed successfully' });
} catch (error) { console.error('Error processing intake summary:', error); res.status(500).json({ success: false, message: 'Internal server error while processing summary' }); }});
// Helper functions (implement based on your data layer)async function findAppointmentById(appointmentId) { // Query your database for the appointment // Return appointment object or null}
async function storeIntakeSummary(summaryData) { // Store the summary in your database}
async function updateAppointmentWithSummary(appointmentId, summary) { // Update the appointment record with summary information}
const PORT = process.env.PORT || 3000;app.listen(PORT, () => { console.log(`Webhook server listening on port ${PORT}`);});from flask import Flask, request, jsonifyfrom datetime import datetimeimport osimport logging
app = Flask(__name__)logging.basicConfig(level=logging.INFO)
# API Key validationHAMI_API_KEY = os.environ.get('HAMI_API_KEY')
def validate_api_key(): """Validate the API key from request headers.""" api_key = request.headers.get('X-API-Key')
if not api_key or api_key != HAMI_API_KEY: return False return True
@app.route('/api/ai-summary', methods=['POST'])def receive_intake_summary(): """Endpoint to receive AI-generated intake summaries from HAMI."""
# Validate API key if not validate_api_key(): return jsonify({ 'success': False, 'message': 'Invalid or missing API key' }), 401
try: # Parse request body data = request.get_json()
# Validate required fields if not data.get('appointmentId') or not data.get('intakeSummary'): return jsonify({ 'success': False, 'message': 'Missing required fields: appointmentId or intakeSummary' }), 400
appointment_id = data['appointmentId'] date_created = data.get('dateCreated') intake_summary = data['intakeSummary']
# Find the appointment in your system appointment = find_appointment_by_id(appointment_id)
if not appointment: return jsonify({ 'success': False, 'message': f'Appointment {appointment_id} not found' }), 422
# Store the intake summary store_intake_summary({ 'appointment_id': appointment_id, 'date_created': date_created, 'summary': intake_summary, 'received_at': datetime.utcnow().isoformat() })
# Update appointment record update_appointment_with_summary(appointment_id, intake_summary)
# Log the successful reception logging.info(f'Intake summary received for appointment {appointment_id}')
return jsonify({ 'success': True, 'message': 'Summary received and processed successfully' }), 200
except Exception as e: logging.error(f'Error processing intake summary: {str(e)}') return jsonify({ 'success': False, 'message': 'Internal server error while processing summary' }), 500
# Helper functions (implement based on your data layer)def find_appointment_by_id(appointment_id: str): """Query your database for the appointment.""" # Implement your database query logic pass
def store_intake_summary(summary_data: dict): """Store the summary in your database.""" # Implement your database storage logic pass
def update_appointment_with_summary(appointment_id: str, summary: dict): """Update the appointment record with summary information.""" # Implement your database update logic pass
if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)from fastapi import FastAPI, Request, HTTPException, Headerfrom pydantic import BaseModel, Fieldfrom typing import List, Optionalfrom datetime import datetimeimport osimport logging
app = FastAPI(title="HAMI Intake Summary Webhook")logging.basicConfig(level=logging.INFO)
# API Key from environmentHAMI_API_KEY = os.environ.get('HAMI_API_KEY')
# Pydantic models for request validationclass PatientInformation(BaseModel): date_of_birth_age: str = Field(alias="Date of Birth/Age") gender: str = Field(alias="Gender")
class HistoryOfPresentIllness(BaseModel): onset_and_duration: str = Field(alias="Onset and Duration") associated_symptoms: str = Field(alias="Associated Symptoms") aggravating_and_alleviating_factors: str = Field( alias="Aggravating and Alleviating Factors" ) severity_of_symptoms: str = Field(alias="Severity of Symptoms")
class PastMedicalHistory(BaseModel): chronic_illnesses: str = Field(alias="Chronic Illnesses") previous_hospitalizations: str = Field(alias="Previous Hospitalizations") surgeries: str = Field(alias="Surgeries") allergies: str = Field(alias="Allergies")
class SocialHistory(BaseModel): social_support: str = Field(alias="Social Support") occupation: str = Field(alias="Occupation") lifestyle_factors: str = Field(alias="Lifestyle Factors") exercise_and_diet: str = Field(alias="Exercise and Diet")
class Medication(BaseModel): name: str = Field(alias="Name") brand_name: str = Field(alias="Brand Name") generic_name: str = Field(alias="Generic Name") dosage: str = Field(alias="Dosage") frequency: str = Field(alias="Frequency") formulation: str = Field(alias="Formulation") route: str = Field(alias="Route") duration: str = Field(alias="Duration")
class Medications(BaseModel): current_medications: List[Medication] = Field(alias="Current Medications")
class ReviewOfSystems(BaseModel): general: str = Field(alias="General") cardiovascular: str = Field(alias="Cardiovascular") respiratory: str = Field(alias="Respiratory") gastrointestinal: str = Field(alias="Gastrointestinal") genitourinary: str = Field(alias="Genitourinary") musculoskeletal: str = Field(alias="Musculoskeletal") neurological: str = Field(alias="Neurological") mental_health_screening: str = Field(alias="Mental Health Screening") psychiatric: str = Field(alias="Psychiatric") endocrine: str = Field(alias="Endocrine") dermatological: str = Field(alias="Dermatological")
class IntakeSummary(BaseModel): patient_information: PatientInformation = Field(alias="Patient Information") chief_complaint: str = Field(alias="Chief Complaint") history_of_present_illness: HistoryOfPresentIllness = Field( alias="History of Present Illness" ) summary: str = Field(alias="Summary") past_medical_history: PastMedicalHistory = Field(alias="Past Medical History") family_history: str = Field(alias="Family History") social_history: SocialHistory = Field(alias="Social History") medications: Medications = Field(alias="Medications") review_of_systems: ReviewOfSystems = Field(alias="Review of Systems")
class Config: populate_by_name = True
class IntakeSummaryPayload(BaseModel): appointment_id: str = Field(alias="appointmentId") date_created: str = Field(alias="dateCreated") intake_summary: IntakeSummary = Field(alias="intakeSummary")
class Config: populate_by_name = True
class SuccessResponse(BaseModel): success: bool message: str
class ErrorResponse(BaseModel): success: bool message: str
# Dependency for API key validationasync def validate_api_key(x_api_key: Optional[str] = Header(None)): """Validate the API key from request headers.""" if not x_api_key or x_api_key != HAMI_API_KEY: raise HTTPException( status_code=401, detail="Invalid or missing API key" ) return x_api_key
@app.post( "/api/ai-summary", response_model=SuccessResponse, responses={ 400: {"model": ErrorResponse}, 401: {"model": ErrorResponse}, 422: {"model": ErrorResponse}, 500: {"model": ErrorResponse} })async def receive_intake_summary( payload: IntakeSummaryPayload, api_key: str = Header(None, alias="X-API-Key")): """ Receive AI-generated intake summaries from HAMI.
This endpoint receives structured patient intake summaries and stores them in your system, associating them with the appropriate appointment. """
# Validate API key if not api_key or api_key != HAMI_API_KEY: raise HTTPException( status_code=401, detail="Invalid or missing API key" )
try: # Find the appointment in your system appointment = await find_appointment_by_id(payload.appointment_id)
if not appointment: raise HTTPException( status_code=422, detail=f"Appointment {payload.appointment_id} not found" )
# Store the intake summary await store_intake_summary({ 'appointment_id': payload.appointment_id, 'date_created': payload.date_created, 'summary': payload.intake_summary.model_dump(by_alias=True), 'received_at': datetime.utcnow().isoformat() })
# Update appointment record await update_appointment_with_summary( payload.appointment_id, payload.intake_summary )
# Log the successful reception logging.info( f'Intake summary received for appointment {payload.appointment_id}' )
return SuccessResponse( success=True, message='Summary received and processed successfully' )
except HTTPException: raise except Exception as e: logging.error(f'Error processing intake summary: {str(e)}') raise HTTPException( status_code=500, detail='Internal server error while processing summary' )
# Helper functions (implement based on your data layer)async def find_appointment_by_id(appointment_id: str): """Query your database for the appointment.""" # Implement your database query logic pass
async def store_intake_summary(summary_data: dict): """Store the summary in your database.""" # Implement your database storage logic pass
async def update_appointment_with_summary( appointment_id: str, summary: IntakeSummary): """Update the appointment record with summary information.""" # Implement your database update logic passusing Microsoft.AspNetCore.Mvc;using System.Text.Json.Serialization;
namespace YourNamespace.Controllers{ [ApiController] [Route("api/[controller]")] public class AiSummaryController : ControllerBase { private readonly IConfiguration _configuration; private readonly ILogger<AiSummaryController> _logger; private readonly IAppointmentService _appointmentService;
public AiSummaryController( IConfiguration configuration, ILogger<AiSummaryController> logger, IAppointmentService appointmentService) { _configuration = configuration; _logger = logger; _appointmentService = appointmentService; }
[HttpPost] public async Task<IActionResult> ReceiveIntakeSummary( [FromBody] IntakeSummaryPayload payload) { // Validate API key if (!Request.Headers.TryGetValue("X-API-Key", out var apiKey) || apiKey != _configuration["HamiApiKey"]) { return Unauthorized(new ErrorResponse { Success = false, Message = "Invalid or missing API key" }); }
try { // Validate required fields if (string.IsNullOrEmpty(payload.AppointmentId) || payload.IntakeSummary == null) { return BadRequest(new ErrorResponse { Success = false, Message = "Missing required fields: appointmentId or intakeSummary" }); }
// Find the appointment var appointment = await _appointmentService .FindByIdAsync(payload.AppointmentId);
if (appointment == null) { return UnprocessableEntity(new ErrorResponse { Success = false, Message = $"Appointment {payload.AppointmentId} not found" }); }
// Store the intake summary await _appointmentService.StoreIntakeSummaryAsync( payload.AppointmentId, payload.DateCreated, payload.IntakeSummary );
_logger.LogInformation( "Intake summary received for appointment {AppointmentId}", payload.AppointmentId );
return Ok(new SuccessResponse { Success = true, Message = "Summary received and processed successfully" }); } catch (Exception ex) { _logger.LogError(ex, "Error processing intake summary"); return StatusCode(500, new ErrorResponse { Success = false, Message = "Internal server error while processing summary" }); } } }
// Model classes public class IntakeSummaryPayload { [JsonPropertyName("appointmentId")] public string AppointmentId { get; set; }
[JsonPropertyName("dateCreated")] public string DateCreated { get; set; }
[JsonPropertyName("intakeSummary")] public IntakeSummary IntakeSummary { get; set; } }
public class IntakeSummary { [JsonPropertyName("Patient Information")] public PatientInformation PatientInformation { get; set; }
[JsonPropertyName("Chief Complaint")] public string ChiefComplaint { get; set; }
[JsonPropertyName("History of Present Illness")] public HistoryOfPresentIllness HistoryOfPresentIllness { get; set; }
[JsonPropertyName("Summary")] public string Summary { get; set; }
[JsonPropertyName("Past Medical History")] public PastMedicalHistory PastMedicalHistory { get; set; }
[JsonPropertyName("Family History")] public string FamilyHistory { get; set; }
[JsonPropertyName("Social History")] public SocialHistory SocialHistory { get; set; }
[JsonPropertyName("Medications")] public Medications Medications { get; set; }
[JsonPropertyName("Review of Systems")] public ReviewOfSystems ReviewOfSystems { get; set; } }
public class PatientInformation { [JsonPropertyName("Date of Birth/Age")] public string DateOfBirthAge { get; set; }
[JsonPropertyName("Gender")] public string Gender { get; set; } }
public class HistoryOfPresentIllness { [JsonPropertyName("Onset and Duration")] public string OnsetAndDuration { get; set; }
[JsonPropertyName("Associated Symptoms")] public string AssociatedSymptoms { get; set; }
[JsonPropertyName("Aggravating and Alleviating Factors")] public string AggravatingAndAlleviatingFactors { get; set; }
[JsonPropertyName("Severity of Symptoms")] public string SeverityOfSymptoms { get; set; } }
public class PastMedicalHistory { [JsonPropertyName("Chronic Illnesses")] public string ChronicIllnesses { get; set; }
[JsonPropertyName("Previous Hospitalizations")] public string PreviousHospitalizations { get; set; }
[JsonPropertyName("Surgeries")] public string Surgeries { get; set; }
[JsonPropertyName("Allergies")] public string Allergies { get; set; } }
public class SocialHistory { [JsonPropertyName("Social Support")] public string SocialSupport { get; set; }
[JsonPropertyName("Occupation")] public string Occupation { get; set; }
[JsonPropertyName("Lifestyle Factors")] public string LifestyleFactors { get; set; }
[JsonPropertyName("Exercise and Diet")] public string ExerciseAndDiet { get; set; } }
public class Medications { [JsonPropertyName("Current Medications")] public List<Medication> CurrentMedications { get; set; } }
public class Medication { [JsonPropertyName("Name")] public string Name { get; set; }
[JsonPropertyName("Brand Name")] public string BrandName { get; set; }
[JsonPropertyName("Generic Name")] public string GenericName { get; set; }
[JsonPropertyName("Dosage")] public string Dosage { get; set; }
[JsonPropertyName("Frequency")] public string Frequency { get; set; }
[JsonPropertyName("Formulation")] public string Formulation { get; set; }
[JsonPropertyName("Route")] public string Route { get; set; }
[JsonPropertyName("Duration")] public string Duration { get; set; } }
public class ReviewOfSystems { [JsonPropertyName("General")] public string General { get; set; }
[JsonPropertyName("Cardiovascular")] public string Cardiovascular { get; set; }
[JsonPropertyName("Respiratory")] public string Respiratory { get; set; }
[JsonPropertyName("Gastrointestinal")] public string Gastrointestinal { get; set; }
[JsonPropertyName("Genitourinary")] public string Genitourinary { get; set; }
[JsonPropertyName("Musculoskeletal")] public string Musculoskeletal { get; set; }
[JsonPropertyName("Neurological")] public string Neurological { get; set; }
[JsonPropertyName("Mental Health Screening")] public string MentalHealthScreening { get; set; }
[JsonPropertyName("Psychiatric")] public string Psychiatric { get; set; }
[JsonPropertyName("Endocrine")] public string Endocrine { get; set; }
[JsonPropertyName("Dermatological")] public string Dermatological { get; set; } }
public class SuccessResponse { public bool Success { get; set; } public string Message { get; set; } }
public class ErrorResponse { public bool Success { get; set; } public string Message { get; set; } }}Summary Field Guidelines
Section titled “Summary Field Guidelines”Patient Information
Section titled “Patient Information”- Date of Birth/Age: May contain actual birth date or just age
- Gender:
male,female, or other values as provided by patient
Chief Complaint
Section titled “Chief Complaint”Free-text description of the patient’s main concerns or reasons for visit.
History of Present Illness
Section titled “History of Present Illness”Structured information about the current condition:
- Onset and Duration: When symptoms started and how long they’ve persisted
- Associated Symptoms: Related symptoms the patient is experiencing
- Aggravating and Alleviating Factors: What makes symptoms better or worse
- Severity of Symptoms: Description of symptom intensity
Summary
Section titled “Summary”A comprehensive clinical narrative synthesizing all collected information. This is a physician-ready summary that can be directly incorporated into clinical documentation.
Past Medical History
Section titled “Past Medical History”- Chronic Illnesses: Ongoing medical conditions
- Previous Hospitalizations: History of hospital admissions
- Surgeries: Surgical history
- Allergies: Known allergies to medications, foods, or substances
Medications
Section titled “Medications”Array of current medications with detailed information:
- Name: Medication name as reported by patient
- Brand Name and Generic Name: May be empty if not specified
- Dosage: Dose and units
- Frequency: How often taken
- Formulation: tablet, capsule, liquid, etc.
- Route: oral, topical, intravenous, etc.
- Duration: How long the patient has been taking the medication
Review of Systems
Section titled “Review of Systems”Systematic review of body systems. Fields may contain:
- Specific findings: Reported symptoms
- “Not asked”: Information not collected during intake
- “Unremarkable”: No significant findings in that system
Handling “Not Asked” and “Unremarkable” Values
Section titled “Handling “Not Asked” and “Unremarkable” Values”The intake summary may contain fields with values:
- “Not asked”: HAMI did not inquire about this information during the conversation
- “Unremarkable”: The patient was asked, and reported no significant findings
- Empty string (
""): Information not provided or not applicable
Your system should handle these values appropriately based on your clinical workflow requirements.
Best Practices
Section titled “Best Practices”Endpoint Design
Section titled “Endpoint Design”- Performance: Respond within 5 seconds to avoid timeouts
- Asynchronous Processing: Process summaries asynchronously if your workflow is complex
- Health Checks: Implement health check endpoints for monitoring
- Logging: Log all incoming requests with appointment IDs for debugging
- Monitoring: Set up alerts for repeated failures
Data Handling
Section titled “Data Handling”- Idempotency: Design your endpoint to handle duplicate postings gracefully
- Version Management: Store multiple versions if patients update their intake multiple times
- Merge Strategy: Decide whether to replace or merge when receiving updated summaries
- Data Validation: Validate appointment IDs exist before storing summaries
- Conflict Resolution: Define how to handle conflicts with existing patient data
Security
Section titled “Security”- API Key Storage: Store API keys in environment variables or secure key management systems
- HTTPS Only: Never accept requests over HTTP
- Rate Limiting: Implement rate limiting to prevent abuse
- Authentication Logging: Log all authentication failures for security monitoring
- IP Whitelisting: Consider restricting access to HAMI’s IP addresses
- HIPAA Compliance: Ensure all data handling is HIPAA compliant
Error Recovery
Section titled “Error Recovery”- Comprehensive Logging: Log all requests, successes, and failures with sufficient detail
- Alerting: Set up alerts for repeated failures or unusual patterns
- Retry Handling: HAMI may retry failed requests, so ensure idempotency
- Fallback Procedures: Establish manual procedures when automated posting fails
Support
Section titled “Support”For additional support or questions about the Intake Summary Webhook:
- Integration Guide: AI Summary Integration
- Universal Ingestion API: Universal Ingestion API
- Email: support@bostonhealth.ai
- Website: bostonhealth.ai