Universal Ingestion API
Overview
Section titled “Overview”The Universal Ingestion API enables healthcare systems to submit FHIR R4-compliant data to the HAMI platform. This endpoint accepts FHIR Bundles containing patient records, appointments, encounters, observations, conditions, and other clinical resources.
Authentication
Section titled “Authentication”All API requests must include an API key in the request headers.
X-API-KEY: your_api_key_hereEndpoint Details
Section titled “Endpoint Details”URL: {HAMI_API_BASE_URL}/api/g/{groupSlug}/ingestion/fhir/v4
Method: POST
Content-Type: application/json
Path Parameters:
{groupSlug}- Your organization’s group slug (for examplemy-clinic), provided by HAMI during onboarding together with your API key and the API base URL for your environment
Request Schema
Section titled “Request Schema”Headers
Section titled “Headers”| Header | Type | Required | Description |
|---|---|---|---|
X-API-KEY | string | Yes | Your API authentication key |
Content-Type | string | Yes | Must be application/json. application/fhir+json is not accepted. |
Request Body
Section titled “Request Body”The request body must be a valid FHIR R4 Bundle with type transaction. The Bundle can contain multiple entries with different resource types. Each resource is checked for FHIR R4 structural validity, and unknown elements are rejected.
Supported Resource Types
Section titled “Supported Resource Types”Any valid FHIR R4 resource can be included and is stored with the transaction. HAMI actively uses the following:
| Resource | What HAMI uses |
|---|---|
| Patient | Name, gender, birth date, phone (telecom with system: phone), MRN (identifier with type code MR) and preferred language (communication). These are used to find or create the patient in HAMI. |
| Appointment | Creates the appointment on the HAMI physician dashboard. If the bundle has no Appointment, HAMI creates one at the time of ingestion. |
| Observation | An Observation with category code symptom is passed to the AI intake as the presenting symptom (from valueCodeableConcept.text, or the first coding’s display). |
Other resources (for example Encounter, Condition, Procedure, MedicationRequest, AllergyIntolerance, DiagnosticReport) are accepted and stored with the transaction.
How the patient is matched
Section titled “How the patient is matched”- Phone number present: HAMI looks up the patient account by phone number. If a profile with the same first and last name exists, it is reused. Otherwise a new profile is created under that account.
- No phone number: HAMI looks up the MRN for your group. If found, the linked profile is reused. Otherwise a new profile is created and the MRN is registered against it for future submissions.
- Neither phone nor MRN: a new profile is created on every submission. Send at least one of the two to avoid duplicate patients.
Preferred language
Section titled “Preferred language”Set Patient.communication so that the intake opens in the patient’s language. HAMI uses the entry marked preferred: true (or the first entry) and reads its language.coding code, preferring the urn:ietf:bcp:47 system. Supported codes: en, ar, ur, es, hi, ps, ru, pt, sw, en-ar. Regional variants such as ar-AE resolve to their base language.
"communication": [ { "language": { "coding": [ { "system": "urn:ietf:bcp:47", "code": "ar" } ] }, "preferred": true }]Bundle Structure
Section titled “Bundle Structure”{ "resourceType": "Bundle", "type": "transaction", "entry": [ { "fullUrl": "urn:uuid:unique-identifier", "resource": { // FHIR Resource content }, "request": { "method": "POST", "url": "ResourceType" } } ]}Response Schema
Section titled “Response Schema”Success Response (201 Created)
Section titled “Success Response (201 Created)”Every successful call returns a transactionId. Depending on the features enabled for your group, the response can also carry ready-to-use links and a QR code, so you can hand the patient straight into HAMI intake, or open HAMI Scribe for the clinician, without a second API call.
| Field | Type | Returned when | Description |
|---|---|---|---|
transactionId | string | Always | Unique identifier for the ingestion transaction. Store it for tracking and support; it is also echoed back in the Intake Summary Webhook. |
url | string | Always (default configuration) | Patient-facing link to this transaction in HAMI (/g/{groupSlug}/transaction/{transactionId}). Suitable for sending by SMS or email. |
qrCodeImage | string | QR code return is enabled for your group | Base64-encoded PNG (300×300) of a QR code the patient scans to start intake on their own phone. Raw base64, no data: prefix. See QR code. |
embedUrl | string | Intake embedding is enabled for your group | Patient-specific intake link to load in an <iframe> inside your application. See Embedded patient intake. |
scribeLaunchUrl | string | Scribe embedding is enabled for your group | Link that opens HAMI Scribe for this appointment inside your EHR, for the clinician. See Scribe launch. |
appointmentUrl | string | Appointment link return is enabled for your group | The patient’s HAMI appointment link, the same one HAMI sends in its own SMS/WhatsApp invitations. See Appointment link. |
Response Examples
Section titled “Response Examples”Default configuration:
{ "transactionId": "0192f3a1-7c4e-7b2a-9d1f-3e5a6b7c8d90", "url": "https://hamiapp.bostonhealth.ai/g/my-group/transaction/0192f3a1-7c4e-7b2a-9d1f-3e5a6b7c8d90"}All optional features enabled:
{ "transactionId": "0192f3a1-7c4e-7b2a-9d1f-3e5a6b7c8d90", "url": "https://hamiapp.bostonhealth.ai/g/my-group/transaction/0192f3a1-7c4e-7b2a-9d1f-3e5a6b7c8d90", "qrCodeImage": "iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAA...", "embedUrl": "https://hamiapp.bostonhealth.ai/g/my-group/embed?embed_code=Qm9zdG9uSGVhbHRoQUk...", "scribeLaunchUrl": "https://hamiapp.bostonhealth.ai/scribe/launch?launch_code=X2xhdW5jaF9jb2RlX2V4YW1wbGU...", "appointmentUrl": "https://hamiapp.bostonhealth.ai/g/my-group/appointment/8mK2pQ7rT4vW?lang=en"}The host in the returned links is the HAMI app for your environment. It may be a white-labelled domain for your organisation.
Optional Response Features
Section titled “Optional Response Features”QR code (qrCodeImage)
Section titled “QR code (qrCodeImage)”A base64-encoded PNG QR code the patient scans to start their HAMI intake on their own device, for example on a check-in kiosk, a printed visit slip, or a screen in the waiting room.
- The QR code encodes a patient-specific join link (
/g/{groupSlug}/join?token=...), so the patient is not asked to find their record again. - The link expires after 30 minutes by default. Your group’s expiry can be configured.
- The value is raw base64. To display it, add the data-URI prefix:
<img src="data:image/png;base64,{qrCodeImage}" alt="Scan to start your HAMI intake" width="300" height="300" />Embedded patient intake (embedUrl)
Section titled “Embedded patient intake (embedUrl)”A patient-specific intake session you load directly in an <iframe> in your portal or app. The patient does not need to sign in, because the session is already bound to the patient and appointment created by this ingestion call.
- The URL contains a single-use
embed_codethat is valid for 120 minutes. After it has been used once it cannot be used again, so load it in exactly one iframe and don’t prefetch it. - Your page’s origin (e.g.
https://portal.yourclinic.com) must be registered with HAMI before go-live. HAMI rejects embed sessions requested from any other origin. - If the bundle’s
Patient.communicationincludes a supported preferred language, the intake opens in that language and skips the language selection screen. - The session survives a page reload inside the iframe, so a patient who refreshes continues where they left off.
- When the patient completes intake, the AI summary is delivered to your Intake Summary Webhook.
See Iframe Embedding for the required iframe attributes.
Scribe launch (scribeLaunchUrl)
Section titled “Scribe launch (scribeLaunchUrl)”A link that opens HAMI Scribe (ambient clinical documentation) for this appointment, for the clinician, typically in an iframe or panel inside your EHR.
- The URL contains a single-use
launch_code, valid for 120 minutes by default (configurable per group). - The clinician signs in with their HAMI account (phone number, then a one-time code or their password). They must be a member of your HAMI group.
- Returned only when an appointment was created for the ingestion. This is nearly always the case: if the bundle has no
Appointmentresource, HAMI creates one from thePatientresource. - Include the patient’s MRN (a
Patient.identifierwith type codeMR) and a stablefullUrlon theAppointmententry. HAMI returns both in the Scribe Webhook so you can match the note to your records.
See Scribe Embedding for the full launch flow and iframe events.
Appointment link (appointmentUrl)
Section titled “Appointment link (appointmentUrl)”The patient’s HAMI appointment link: the same link HAMI would otherwise send in its own SMS or WhatsApp invitation. Use it when you want to deliver the invitation through your own channels (patient portal, your SMS provider, email).
- Format:
/g/{groupSlug}/appointment/{appointmentCode}?lang={language}. - For groups with magic-link sign-in enabled, the link also carries a
sessionparameter that signs the patient in automatically. Treat it as a credential. - Returned only when an appointment was created for the ingestion.
Code Examples
Section titled “Code Examples”curl --location 'https://your-hami-api-host/api/g/my-group/ingestion/fhir/v4' \ --header 'X-API-KEY: your_api_key_here' \ --header 'Content-Type: application/json' \ --data '{ "resourceType": "Bundle", "type": "transaction", "entry": [ { "fullUrl": "urn:uuid:f6ff3181-2799-47a0-9b8e-ad5569eb91b0", "resource": { "resourceType": "Patient", "text": { "status": "generated", "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">Patient: Ali Khan, Male, DOB: 2004-05-23</div>" }, "identifier": [ { "system": "http://hospital.org/mrn", "value": "24250053463", "type": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "MR", "display": "Medical record number" } ], "text": "MRN" } } ], "name": [ { "family": "Khan", "given": ["Ali"] } ], "gender": "male", "birthDate": "2004-05-23", "telecom": [ { "system": "phone", "use": "mobile", "value": "+923001234313" } ] }, "request": { "method": "POST", "url": "Patient" } }, { "fullUrl": "urn:uuid:39f640a9-e304-4c13-8b40-07ece7a03c15", "resource": { "resourceType": "Appointment", "text": { "status": "generated", "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">Appointment: Booked from 2024-05-23 10:00 to 11:00 UTC</div>" }, "status": "booked", "start": "2024-05-23T10:00:00Z", "end": "2024-05-23T11:00:00Z", "participant": [ { "actor": { "reference": "urn:uuid:f6ff3181-2799-47a0-9b8e-ad5569eb91b0" }, "status": "accepted" } ] }, "request": { "method": "POST", "url": "Appointment" } }, { "fullUrl": "urn:uuid:73ca6eec-0a49-429d-8c4d-86c8ccb8a365", "resource": { "resourceType": "Encounter", "text": { "status": "generated", "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">Encounter: In-progress ambulatory visit</div>" }, "status": "in-progress", "class": { "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", "code": "AMB", "display": "ambulatory" }, "subject": { "reference": "urn:uuid:f6ff3181-2799-47a0-9b8e-ad5569eb91b0" }, "appointment": [ { "reference": "urn:uuid:39f640a9-e304-4c13-8b40-07ece7a03c15" } ], "period": { "start": "2024-05-23T10:05:00Z" } }, "request": { "method": "POST", "url": "Encounter" } }, { "fullUrl": "urn:uuid:825d9822-cbe3-4805-9094-adcbf44e86f8", "resource": { "resourceType": "Condition", "text": { "status": "generated", "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">Condition: Active confirmed encounter diagnosis of Vomiting</div>" }, "subject": { "reference": "urn:uuid:f6ff3181-2799-47a0-9b8e-ad5569eb91b0" }, "encounter": { "reference": "urn:uuid:73ca6eec-0a49-429d-8c4d-86c8ccb8a365" }, "clinicalStatus": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", "code": "active" } ] }, "verificationStatus": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", "code": "confirmed" } ] }, "category": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/condition-category", "code": "encounter-diagnosis" } ] } ], "code": { "coding": [ { "system": "http://snomed.info/sct", "code": "422400008", "display": "Vomiting" } ], "text": "Vomiting" }, "recordedDate": "2024-05-23T10:10:00Z" }, "request": { "method": "POST", "url": "Condition" } }, { "fullUrl": "urn:uuid:40275738-6dfc-45f8-bacf-f3a702bf1720", "resource": { "resourceType": "Observation", "meta": { "profile": ["http://hl7.org/fhir/StructureDefinition/bp"] }, "status": "final", "category": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "vital-signs" } ] } ], "code": { "coding": [ { "system": "http://loinc.org", "code": "85354-9", "display": "Blood pressure panel with all children optional" } ], "text": "Blood Pressure" }, "subject": { "reference": "urn:uuid:f6ff3181-2799-47a0-9b8e-ad5569eb91b0" }, "encounter": { "reference": "urn:uuid:73ca6eec-0a49-429d-8c4d-86c8ccb8a365" }, "effectiveDateTime": "2024-05-23T10:12:00Z", "performer": [ { "reference": "Practitioner/practitioner-1", "display": "Dr. Example" } ], "component": [ { "code": { "coding": [ { "system": "http://loinc.org", "code": "8480-6", "display": "Systolic blood pressure" } ], "text": "Systolic blood pressure" }, "valueQuantity": { "value": 79, "unit": "mmHg", "system": "http://unitsofmeasure.org", "code": "mm[Hg]" } }, { "code": { "coding": [ { "system": "http://loinc.org", "code": "8462-4", "display": "Diastolic blood pressure" } ], "text": "Diastolic blood pressure" }, "valueQuantity": { "value": 45, "unit": "mmHg", "system": "http://unitsofmeasure.org", "code": "mm[Hg]" } } ] }, "request": { "method": "POST", "url": "Observation" } } ] }'import fetch from 'node-fetch';
interface FHIRBundle { resourceType: 'Bundle'; type: 'transaction'; entry: Array<{ fullUrl: string; resource: any; request: { method: string; url: string; }; }>;}
interface IngestionResponse { transactionId: string; url?: string; // Present only when the matching feature is enabled for your group qrCodeImage?: string; // base64 PNG, no data: prefix embedUrl?: string; // patient intake iframe URL (single use) scribeLaunchUrl?: string; // clinician Scribe URL (single use) appointmentUrl?: string; // patient appointment link}
async function ingestFHIRBundle( apiKey: string, groupSlug: string, bundle: FHIRBundle): Promise<IngestionResponse> { const url = `https://your-hami-api-host/api/g/${groupSlug}/ingestion/fhir/v4`;
const response = await fetch(url, { method: 'POST', headers: { 'X-API-KEY': apiKey, 'Content-Type': 'application/json', }, body: JSON.stringify(bundle), });
if (!response.ok) { const errorText = await response.text(); throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`); }
return await response.json();}
// Usage exampleasync function main() { const bundle: FHIRBundle = { resourceType: 'Bundle', type: 'transaction', entry: [ { fullUrl: 'urn:uuid:f6ff3181-2799-47a0-9b8e-ad5569eb91b0', resource: { resourceType: 'Patient', identifier: [ { system: 'http://hospital.org/mrn', value: '24250053463', type: { coding: [ { system: 'http://terminology.hl7.org/CodeSystem/v2-0203', code: 'MR', display: 'Medical record number', }, ], text: 'MRN', }, }, ], name: [ { family: 'Khan', given: ['Ali'], }, ], gender: 'male', birthDate: '2004-05-23', telecom: [ { system: 'phone', use: 'mobile', value: '+923001234313', }, ], }, request: { method: 'POST', url: 'Patient', }, }, { fullUrl: 'urn:uuid:40275738-6dfc-45f8-bacf-f3a702bf1720', resource: { resourceType: 'Observation', status: 'final', category: [ { coding: [ { system: 'http://terminology.hl7.org/CodeSystem/observation-category', code: 'vital-signs', }, ], }, ], code: { coding: [ { system: 'http://loinc.org', code: '85354-9', display: 'Blood pressure panel with all children optional', }, ], text: 'Blood Pressure', }, subject: { reference: 'urn:uuid:f6ff3181-2799-47a0-9b8e-ad5569eb91b0', }, effectiveDateTime: '2024-05-23T10:12:00Z', component: [ { code: { coding: [ { system: 'http://loinc.org', code: '8480-6', display: 'Systolic blood pressure', }, ], }, valueQuantity: { value: 79, unit: 'mmHg', system: 'http://unitsofmeasure.org', code: 'mm[Hg]', }, }, { code: { coding: [ { system: 'http://loinc.org', code: '8462-4', display: 'Diastolic blood pressure', }, ], }, valueQuantity: { value: 45, unit: 'mmHg', system: 'http://unitsofmeasure.org', code: 'mm[Hg]', }, }, ], }, request: { method: 'POST', url: 'Observation', }, }, ], };
try { const result = await ingestFHIRBundle( 'your_api_key_here', 'my-group', bundle ); console.log('Ingestion successful!'); console.log('Transaction ID:', result.transactionId);
// Optional fields: check before use, and never log the URLs themselves if (result.embedUrl) { document.querySelector<HTMLIFrameElement>('#hami-intake')!.src = result.embedUrl; } if (result.qrCodeImage) { document.querySelector<HTMLImageElement>('#hami-qr')!.src = `data:image/png;base64,${result.qrCodeImage}`; } } catch (error) { console.error('Error ingesting FHIR bundle:', error); }}
main();import base64import requestsfrom typing import Dict, List, Anyimport json
def ingest_fhir_bundle( api_key: str, group_slug: str, bundle: Dict[str, Any]) -> Dict[str, str]: """ Ingest a FHIR R4 Bundle into the HAMI platform.
Args: api_key: API authentication key group_slug: Your organization's group slug bundle: FHIR R4 Bundle dictionary with type 'transaction'
Returns: Dictionary containing the transaction ID
Raises: requests.exceptions.RequestException: If the API request fails """ url = f'https://your-hami-api-host/api/g/{group_slug}/ingestion/fhir/v4'
headers = { 'X-API-KEY': api_key, 'Content-Type': 'application/json' }
response = requests.post(url, headers=headers, json=bundle) response.raise_for_status()
return response.json()
def create_patient_resource( mrn: str, family_name: str, given_names: List[str], gender: str, birth_date: str, phone: str, patient_uuid: str) -> Dict[str, Any]: """Helper function to create a FHIR Patient resource.""" return { 'fullUrl': f'urn:uuid:{patient_uuid}', 'resource': { 'resourceType': 'Patient', 'identifier': [ { 'system': 'http://hospital.org/mrn', 'value': mrn, 'type': { 'coding': [ { 'system': 'http://terminology.hl7.org/CodeSystem/v2-0203', 'code': 'MR', 'display': 'Medical record number' } ], 'text': 'MRN' } } ], 'name': [ { 'family': family_name, 'given': given_names } ], 'gender': gender, 'birthDate': birth_date, 'telecom': [ { 'system': 'phone', 'use': 'mobile', 'value': phone } ] }, 'request': { 'method': 'POST', 'url': 'Patient' } }
def create_observation_resource( observation_uuid: str, patient_uuid: str, loinc_code: str, loinc_display: str, value: float, unit: str, ucum_code: str, effective_datetime: str) -> Dict[str, Any]: """Helper function to create a FHIR Observation resource.""" return { 'fullUrl': f'urn:uuid:{observation_uuid}', 'resource': { 'resourceType': 'Observation', 'status': 'final', 'category': [ { 'coding': [ { 'system': 'http://terminology.hl7.org/CodeSystem/observation-category', 'code': 'vital-signs' } ] } ], 'code': { 'coding': [ { 'system': 'http://loinc.org', 'code': loinc_code, 'display': loinc_display } ] }, 'subject': { 'reference': f'urn:uuid:{patient_uuid}' }, 'effectiveDateTime': effective_datetime, 'valueQuantity': { 'value': value, 'unit': unit, 'system': 'http://unitsofmeasure.org', 'code': ucum_code } }, 'request': { 'method': 'POST', 'url': 'Observation' } }
# Usage exampleif __name__ == '__main__': # Create a FHIR Bundle bundle = { 'resourceType': 'Bundle', 'type': 'transaction', 'entry': [] }
# Add patient patient_uuid = 'f6ff3181-2799-47a0-9b8e-ad5569eb91b0' bundle['entry'].append( create_patient_resource( mrn='24250053463', family_name='Khan', given_names=['Ali'], gender='male', birth_date='2004-05-23', phone='+923001234313', patient_uuid=patient_uuid ) )
# Add vital signs observations observations = [ { 'uuid': 'c650edeb-11b7-42bd-8302-c804fd74dac7', 'loinc_code': '8867-4', 'display': 'Heart rate', 'value': 129, 'unit': 'beats/min', 'ucum_code': '/min' }, { 'uuid': '4ebb218f-09b8-450e-8bbe-648b0286462f', 'loinc_code': '9279-1', 'display': 'Respiratory rate', 'value': 31, 'unit': '1/min', 'ucum_code': '/min' }, { 'uuid': '03cbce73-b8a9-4690-983f-e5d60ff8a617', 'loinc_code': '8310-5', 'display': 'Body temperature', 'value': 37.0, 'unit': '°C', 'ucum_code': 'Cel' }, { 'uuid': '0e8a11a3-d9db-4fe6-a951-d57a12ef542b', 'loinc_code': '29463-7', 'display': 'Body weight', 'value': 7.5, 'unit': 'kg', 'ucum_code': 'kg' } ]
for obs in observations: bundle['entry'].append( create_observation_resource( observation_uuid=obs['uuid'], patient_uuid=patient_uuid, loinc_code=obs['loinc_code'], loinc_display=obs['display'], value=obs['value'], unit=obs['unit'], ucum_code=obs['ucum_code'], effective_datetime='2024-05-23T10:12:00Z' ) )
# Ingest the bundle try: result = ingest_fhir_bundle( api_key='your_api_key_here', group_slug='my-group', bundle=bundle )
print('Ingestion successful!') print(f"Transaction ID: {result['transactionId']}")
# Optional fields: present only when enabled for your group if 'qrCodeImage' in result: with open('hami-qr.png', 'wb') as f: f.write(base64.b64decode(result['qrCodeImage'])) scribe_launch_url = result.get('scribeLaunchUrl') # hand to your EHR UI; do not log
except requests.exceptions.RequestException as e: print(f'Error ingesting FHIR bundle: {e}') if hasattr(e.response, 'text'): print(f'Response: {e.response.text}')FHIR Resource Guidelines
Section titled “FHIR Resource Guidelines”Patient Resource
Section titled “Patient Resource”The Patient resource should include:
- Identifiers: MRN or other patient identifiers with appropriate systems
- Name: Family name and given names
- Gender:
male,female,other, orunknown - Birth Date: ISO 8601 date format (YYYY-MM-DD)
- Telecom: Contact information (phone, email)
Observation Resources (Vital Signs)
Section titled “Observation Resources (Vital Signs)”For vital signs observations, use the standard FHIR vital signs profiles:
| Vital Sign | LOINC Code | UCUM Unit |
|---|---|---|
| Blood Pressure | 85354-9 | mm[Hg] (component-based) |
| Heart Rate | 8867-4 | /min |
| Respiratory Rate | 9279-1 | /min |
| Body Temperature | 8310-5 | Cel or [degF] |
| Body Weight | 29463-7 | kg or [lb_av] |
| Body Height | 8302-2 | cm or [in_i] |
| Oxygen Saturation | 2708-6 or 59408-5 | % |
Reference Handling
Section titled “Reference Handling”FHIR Bundles support two types of references:
-
URN-based references (for resources in the same bundle):
"reference": "urn:uuid:f6ff3181-2799-47a0-9b8e-ad5569eb91b0" -
Logical references (for existing resources):
"reference": "Patient/patient-123"
DateTime Format
Section titled “DateTime Format”All datetime fields must use ISO 8601 format with UTC timezone:
- Format:
YYYY-MM-DDTHH:mm:ssZ - Example:
2024-05-23T10:12:00Z
Error Responses
Section titled “Error Responses”Every error response has the same shape:
| Field | Type | Description |
|---|---|---|
statusCode | number | HTTP status code |
message | string | array | Human-readable error. For FHIR validation errors, an array of validation messages. |
correlationId | string | Request identifier, also returned in the x-correlation-id response header. Include it when contacting support. You can send your own x-correlation-id request header and HAMI will use it. |
400 Bad Request
Section titled “400 Bad Request”The body is not valid JSON, is not a JSON object, or fails FHIR R4 structural validation (for example an unknown element or a wrongly typed value).
{ "statusCode": 400, "message": [ { "message": "...", "location": "..." } ], "correlationId": "5b0f6c2e-3d41-4a8e-9f2b-7c1d0e9a8b76"}401 Unauthorized
Section titled “401 Unauthorized”The X-API-KEY header is missing, or the key is not valid for this group.
{ "statusCode": 401, "message": "Invalid API key", "correlationId": "5b0f6c2e-3d41-4a8e-9f2b-7c1d0e9a8b76"}404 Not Found
Section titled “404 Not Found”The groupSlug in the URL does not exist.
{ "statusCode": 404, "message": "No group found.", "correlationId": "5b0f6c2e-3d41-4a8e-9f2b-7c1d0e9a8b76"}500 Internal Server Error
Section titled “500 Internal Server Error”An unexpected error occurred. The details are hidden in the response. Contact support with the correlationId.
{ "statusCode": 500, "message": "Request failed", "correlationId": "5b0f6c2e-3d41-4a8e-9f2b-7c1d0e9a8b76"}Limits
Section titled “Limits”- Maximum request size: 50 MB
- Throughput: if you plan to send high volumes (for example a bulk backfill), contact support@bostonhealth.ai beforehand so we can plan capacity with you.
Best Practices
Section titled “Best Practices”Bundle Size
Section titled “Bundle Size”- Send one bundle per patient visit. Each successful call creates one transaction and one appointment.
- Store the returned
transactionIdfor each bundle for tracking and debugging
Resource References
Section titled “Resource References”- Use URN-based references (
urn:uuid:...) for resources within the same bundle - Ensure all referenced resources are included in the bundle or exist in the system
- Use valid UUIDs for URN-based references
Validation
Section titled “Validation”Before sending data to the API:
- Validate your FHIR Bundle against the FHIR R4 specification
- Ensure all required fields are present
- Use standard terminologies (LOINC, SNOMED CT, etc.)
- Include appropriate resource profiles and meta tags
Error Handling
Section titled “Error Handling”Implement robust error handling:
- Retry
5xxresponses and network failures with exponential backoff (see the idempotency note below) - Don’t retry
4xxresponses unchanged. Fix the request first. - Log all
transactionIdvalues for successful submissions, and thecorrelationIdfor failures
Idempotency
Section titled “Idempotency”The API is not idempotent. Each successful request creates a new transaction and a new appointment, even when the bundle is identical to a previous one. The patient profile is reused when it can be matched by phone number and name, or by MRN (see How the patient is matched).
- Once you have received a
201, don’t resubmit the same visit. - If a request times out and you don’t know whether it succeeded, contact support with your
x-correlation-idbefore resending. Otherwise you risk creating a duplicate appointment.
Data Mapping Guide
Section titled “Data Mapping Guide”From HL7 v2 to FHIR
Section titled “From HL7 v2 to FHIR”If you’re migrating from HL7 v2 messages, here are common mappings:
| HL7 v2 | FHIR Resource | FHIR Field |
|---|---|---|
| PID segment | Patient | Patient resource |
| PID-3 | Patient | identifier |
| PID-5 | Patient | name |
| PID-7 | Patient | birthDate |
| PID-8 | Patient | gender |
| PID-13 | Patient | telecom (phone) |
| OBX segment | Observation | Observation resource |
| OBX-3 | Observation | code |
| OBX-5 | Observation | value[x] |
| OBX-6 | Observation | valueQuantity.unit |
Example Transformation
Section titled “Example Transformation”# HL7 v2 OBX segment to FHIR Observationobx_segment = "OBX|1|NM|8867-4^Heart rate^LN||129|/min|||||F"
# Transforms to:observation = { "resourceType": "Observation", "status": "final", "code": { "coding": [{ "system": "http://loinc.org", "code": "8867-4", "display": "Heart rate" }] }, "valueQuantity": { "value": 129, "unit": "/min", "system": "http://unitsofmeasure.org", "code": "/min" }}Integration Workflow
Section titled “Integration Workflow”- Collect Clinical Data: Gather patient data from your EHR or clinical system
- Transform to FHIR: Convert data to FHIR R4 format following the specification
- Create Bundle: Package resources into a FHIR Bundle with type
transaction - Validate: Validate the bundle against FHIR R4 schema and profiles
- Submit: POST the bundle to the ingestion endpoint
- Store Transaction ID: Save the returned
transactionIdfor tracking - Hand off: Use whichever links your group has enabled: show the
qrCodeImage, load theembedUrlin an iframe, send theappointmentUrlto the patient, or open thescribeLaunchUrlfor the clinician - Receive results: Accept the AI intake summary at your Intake Summary Webhook and the completed clinical note at your Scribe Webhook
- Monitor: Track ingestion status and handle any errors
See the Universal Ingestion Flow guide for a diagram of the full flow.
Validation Tools
Section titled “Validation Tools”To validate your FHIR Bundles before submission:
- FHIR Validator: https://fhir.github.io/validator/
- HAPI FHIR: Java-based FHIR validation library
- Python:
fhir.resourcesPython package - Node.js:
@types/fhirand validation libraries
Support
Section titled “Support”For additional support or questions about the Universal Ingestion API:
- Documentation: HAMI Integration Platform
- FHIR Specification: HL7 FHIR R4
- Email: support@bostonhealth.ai
- Website: bostonhealth.ai