Creating a clinical encounter
Record a visit for an existing patient as a stored Encounter. You will authenticate, discover the practitioner the create requires as a participant, then POST the encounter with its status, class, patient subject, and period. Three scopes carry the flow:
subject resolves, and system/Practitioner.rs to discover the participant.
The participant is the part most first writes miss. An Encounter create requires at least one practitioner participant — the visit has to name who attended it. The discovery step below hands you a real Practitioner reference so the participant array satisfies that check, and the patient subject and class round out a body the server accepts.
Audience
You integrate an EHR and record visits into HuliPractice. You have already registered or resolved the patient, you read a Bundle without a viewer, and you know what a FHIR reference is. You want to take a visit from a patient plus a practitioner to a 201 Created Encounter.
You'll need
- A bearer token from HuliPractice (Practice Settings → Integrations → API Keys), or a SMART Backend Services access token. See Bearer Tokens for provisioning and
POST /auth/tokenfor the token exchange — this recipe assumes you already hold one. - These three scopes on that token:
- system/Encounter.cru — create
Encounter(.crualso grants read +search). - system/Patient.rs — read + search
Patient; the encounter'ssubjectmust reference a patient that resolves in your organization. - system/Practitioner.rs — read + search
PractitionerandPractitionerRoleto discover the participant.
- system/Encounter.cru — create
- The
idof the patient the visit is for. Resolve it with a Patient search if you only hold a name or identifier. curl, or Node, Python, Java, or Go if you prefer a language client.
End state
You hold a 201 Created whose body is the stored Encounter — with a server-assigned id, the patient as subject, the discovered practitioner in participant[0].individual, the ActCode class you chose, and the period you sent. The encounter is then readable and searchable by patient or practitioner.
Steps
1. Export the token and the patient id
export HULI_TOKEN="<paste your bearer token here>"
export PATIENT_ID="01965e2a-8c4d-7000-9001-0000000000a2"
Resolve a real patient id in your sandbox instead of the illustrative one above:
/fhir/R4/Patient?_count=1Set your sandbox token above to run this request.
2. Discover the practitioner
The encounter needs a practitioner participant. Search Practitioner by name to get the reference; the id of the matching entry is what you put in participant[0].individual.reference.
/fhir/R4/Practitioner?name=Fern%C3%A1ndezThe Practitioner search pages on offset pagination (_count + _offset), the same model the discovery resources use. If you also need the practitioner's rooms or specialties — for example to pre-check a downstream booking — read their PractitionerRole; for recording a completed visit, the Practitioner reference alone is enough.
curl "https://api.huli.ai/fhir/R4/Practitioner?name=Fern%C3%A1ndez&_count=20" \
-H "Authorization: Bearer $HULI_TOKEN" \
-H "Accept: application/fhir+json"
const params = new URLSearchParams({ name: 'Fernández', _count: '20' });
const resp = await fetch(`https://api.huli.ai/fhir/R4/Practitioner?${params}`, {
headers: {
Authorization: `Bearer ${process.env.HULI_TOKEN}`,
Accept: 'application/fhir+json',
},
});
const bundle = await resp.json();
// The participant reference is Practitioner/<entry.resource.id>.
const practitioner = bundle.entry?.[0]?.resource;
console.log(`Practitioner/${practitioner?.id}`);
import os
import requests
resp = requests.get(
"https://api.huli.ai/fhir/R4/Practitioner",
params={"name": "Fernández", "_count": 20},
headers={
"Authorization": f"Bearer {os.environ['HULI_TOKEN']}",
"Accept": "application/fhir+json",
},
timeout=30,
)
bundle = resp.json()
# The participant reference is Practitioner/<entry.resource.id>.
practitioner = bundle["entry"][0]["resource"]
print(f"Practitioner/{practitioner['id']}")
/fhir/R4/Practitioner?_count=1Set your sandbox token above to run this request.
3. POST the Encounter
Assemble the discovered practitioner and the patient into the create body: status (use finished for a completed visit or in-progress while it is ongoing), the ActCode class, the patient subject, a participant array naming the practitioner, and the period.
The body below is the comprehensive form — every field the create decoder honors on an Encounter write, including the optional appointment link, the visit reasonCode, and the contained clinical resources (an ICD-10 Condition for a diagnosis and a ClinicalImpression for the subjective summary). Required fields are flagged inline; the Full field reference after the example lists each field and whether the decoder reads it on write. A minimal write needs only status, subject, and one participant.
/fhir/R4/Encountercurl -i -X POST https://api.huli.ai/fhir/R4/Encounter \
-H "Authorization: Bearer $HULI_TOKEN" \
-H "Content-Type: application/fhir+json" \
-H "Accept: application/fhir+json" \
-d '{
"resourceType": "Encounter",
"status": "finished",
"class": {
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
"code": "AMB",
"display": "ambulatory"
},
"subject": {
"reference": "Patient/01965e2a-8c4d-7000-9001-0000000000a2"
},
"participant": [
{
"individual": {
"reference": "Practitioner/01965e2a-8c4d-7000-9001-0000000000c1"
}
}
],
"appointment": [
{
"reference": "Appointment/01965e2a-8c4d-7000-9050-0000000000e1"
}
],
"period": {
"start": "2026-06-15T09:00:00.000-06:00",
"end": "2026-06-15T09:30:00.000-06:00"
},
"reasonCode": [
{ "text": "Control de hipertensión" }
],
"contained": [
{
"resourceType": "Condition",
"id": "condition-1",
"code": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/icd-10",
"code": "I10",
"display": "Hipertensión esencial (primaria)"
}
],
"text": "Hipertensión esencial (primaria)"
},
"subject": { "reference": "Patient/01965e2a-8c4d-7000-9001-0000000000a2" }
},
{
"resourceType": "ClinicalImpression",
"id": "clinical-impression-1",
"status": "completed",
"subject": { "reference": "Patient/01965e2a-8c4d-7000-9001-0000000000a2" },
"summary": "Paciente refiere cefalea ocasional; sin otros síntomas."
}
]
}'
const encounter = {
resourceType: 'Encounter',
status: 'finished', // required — FHIR R4 status; Huli's "completed" maps to this
class: {
// optional — only class.code is read; AMB | EMER | IMP | VR (defaults to AMB if omitted)
system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode',
code: 'AMB',
display: 'ambulatory',
},
subject: { reference: `Patient/${process.env.PATIENT_ID}` }, // required
participant: [
// At least one practitioner participant is required; participant[0].individual is read.
{ individual: { reference: 'Practitioner/01965e2a-8c4d-7000-9001-0000000000c1' } },
],
appointment: [
// optional — links the visit to the appointment that scheduled it (appointment[0] read)
{ reference: 'Appointment/01965e2a-8c4d-7000-9050-0000000000e1' },
],
period: {
start: '2026-06-15T09:00:00.000-06:00',
end: '2026-06-15T09:30:00.000-06:00', // omit for an in-progress visit
},
reasonCode: [{ text: 'Control de hipertensión' }], // optional — reasonCode[0].text read
contained: [
{
// ICD-10 diagnosis — code.coding[0] + code.text read into the encounter's diagnoses
resourceType: 'Condition',
id: 'condition-1',
code: {
coding: [
{
system: 'http://hl7.org/fhir/sid/icd-10',
code: 'I10',
display: 'Hipertensión esencial (primaria)',
},
],
text: 'Hipertensión esencial (primaria)',
},
subject: { reference: `Patient/${process.env.PATIENT_ID}` },
},
{
// subjective summary — ClinicalImpression.summary read
resourceType: 'ClinicalImpression',
id: 'clinical-impression-1',
status: 'completed',
subject: { reference: `Patient/${process.env.PATIENT_ID}` },
summary: 'Paciente refiere cefalea ocasional; sin otros síntomas.',
},
],
};
const resp = await fetch('https://api.huli.ai/fhir/R4/Encounter', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HULI_TOKEN}`,
'Content-Type': 'application/fhir+json',
Accept: 'application/fhir+json',
},
body: JSON.stringify(encounter),
});
if (resp.status === 201) {
const created = (await resp.json()) as { id: string };
console.log('recorded', created.id);
} else {
const outcome = (await resp.json()) as { issue: { code: string; diagnostics: string }[] };
// issue[0].code is the FHIR IssueType; diagnostics describes the problem.
console.log(resp.status, outcome.issue[0].code, outcome.issue[0].diagnostics);
}
import os
import requests
patient_ref = f"Patient/{os.environ['PATIENT_ID']}"
encounter = {
"resourceType": "Encounter",
"status": "finished", # required — FHIR R4 status; Huli's "completed" maps to this
"class": {
# optional — only class.code is read; AMB | EMER | IMP | VR (defaults to AMB if omitted)
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
"code": "AMB",
"display": "ambulatory",
},
"subject": {"reference": patient_ref}, # required
"participant": [
# At least one practitioner participant is required; participant[0].individual is read.
{"individual": {"reference": "Practitioner/01965e2a-8c4d-7000-9001-0000000000c1"}},
],
"appointment": [
# optional — links the visit to the appointment that scheduled it (appointment[0] read)
{"reference": "Appointment/01965e2a-8c4d-7000-9050-0000000000e1"},
],
"period": {
"start": "2026-06-15T09:00:00.000-06:00",
"end": "2026-06-15T09:30:00.000-06:00", # omit for an in-progress visit
},
"reasonCode": [{"text": "Control de hipertensión"}], # optional — reasonCode[0].text read
"contained": [
{
# ICD-10 diagnosis — code.coding[0] + code.text read into the encounter's diagnoses
"resourceType": "Condition",
"id": "condition-1",
"code": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/icd-10",
"code": "I10",
"display": "Hipertensión esencial (primaria)",
}
],
"text": "Hipertensión esencial (primaria)",
},
"subject": {"reference": patient_ref},
},
{
# subjective summary — ClinicalImpression.summary read
"resourceType": "ClinicalImpression",
"id": "clinical-impression-1",
"status": "completed",
"subject": {"reference": patient_ref},
"summary": "Paciente refiere cefalea ocasional; sin otros síntomas.",
},
],
}
resp = requests.post(
"https://api.huli.ai/fhir/R4/Encounter",
headers={
"Authorization": f"Bearer {os.environ['HULI_TOKEN']}",
"Content-Type": "application/fhir+json",
"Accept": "application/fhir+json",
},
json=encounter,
timeout=30,
)
if resp.status_code == 201:
print("recorded", resp.json()["id"])
else:
outcome = resp.json()
# issue[0].code is the FHIR IssueType; diagnostics describes the problem.
print(resp.status_code, outcome["issue"][0]["code"], outcome["issue"][0]["diagnostics"])
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
// status "finished" is the FHIR token for a completed visit (Huli's
// "completed" maps to it). class is the fixed ActCode set; at least one
// practitioner participant is required. appointment, reasonCode, and the
// contained Condition/ClinicalImpression are optional enrichment the create
// decoder honors.
patientRef := "Patient/" + os.Getenv("PATIENT_ID")
body := []byte(`{
"resourceType": "Encounter",
"status": "finished",
"class": {
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
"code": "AMB",
"display": "ambulatory"
},
"subject": {"reference": "` + patientRef + `"},
"participant": [
{"individual": {"reference": "Practitioner/01965e2a-8c4d-7000-9001-0000000000c1"}}
],
"appointment": [
{"reference": "Appointment/01965e2a-8c4d-7000-9050-0000000000e1"}
],
"period": {
"start": "2026-06-15T09:00:00.000-06:00",
"end": "2026-06-15T09:30:00.000-06:00"
},
"reasonCode": [{"text": "Control de hipertensión"}],
"contained": [
{
"resourceType": "Condition",
"id": "condition-1",
"code": {
"coding": [{
"system": "http://hl7.org/fhir/sid/icd-10",
"code": "I10",
"display": "Hipertensión esencial (primaria)"
}],
"text": "Hipertensión esencial (primaria)"
},
"subject": {"reference": "` + patientRef + `"}
},
{
"resourceType": "ClinicalImpression",
"id": "clinical-impression-1",
"status": "completed",
"subject": {"reference": "` + patientRef + `"},
"summary": "Paciente refiere cefalea ocasional; sin otros síntomas."
}
]
}`)
req, err := http.NewRequest(http.MethodPost,
"https://api.huli.ai/fhir/R4/Encounter", bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("HULI_TOKEN"))
req.Header.Set("Content-Type", "application/fhir+json")
req.Header.Set("Accept", "application/fhir+json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
switch resp.StatusCode {
case http.StatusCreated:
fmt.Printf("201 recorded\n%s\n", out)
case http.StatusBadRequest: // structural validation (missing participant, bad class/status)
fmt.Printf("400 validation\n%s\n", out)
case http.StatusUnprocessableEntity: // a subject/practitioner reference that does not resolve
fmt.Printf("422 reference not found\n%s\n", out)
case http.StatusConflict: // patient merged (HPB-00108) or deceased (HPB-00109)
fmt.Printf("409 patient not writable\n%s\n", out)
default:
fmt.Printf("%d\n%s\n", resp.StatusCode, out)
}
}
/fhir/R4/EncounterSet your sandbox token above to run this request.
A 201 Created returns the stored Encounter with a server-assigned id. The class mapping round-trips (the AMB ActCode you sent comes back as AMB), and the participant carries the practitioner you discovered.
Full field reference
Every field the Encounter create decoder reads on write. "Honored" means the create decoder maps the field into the stored visit; fields not listed (or marked ignored) are accepted but not persisted from your input. Required: status, subject, and at least one participant.
| Field | Req? | Honored on write | Notes |
|---|---|---|---|
status | required | yes | FHIR status; send finished (maps to Huli completed) or in-progress. |
subject.reference | required | yes | Patient/<uuid> — must resolve in the organization (else 422). |
participant[0].individual.reference | required | yes | Practitioner/<uuid> — at least one participant is required; the first individual is read. |
class.code | optional | yes | One of AMB/EMER/IMP/VR; maps to ambulatory/emergency/inpatient/virtual. Absent defaults to AMB. |
class.system / display | optional | ignored | Re-emitted from the code mapping on read. |
appointment[0].reference | optional | yes | Appointment/<uuid> linking the visit to its scheduling appointment. |
period.start | optional | yes | Visit start. |
period.end | optional | yes | Visit end; omit for an in-progress visit. |
reasonCode[0].text | optional | yes | Free-text reason for the visit. Only the first entry's text is read. |
contained[] Condition | optional | yes | ICD-10 diagnosis: code.coding[0] (system/code/display) + code.text are read into the encounter's diagnoses. |
contained[] ClinicalImpression | optional | yes | summary is read as the subjective note. |
participant[].type / period | optional | ignored | Only individual is consumed on write. |
diagnosis[] | optional | ignored on write | Built on read from the contained Conditions — send diagnoses as contained Conditions, not as diagnosis[] references. |
serviceProvider | optional | ignored | The server stamps the token's organization. |
What to verify
- HTTP status is
201. 201 Created - The response body's
resourceTypeisEncounterand it carries a server-assignedid. statusisfinished(orin-progressif ongoing) andclass.codeis the ActCode you sent.subject.referenceresolves to yourPATIENT_ID, andparticipant[0].individual.referenceis the practitioner you discovered in step 2.period.startmatches what you sent;period.endis present for a finished visit and absent for an in-progress one.
What can go wrong
All errors return a FHIR OperationOutcome, never a bare string — {severity, code, diagnostics}, with no details object. Branch on the HTTP status and issue[0].code (the FHIR IssueType) for machine classification; the diagnostics string describes the specific problem. Structural problems on a writable resource are the HPB-00101 validation family.
participant with a Practitioner individual reference — an Encounter records who attended the visit. A body with an empty or absent participant array is rejected with issue[0].coderequired and a diagnostics of "at least one participant (practitioner) is required". Discover the practitioner in step 2 and build the participant before posting.
subject (apatient reference) and status are required; class.code must be one of AMB, EMER, IMP, VR. On create, status is restricted to the four round-trippable states — planned, in-progress, finished, cancelled; the transitional/terminal markers (arrived, triaged, onleave, entered-in-error, unknown) are rejected with Encounter.status must be one of: planned, in-progress, finished, cancelled on create. Huli's internal completed is not a FHIR status (send finished), and a class code outside the ActCode set also fails. (A PUT accepts the broader FHIR status set, governed by the status-transition table.) Send finished/in-progress and a valid ActCode.
subject orparticipant.individual reference whose UUID is well-formed but does not name a patient / practitioner in your organization is rejected with a diagnostics of "Referenced Patient not found in organization" (or "Referenced Practitioner not found in organization"). Resolve both against Patient / Practitioner search first, and confirm the token's organization owns them.
been merged into another record (HPB-00108) or marked deceased (HPB-00109) is rejected — the encounter would attach clinical data to a patient that can no longer take it. Resolve the surviving record (for a merge) or stop, and do not retry the same subject.
A representative 400 body for the missing-participant case:
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "required",
"diagnostics": "at least one participant (practitioner) is required",
"expression": ["Encounter.participant"]
}
]
}
Next recipes
- Send lab results to the chart — record the visit's vitals and lab results as
Observationresources linked to this encounter. - Registering a patient — onboard the patient first when the subject does not yet exist, including the MX NOM-024 address path.
- Authenticate as a SMART Backend Service — swap the admin bearer token for
client_credentials+private_key_jwt(RS384, 5-minute access token) when you ship the encounter flow server-to-server.