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: https://api.hami.health/api/g/{groupId}/ingestion/fhir/v4
Method: POST
Content-Type: application/json
Path Parameters:
{groupId}- Your organization’s unique group identifier
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 |
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.
Supported Resource Types
Section titled “Supported Resource Types”- Patient - Patient demographics and identifiers
- Appointment - Scheduled appointments
- Encounter - Clinical encounters
- Observation - Vital signs, lab results, and other observations
- Condition - Patient diagnoses and conditions
- Procedure - Medical procedures
- MedicationRequest - Medication prescriptions
- AllergyIntolerance - Patient allergies
- DiagnosticReport - Diagnostic test reports
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 (200 OK)
Section titled “Success Response (200 OK)”| Field | Type | Description | Example |
|---|---|---|---|
transactionId | string | Unique identifier for the ingestion transaction | "t3zL38PrPDaFp2fLL4DvtY" |
Response Example
Section titled “Response Example”{ "transactionId": "t3zL38PrPDaFp2fLL4DvtY"}Code Examples
Section titled “Code Examples”curl --location 'https://api.hami.health/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;}
async function ingestFHIRBundle( apiKey: string, groupId: string, bundle: FHIRBundle): Promise<IngestionResponse> { const url = `https://api.hami.health/api/g/${groupId}/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); } catch (error) { console.error('Error ingesting FHIR bundle:', error); }}
main();import requestsfrom typing import Dict, List, Anyimport json
def ingest_fhir_bundle( api_key: str, group_id: str, bundle: Dict[str, Any]) -> Dict[str, str]: """ Ingest a FHIR R4 Bundle into the HAMI platform.
Args: api_key: API authentication key group_id: Organization's unique group identifier 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://api.hami.health/api/g/{group_id}/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_id='my-group', bundle=bundle )
print('Ingestion successful!') print(f"Transaction ID: {result['transactionId']}")
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”400 Bad Request
Section titled “400 Bad Request”Returned when the FHIR Bundle is malformed or invalid.
{ "error": "Bad Request", "message": "Invalid FHIR Bundle: resourceType must be 'Bundle'", "statusCode": 400}401 Unauthorized
Section titled “401 Unauthorized”Returned when the API key is missing or invalid.
{ "error": "Unauthorized", "message": "Invalid or missing API key", "statusCode": 401}422 Unprocessable Entity
Section titled “422 Unprocessable Entity”Returned when the FHIR Bundle fails validation.
{ "error": "Unprocessable Entity", "message": "FHIR validation error: Patient.birthDate must be a valid date", "statusCode": 422, "details": { "resourceType": "Patient", "field": "birthDate", "issue": "Invalid date format" }}500 Internal Server Error
Section titled “500 Internal Server Error”Returned when an unexpected error occurs on the server.
{ "error": "Internal Server Error", "message": "An unexpected error occurred while processing the FHIR Bundle", "statusCode": 500}Rate Limiting
Section titled “Rate Limiting”API requests are rate-limited to ensure system stability:
- Rate Limit: 100 requests per minute per API key
- Burst Limit: 10 requests per second
- Bundle Size Limit: Maximum 100 entries per bundle
When rate limits are exceeded, the API returns a 429 Too Many Requests response:
{ "error": "Too Many Requests", "message": "Rate limit exceeded. Please try again later.", "statusCode": 429, "retryAfter": 60}Best Practices
Section titled “Best Practices”Bundle Size
Section titled “Bundle Size”- Keep bundles under 100 entries for optimal performance
- For large datasets, split into multiple bundles and send sequentially
- 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 on network failures with exponential backoff
- Log all
transactionIdvalues for successful submissions - Handle validation errors by correcting and resubmitting
- Monitor rate limits and implement throttling
Idempotency
Section titled “Idempotency”The API is idempotent when using consistent identifiers:
- Use the same
fullUrlUUIDs for the same resources - Duplicate submissions with identical data will not create duplicates
- Store and reuse transaction IDs for reference
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 - Monitor: Track ingestion status and handle any errors
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