Skip to main content

FHIR Exchange

Elix uses HL7 FHIR R4 where records cross into or out of another system. Inside Elix, data lives in its own model; at the boundary it is expressed as standard FHIR resources with standard codes, so the other side does not need to learn Elix's model.

WhatFHIR resourcesHow
EncountersEncounter, Bundle (searchset)Read and search over the FHIR interface
Lab orders and resultsServiceRequest, Observation, DiagnosticReportFetch as a bundle, or publish to a FHIR server
Imaging orders and reportsServiceRequest, DiagnosticReportFetch as a bundle, or publish to a FHIR server
Vital signsObservationFetch, or publish to a FHIR server
PatientsPatientDelivered to a health information exchange, confirmed by signed callback

All calls below use the base address https://api.app.elix.ng/api/v1 and the bearer token, facility headers and purpose of use described in Authentication.

Reading encounters​

The encounter service exposes a FHIR R4 read interface. Responses use the media type application/fhir+json.

Read one encounter​

curl 'https://api.app.elix.ng/api/v1/encounters/fhir/Encounter/<encounterId>?purposeOfUse=TREATMENT' \
-H 'Authorization: Bearer <accessToken>' \
-H 'x-org-id: <organisation id>' -H 'x-facility-id: <facility id>'

Returns a FHIR Encounter. The resource carries:

  • status: planned, in-progress, finished, cancelled or entered-in-error
  • class, coded from the HL7 v3 ActCode system
  • subject as Patient/<id> and serviceProvider as Organization/<id>
  • period with the start and, once the visit closes, the end
  • participant entries for the clinicians involved

An encounter outside your tenant or facility answers 404, so the response never confirms that an id exists elsewhere.

Search a patient's encounters​

curl 'https://api.app.elix.ng/api/v1/encounters/fhir/Encounter?patient=Patient/<patientId>&status=finished&date=ge2026-01-01&_count=50&purposeOfUse=TREATMENT' \
-H 'Authorization: Bearer <accessToken>' \
-H 'x-org-id: <organisation id>' -H 'x-facility-id: <facility id>'
ParameterRequiredNotes
patientYesA patient id, bare or as Patient/<id>. Search is always for one patient, so their privacy setting can be applied.
purposeOfUseYesTREATMENT, PAYMENT, OPS or RESEARCH
statusNoOne FHIR encounter status. An unknown value is a 400, not an empty result.
dateNoA FHIR date with an optional prefix: ge2026-01-01, le2026-12-31. A bare date means that day.
_countNoPage size, 1 to 100, default 20
cursorNoTaken from the previous page's next link

The answer is a searchset Bundle, newest encounter first. It has no total: Elix pages by position and does not count the whole set. If there are more results, the Bundle has a link with relation: "next". Follow it until there is none.

Required permission: encounters.access, or the narrower encounters.fhir.encounter.search and encounters.fhir.encounter.read.

Lab, imaging and vital signs as FHIR​

Each lab order, imaging order and vital signs set can be returned as FHIR resources.

EndpointReturnsPermission
GET /investigations/lab/orders/<labOrderId>/clinical/fhirServiceRequest, Observation per result, DiagnosticReportinvestigations.lab.list
GET /investigations/imaging/orders/<imagingOrderId>/clinical/fhirServiceRequest, DiagnosticReportinvestigations.imaging.list
GET /encounters/vitals/<vitalSetId>/clinical/fhirObservation per vital sign, LOINC codedencounters.vitals.read

The lab and imaging answers hold the source bundle and the resources sorted by type:

{
"orderId": "3f9a...",
"bundle": { "resourceType": "Bundle", "type": "collection", "entry": [ ... ] },
"orders": [ { "localId": "...", "resourceType": "ServiceRequest", "status": "completed", "code": "...", "resource": { ... } } ],
"observations": [ { "localId": "...", "resourceType": "Observation", "code": "718-7", "occurredAt": "2026-10-02T09:14:00Z", "resource": { ... } } ],
"reports": [ { "localId": "...", "resourceType": "DiagnosticReport", "conclusion": "...", "resource": { ... } } ]
}

The resource field is the FHIR resource itself. Use it as is; the fields beside it are a summary for convenience.

Publishing to a FHIR server​

Elix can push the same resources to a FHIR server, such as the one behind an external EMR or a regional record.

EndpointPublishesPermission
POST /investigations/lab/orders/<labOrderId>/clinical/publish-fhirThe order, its results and its reportinvestigations.lab.release
POST /investigations/imaging/orders/<imagingOrderId>/clinical/publish-fhirThe order and its reportinvestigations.imaging.write
POST /encounters/vitals/<vitalSetId>/clinical/publish-fhirEach vital signencounters.vitals.manage

How Elix writes to the server:

  • A resource with an id is sent as PUT <base>/<Type>/<id>, so publishing twice updates the same resource rather than creating a copy. A resource without one is sent as POST <base>/<Type>.
  • Requests carry Content-Type: application/json and, where Elix has them, x-correlation-id and x-idempotency-key headers that your server can log or use to drop repeats.
  • The server answers with the stored resource. Elix records the id your server returns.

The FHIR server Elix publishes to is configured by Elix for your deployment. Tell us its base address and how it authenticates (bearer token, basic auth or an API key header) when you request access; see Connect your EMR.

Health information exchanges​

Elix sends patient records to health information exchanges, such as a state's shared record, as FHIR Patient resources wrapped in a delivery envelope. Delivery runs through a queue with retries, so a slow or unavailable exchange delays a record rather than losing it. Records that still fail after the retries are held for review and can be replayed.

Each request Elix sends to an exchange is signed. It carries the time it was signed, a nonce, a SHA-256 hash of the body, and an HMAC-SHA256 signature over the method, path, query, body hash, time, nonce and client id. An exchange can check the signature and refuse anything stale or replayed.

Signed callbacks​

When an exchange or partner calls Elix back, for example to confirm that a patient was accepted and give the id it assigned, the call has no bearer token. It is authenticated by a signature instead.

  • Elix and the partner share a secret, issued per connection.
  • The partner computes HMAC-SHA256(secret, raw request body) and sends the hex digest in the agreed header, optionally prefixed with sha256=.
  • The signature is over the exact bytes sent. Do not sign a re-serialised copy of the JSON: spacing or key order that differs from what you send will fail the check.
  • A request with no signature, a wrong signature, or for a connection with no secret configured is refused with 401. Elix never accepts an unauthenticated callback.

A signing example in Node.js:

import {createHmac} from 'node:crypto';

const body = JSON.stringify(payload); // send exactly this string
const signature = createHmac('sha256', secret).update(body).digest('hex');

await fetch(callbackUrl, {
method: 'POST',
headers: {'Content-Type': 'application/json', 'x-signature': `sha256=${signature}`},
body,
});

The callback address, header name and secret are agreed per connection when the integration is set up.

Codes and terminology​

Elix codes clinical content with standard systems where they exist: LOINC for observations and lab tests, HL7 terminology for encounter classes and participation types. Where a facility uses local codes, an administrator maps them to the standard ones in the terminology maps of the integration platform, so what leaves Elix is coded consistently.