How to integrate clinic systems with a FHIR backend
Tutorial: connect a clinic EMR to patients' FHIR R4 records — directory of doctors, matching by record number, a visit as one transaction with the doctor as author, lab reports, other providers' data and webhooks.
This tutorial connects a clinic's software (an EMR, a practice management system or a booking tool) to patients' HL7 FHIR R4 records in Anpheros: the clinic and its doctors are registered in the directory, patients are matched by the clinic's record number, a visit is written as one transaction with the doctor named as author, and the clinic learns about data other providers add. Every request is curl against the free sandbox, and the responses are the platform's own.
The integration runs on the clinic's server, with a project API key for the patients the clinic registers and, for people who already have an Anpheros record, an OAuth grant they approve.
What maps to what
| In the clinic system | In FHIR R4 |
|---|---|
| the clinic, its departments | Organization |
| a doctor or nurse | Practitioner |
| that person working at that clinic, with a specialty | PractitionerRole |
| the patient file and its number | Patient with an identifier in the clinic's system |
| a visit | Encounter |
| vital signs, results measured during the visit | Observation linked to the Encounter |
| a diagnosis | Condition (ICD-10), with verificationStatus provisional or confirmed |
| a prescription, current treatment | MedicationStatement (ATC) |
| a letter, a discharge summary, a scan | DocumentReference with the file |
| lab results | the lab connector, into Observation |
Step 1: register the clinic and its doctors
Directory resources have no patient. Other projects in the same environment can read them, so a patient's other providers see who wrote what, but only your project can change them.
export BASE=https://platform.anpheros.com/fhir/R4
export KEY=sk_test_…
H=(-H "Authorization: Bearer $KEY" -H "Content-Type: application/fhir+json")
curl -X POST $BASE/Organization "${H[@]}" -d '{"resourceType": "Organization", "active": true, "name": "Clinica Cardio Nord",
"identifier": [{"system": "https://clinic.example/org", "value": "CCN"}]}'
curl -X POST $BASE/Practitioner "${H[@]}" -d '{"resourceType": "Practitioner", "active": true,
"name": [{"family": "Popescu", "given": ["Ana"], "prefix": ["Dr."]}]}'
curl -X POST $BASE/PractitionerRole "${H[@]}" -d '{"resourceType": "PractitionerRole", "active": true,
"practitioner": {"reference": "Practitioner/'$PRACTITIONER'"},
"organization": {"reference": "Organization/'$ORG'"},
"code": [{"text": "Cardiologist"}]}'
Keep the returned ids next to your staff records. When someone leaves, set their PractitionerRole to "active": false; their past writes keep their name, and they can no longer act through your system.
Step 2: match patients
For patients the clinic registers, create the Patient with the clinic's record number as an identifier:
curl -X POST $BASE/Patient "${H[@]}" -d '{"resourceType": "Patient",
"identifier": [{"system": "https://clinic.example/mrn", "value": "MRN-20981"}],
"name": [{"family": "Dinu", "given": ["Andrei"]}], "gender": "male", "birthDate": "1968-11-05"}'
From then on, find the patient by that number, never by name and birth date:
curl "$BASE/Patient?identifier=https://clinic.example/mrn|MRN-20981" -H "Authorization: Bearer $KEY"
When the person already has an Anpheros record, from their own app or another provider, the clinic does not create a second one. It asks the person for access with OAuth, from a tablet at the reception desk or a link sent before the visit, using a confidential application (the clinic's server keeps the secret). The person chooses their record and how long the clinic may access it. The steps are in How to implement patient consent with SMART on FHIR; the token's patient field is the id to store with your file.
Step 3: write a visit as one transaction
A visit produces several resources that belong together. Send them in a transaction so they are stored all at once or not at all, and say who is working: X-Anpheros-Acting-As names the doctor's role. It becomes the author in Provenance, is written to the access log, and is shown to the patient by name.
curl -X POST $BASE "${H[@]}" \
-H "X-Anpheros-Acting-As: PractitionerRole/$ROLE" \
-H "X-Anpheros-Author-Type: practitioner" \
-H "X-Anpheros-Source-System: ccn-emr" -d '{
"resourceType": "Bundle", "type": "transaction",
"entry": [
{"fullUrl": "urn:uuid:aa000000-0000-4000-8000-000000000001",
"resource": {"resourceType": "Encounter", "status": "finished",
"class": {"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", "code": "AMB", "display": "ambulatory"},
"subject": {"reference": "Patient/'$PID'"},
"serviceProvider": {"reference": "Organization/'$ORG'"},
"participant": [{"individual": {"reference": "PractitionerRole/'$ROLE'"}}],
"period": {"start": "2026-10-09T10:00:00Z", "end": "2026-10-09T10:30:00Z"}},
"request": {"method": "POST", "url": "Encounter"}},
{"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": "8867-4", "display": "Heart rate"}]},
"subject": {"reference": "Patient/'$PID'"},
"encounter": {"reference": "urn:uuid:aa000000-0000-4000-8000-000000000001"},
"effectiveDateTime": "2026-10-09T10:10:00Z",
"valueQuantity": {"value": 92, "unit": "/min", "system": "http://unitsofmeasure.org", "code": "/min"}},
"request": {"method": "POST", "url": "Observation"}},
{"resource": {"resourceType": "Condition",
"subject": {"reference": "Patient/'$PID'"},
"encounter": {"reference": "urn:uuid:aa000000-0000-4000-8000-000000000001"},
"clinicalStatus": {"coding": [{"system": "http://terminology.hl7.org/CodeSystem/condition-clinical", "code": "active"}]},
"verificationStatus": {"coding": [{"system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", "code": "provisional"}]},
"code": {"coding": [{"system": "http://hl7.org/fhir/sid/icd-10", "code": "I48.0", "display": "Paroxysmal atrial fibrillation"}]}},
"request": {"method": "POST", "url": "Condition"}}
]}'
The transaction-response has 201 Created for the Encounter, the Observation and the Condition. The Observation and the Condition now point to the real Encounter/0ca25459-…. The Provenance of each names the doctor's role, the clinic and the source system:
"agent": [{"who": {"reference": "PractitionerRole/e6d8c227-…",
"identifier": {"system": "https://anpheros.com/fhir/CodeSystem/author-type", "value": "practitioner"}},
"onBehalfOf": {"reference": "Organization/c3a56a4b-…", "display": "Clinica Cardio Nord"}}],
"entity": [{"role": "source", "what": {"identifier": {"system": "ccn-emr"}}}]
The diagnosis is provisional until it is confirmed. Update it to confirmed later, and the history keeps both. What each field means is explained in Medical data provenance explained.
Step 4: laboratory results and documents
Results from the clinic's own laboratory, or reports the patient brings, go through the lab connector in one request per report:
curl -X POST "https://platform.anpheros.com/v1/patients/$PID/labs/import" -H "Authorization: Bearer $KEY" \
-H "Content-Type: x-application/hl7-v2+er7" -H "X-Anpheros-Source-System: ccn-lab" --data-binary @oru.hl7
Letters and discharge summaries are documents: create a DocumentReference through /v1/patients/{id}/documents and upload the PDF. The original file is never modified, and values extracted from it point back to it. See How to import laboratory results into a FHIR record and Medical data API.
Step 5: read what other providers wrote, and stay in your lane
With the patient's consent, the clinic reads the whole record within its scopes, including what other clinics, laboratories and the patient added. It can change only what it wrote itself. An update or deletion of another organisation's resource answers 403, and the original stays as it was. To correct something another provider wrote, write your own resource (for example a new Condition with your assessment) and let the record show both, each with its author.
Before a visit, GET $BASE/Patient/$PID/$summary gives the International Patient Summary of the record: problems, medications, allergies and recent vital signs, from every source.
Step 6: hear about new data
Register a webhook once for the clinic's project:
curl -X POST https://platform.anpheros.com/v1/webhooks -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"url": "https://ccn.example/anpheros/hook", "events": ["resource.created", "resource.updated", "consent.revoked"]}'
When another project writes into a record your clinic can access, the event says so with data.external: true and the source, so your EMR can show "new results from another laboratory" without polling. A consent.revoked event means the person has withdrawn the clinic's access: stop syncing that record. Signatures, retries and payloads are covered in Webhooks.
What the patient sees
The patient sees the visit, the heart rate and the provisional diagnosis in their record, each marked as added by Dr. Ana Popescu at Clinica Cardio Nord. In their connections, the clinic's application lists every access, by name when a practitioner was acting. That transparency is what makes patients willing to grant access to the next clinic.
Checklist for going live
- [ ] Organization, Practitioners and PractitionerRoles registered; leavers set to inactive
- [ ] Patients matched by the clinic's identifier, never by name
- [ ] Existing records reached through OAuth consent, not duplicated
- [ ] Every request from clinical staff carries
X-Anpheros-Acting-As - [ ] Visits written as transactions; diagnoses with the right verification status
- [ ] Source system set on every write; lab reports through the connector
- [ ] Webhook endpoint verifying signatures;
consent.revokedhandled - [ ] Production project with a signed DPA (
sk_live_…keys); see Pricing
Frequently asked questions
Does Anpheros have a ready-made connector for my EMR?
No. There are no ready-made connectors to specific EMRs or laboratory systems; the integration is built against the FHIR R4 and REST APIs, usually as a small service next to the EMR. HL7 v2 lab messages are accepted as they are.
Can a doctor work for two clinics?
Yes. One Practitioner can have a PractitionerRole at each organisation. The role named in X-Anpheros-Acting-As decides which clinic each write is recorded for.
What happens when the patient revokes the clinic's access?
The clinic's calls for that record answer 401, and a consent.revoked webhook is sent. What the clinic wrote stays in the patient's record with its provenance; the clinic just can no longer read or change it.