Skip to content

Universal Ingestion API

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.

All API requests must include an API key in the request headers.

X-API-KEY: your_api_key_here

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 example my-clinic), provided by HAMI during onboarding together with your API key and the API base URL for your environment
HeaderTypeRequiredDescription
X-API-KEYstringYesYour API authentication key
Content-TypestringYesMust be application/json. application/fhir+json is not accepted.

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.

Any valid FHIR R4 resource can be included and is stored with the transaction. HAMI actively uses the following:

ResourceWhat HAMI uses
PatientName, 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.
AppointmentCreates the appointment on the HAMI physician dashboard. If the bundle has no Appointment, HAMI creates one at the time of ingestion.
ObservationAn 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.

  1. 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.
  2. 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.
  3. Neither phone nor MRN: a new profile is created on every submission. Send at least one of the two to avoid duplicate patients.

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
}
]
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:unique-identifier",
"resource": {
// FHIR Resource content
},
"request": {
"method": "POST",
"url": "ResourceType"
}
}
]
}

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.

FieldTypeReturned whenDescription
transactionIdstringAlwaysUnique identifier for the ingestion transaction. Store it for tracking and support; it is also echoed back in the Intake Summary Webhook.
urlstringAlways (default configuration)Patient-facing link to this transaction in HAMI (/g/{groupSlug}/transaction/{transactionId}). Suitable for sending by SMS or email.
qrCodeImagestringQR code return is enabled for your groupBase64-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.
embedUrlstringIntake embedding is enabled for your groupPatient-specific intake link to load in an <iframe> inside your application. See Embedded patient intake.
scribeLaunchUrlstringScribe embedding is enabled for your groupLink that opens HAMI Scribe for this appointment inside your EHR, for the clinician. See Scribe launch.
appointmentUrlstringAppointment link return is enabled for your groupThe patient’s HAMI appointment link, the same one HAMI sends in its own SMS/WhatsApp invitations. See Appointment link.

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.

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" />

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_code that 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.communication includes 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.

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 Appointment resource, HAMI creates one from the Patient resource.
  • Include the patient’s MRN (a Patient.identifier with type code MR) and a stable fullUrl on the Appointment entry. 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.

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 session parameter that signs the patient in automatically. Treat it as a credential.
  • Returned only when an appointment was created for the ingestion.
Terminal window
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"
}
}
]
}'

The Patient resource should include:

  • Identifiers: MRN or other patient identifiers with appropriate systems
  • Name: Family name and given names
  • Gender: male, female, other, or unknown
  • Birth Date: ISO 8601 date format (YYYY-MM-DD)
  • Telecom: Contact information (phone, email)

For vital signs observations, use the standard FHIR vital signs profiles:

Vital SignLOINC CodeUCUM Unit
Blood Pressure85354-9mm[Hg] (component-based)
Heart Rate8867-4/min
Respiratory Rate9279-1/min
Body Temperature8310-5Cel or [degF]
Body Weight29463-7kg or [lb_av]
Body Height8302-2cm or [in_i]
Oxygen Saturation2708-6 or 59408-5%

FHIR Bundles support two types of references:

  1. URN-based references (for resources in the same bundle):

    "reference": "urn:uuid:f6ff3181-2799-47a0-9b8e-ad5569eb91b0"
  2. Logical references (for existing resources):

    "reference": "Patient/patient-123"

All datetime fields must use ISO 8601 format with UTC timezone:

  • Format: YYYY-MM-DDTHH:mm:ssZ
  • Example: 2024-05-23T10:12:00Z

Every error response has the same shape:

FieldTypeDescription
statusCodenumberHTTP status code
messagestring | arrayHuman-readable error. For FHIR validation errors, an array of validation messages.
correlationIdstringRequest 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.

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"
}

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"
}

The groupSlug in the URL does not exist.

{
"statusCode": 404,
"message": "No group found.",
"correlationId": "5b0f6c2e-3d41-4a8e-9f2b-7c1d0e9a8b76"
}

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"
}
  • 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.
  • Send one bundle per patient visit. Each successful call creates one transaction and one appointment.
  • Store the returned transactionId for each bundle for tracking and debugging
  • 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

Before sending data to the API:

  1. Validate your FHIR Bundle against the FHIR R4 specification
  2. Ensure all required fields are present
  3. Use standard terminologies (LOINC, SNOMED CT, etc.)
  4. Include appropriate resource profiles and meta tags

Implement robust error handling:

  • Retry 5xx responses and network failures with exponential backoff (see the idempotency note below)
  • Don’t retry 4xx responses unchanged. Fix the request first.
  • Log all transactionId values for successful submissions, and the correlationId for failures

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-id before resending. Otherwise you risk creating a duplicate appointment.

If you’re migrating from HL7 v2 messages, here are common mappings:

HL7 v2FHIR ResourceFHIR Field
PID segmentPatientPatient resource
PID-3Patientidentifier
PID-5Patientname
PID-7PatientbirthDate
PID-8Patientgender
PID-13Patienttelecom (phone)
OBX segmentObservationObservation resource
OBX-3Observationcode
OBX-5Observationvalue[x]
OBX-6ObservationvalueQuantity.unit
# HL7 v2 OBX segment to FHIR Observation
obx_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"
}
}
  1. Collect Clinical Data: Gather patient data from your EHR or clinical system
  2. Transform to FHIR: Convert data to FHIR R4 format following the specification
  3. Create Bundle: Package resources into a FHIR Bundle with type transaction
  4. Validate: Validate the bundle against FHIR R4 schema and profiles
  5. Submit: POST the bundle to the ingestion endpoint
  6. Store Transaction ID: Save the returned transactionId for tracking
  7. Hand off: Use whichever links your group has enabled: show the qrCodeImage, load the embedUrl in an iframe, send the appointmentUrl to the patient, or open the scribeLaunchUrl for the clinician
  8. Receive results: Accept the AI intake summary at your Intake Summary Webhook and the completed clinical note at your Scribe Webhook
  9. Monitor: Track ingestion status and handle any errors

See the Universal Ingestion Flow guide for a diagram of the full flow.

To validate your FHIR Bundles before submission:

  • FHIR Validator: https://fhir.github.io/validator/
  • HAPI FHIR: Java-based FHIR validation library
  • Python: fhir.resources Python package
  • Node.js: @types/fhir and validation libraries

For additional support or questions about the Universal Ingestion API: