Huli FHIR Core Implementation Guide (R4)
0.1.0 - Release
Huli FHIR Core Implementation Guide (R4) - Local Development build (v0.1.0) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions
| FHIR path | Cardinality | Must Support | Justification |
|---|---|---|---|
Appointment.id |
1..1 | – | UUID primary key; the FHIR id is the appointment id alone |
Appointment.meta.lastUpdated |
0..1 | – | Auto-updated trigger |
Appointment.created |
0..1 | – | Maps to standard FHIR element |
Appointment supports optimistic concurrency control so concurrent editors do
not silently clobber each other:
Appointment.meta.versionId, a weak
ETag header (W/"<versionId>"), and a Last-Modified header derived from
meta.lastUpdated.PUT /fhir/R4/Appointment/{id} with an If-Match
header carrying the version the client last read — is accepted only when the
supplied version still matches the stored version. A stale If-Match
(the resource changed since the client read it) is rejected with
409 Conflict; the client should re-read and retry against the current
version.PUT (no If-Match) is still accepted — concurrency
enforcement is opt-in by sending If-Match.| FHIR path | Cardinality | Must Support | Notes |
|---|---|---|---|
Appointment.meta.versionId |
0..1 | Yes | Monotonic per-resource version; mirrored in the weak ETag header |
| FHIR path | Cardinality | Must Support | Justification |
|---|---|---|---|
Appointment.status |
1..1 | Yes | Required; status code |
Value mapping:
| Huli code | FHIR code | Display (ES) |
|---|---|---|
| proposed | proposed | Propuesta |
| pending | pending | Pendiente |
| booked | booked | Confirmada |
| arrived | arrived | Llegada |
| fulfilled | fulfilled | Completada |
| cancelled | cancelled | Cancelada |
| noshow | noshow | No Asistio |
| entered_in_error | entered-in-error | Error de Captura |
All codes are already FHIR-aligned.
| FHIR path | Cardinality | Must Support | Justification |
|---|---|---|---|
Appointment.start |
0..1 | Yes | Lower bound of the appointment time slot |
Appointment.end |
0..1 | Yes | Upper bound of the appointment time slot |
| FHIR path | Cardinality | Must Support | Justification |
|---|---|---|---|
Appointment.appointmentType |
0..1 | – | Appointment type code |
Read-only on write.
Appointment.appointmentTypeis server-derived from the chosenserviceTypeand is emitted on read only. The write decoder ignores anyappointmentTypein the request body.
Value mapping:
| Huli code | SNOMED code | FHIR display | Display (ES) |
|---|---|---|---|
| routine | 410620009 | Well patient encounter | Primera Vez |
| followup | 185389009 | Follow-up visit | Seguimiento |
| procedure | 308335008 | Patient encounter procedure | Procedimiento |
| emergency | 373110003 | Emergency encounter | Urgencia |
| telehealth | 448337001 | Telemedicine encounter | Telemedicina |
| walkin | 310121005 | Administrative encounter | Administrativo |
| FHIR path | Cardinality | Must Support | Justification |
|---|---|---|---|
Appointment.specialty[0] |
0..* | – | References a specialty (SNOMED coded) |
Appointment.specialty[0].text |
0..1 | – | Resolved display name |
Writable for multi-specialty services. On read,
Appointment.specialty[0].textcarries the resolved display name. On write the API decodes the chosen specialty from aspecialtycoding under the Huli specialtyCodeSystem(system=https://fhir.huli.ai/r4/CodeSystem/specialty,code= the specialty UUID — the value aHealthcareServiceadvertises for a multi-specialty offering). It is optional: a single-specialty (or "all") service derives the specialty server-side, so a human/SNOMED-onlyspecialtycoding is ignored. When the chosenserviceTypeoffers two or more specialties, a specialty MUST be selected — omitting it is rejected422(HPB-00115), and a specialty the service does not offer is rejected422(HPB-00116). A present-but- malformed specialty code (non-UUID) is rejected422(value,Appointment.specialty).
| FHIR path | Cardinality | Must Support | Justification |
|---|---|---|---|
Appointment.cancelationReason |
0..1 | – | Cancellation reason code |
Value mapping:
| Huli code | SNOMED code | FHIR text |
|---|---|---|
| patient_request | 185332005 | Solicitud del Paciente |
| provider_request | 185333000 | Solicitud del Proveedor |
| schedule_conflict | – | Conflicto de Horario |
| emergency | – | Emergencia |
| weather | – | Clima |
| equipment_failure | – | Falla de Equipo |
| patient_condition | 185332005 | Condicion del Paciente |
| other | – | Otro |
| FHIR path | Cardinality | Must Support | Justification |
|---|---|---|---|
Appointment.description |
0..1 | – | Nullable TEXT |
Appointment.patientInstruction |
0..1 | – | Nullable TEXT |
Appointment.priority |
0..1 | – | INTEGER, 0=undefined |
| FHIR path | Cardinality | Must Support | Justification |
|---|---|---|---|
Appointment.participant[patient].actor |
0..1 | – | Nullable UUID (walk-ins) -> Reference(Patient) |
Appointment.participant[practitioner/room/equipment] |
1..* | Yes | The appointment's resource participants |
Participant mapping:
| Huli resource type | FHIR actor type |
|---|---|
| practitioner | Reference(Practitioner) |
| room | Reference(Location) |
| equipment | Reference(Device) |
Room reference id-space. A room participant is emitted as
Reference(Location)keyed on the room's physical-location id — the same idGET/SEARCH /Locationresolves and thatSchedule/HealthcareServicereference — not an internal scheduling id. So theLocationreference an integrator reads off anAppointmentresolves viaGET /Location/{id}and appears inLocationsearch (discover → book closes through the API). On write the API resolves thatLocationid back to the bookable room resource; aLocationthat is not an active bookable room (a site/building, or a room with no bookable schedule) is rejected400(value).
Practitioner participant id-space. The practitioner participant is emitted as
Reference(Practitioner)keyed on thePractitionerRole.id(the bookable schedulable resource thatSchedule/Slot/PractitionerRolediscovery surfaces) — NOT the practitioner-user id thatPractitionerRole.practitionerpoints at. The reference type isPractitionerfor FHIR conformance, but the id-space is the practitioner-role. On write, anactorreferencing a practitioner-user id (rather than the role/schedulable-resource id) is not a bookable resource and is rejected404— discover the role, book the role.
| Huli participant type | FHIR required |
|---|---|
| required | required |
| optional | optional |
| information_only | information-only |
| Huli participant status | FHIR status |
|---|---|
| accepted | accepted |
| declined | declined |
| tentative | tentative |
| needs_action | needs-action |
| FHIR path | Cardinality | Must Support | Justification |
|---|---|---|---|
Appointment.extension[confirmationStatus] |
0..1 | Yes | Separate patient confirmation workflow |
Read-only on write. The confirmation-status extension is server-derived (it tracks the separate patient-confirmation workflow) and is emitted on read only. The write decoder ignores any
extension[confirmationStatus]in the request body.
Confirmation status mapping (codes the runtime emits):
| Huli id | FHIR extension value |
|---|---|
| 1 | unconfirmed |
| 2 | confirmed |
| 3 | cancelled |
An administrative meeting is an appointment booked against a service whose appointment type
is administrative. Administrative meetings REQUIRE a title, do NOT carry a patient
participant, and MAY carry external email invitees; clinical appointments reject all three. A room
(Location) participant is not required for an administrative meeting (the room requirement in
the Participants table above applies to clinical/telehealth appointments only). These fields
round-trip through dedicated extensions (read + write):
Extension URL (under https://fhir.huli.ai/r4/StructureDefinition/) |
Value | Read | Write | Notes |
|---|---|---|---|---|
huli-appointment-title |
valueString |
Yes | Yes | The meeting title. Required for administrative appointments (required, Appointment.extension(huli-appointment-title)); rejected on clinical appointments (value). On a PUT full-replace, omitting it clears the stored title. |
huli-appointment-all-day |
valueBoolean |
Yes (emitted only when true) |
Yes | Marks an all-day meeting. The service enforces whole-local-day (midnight-to-midnight) bounds. |
huli-appointment-external-attendee |
nested email (valueString, required) + displayName (valueString, optional) |
Yes — single-resource GET / create / update only (PII; never on the search keyset path) | Yes | Repeats per invitee, max 30. On a PUT, supplying the extension(s) replaces the invitee set; omitting it leaves the set unchanged. |
A clinical→administrative (or reverse) service flip is supported on
PUT: because a FHIRPUTis a full replace, an omittedhuli-appointment-titleextension clears the title and an omittedPatientparticipant clears the patient, letting the resulting appointment satisfy the target kind's rules.
Invite emails + ICS are sent over FHIR. Creating, cancelling, or updating an administrative meeting through this API emails its
huli-appointment-external-attendeeinvitees with a calendar (ICS) invite — the same dispatch the in-app path uses: a create sends anICSREQUEST, a cancel sends a CANCEL, and an update diffs the attendee set (added → REQUEST, removed → CANCEL, retained → rescheduled REQUEST only when the time changed). Delivery is post-commit and at-least-once (a durable per-invitee outbox row recovered by the confirmation sweeper), so a successful write reliably notifies attendees.
Public FHIR Appointment writes route through the same scheduling service the in-app calendar uses, so the public API enforces the app's scheduling rules rather than a thin direct insert. This means the request body must name the resources an appointment needs, and writes can be rejected by the same business rules the app applies.
| FHIR path | Cardinality (write) | Notes |
|---|---|---|
Appointment.serviceType[0].coding[0] |
1..1 | system = https://fhir.huli.ai/r4/CodeSystem/org-service, code = the org service UUID. The service derives appointment type, specialty, booking policy, and per-service resource requirements. A missing or unresolvable serviceType is rejected 422 (required / value). The previous behaviour — silently defaulting to the organization's first active service — has been removed. |
A create / field-edit must include the resource participants; they are persisted on the appointment (an earlier revision dropped them):
| FHIR actor type | Huli resource type | Cardinality (write) |
|---|---|---|
Reference(Practitioner) |
practitioner | 1..* (at least one) |
Reference(Location) |
room | 1..1 (required) |
Reference(Device) |
equipment | 0..* |
Reference(Patient) |
patient | 0..1 |
The Reference(Location) room must be the discoverable physical-room
Location id (the one returned by GET/SEARCH /Location); the API resolves it
to the bookable room resource. A Location that is not an active bookable room is
rejected 400 (value, Appointment.participant.actor).
The create and field-edit run double-booking detection and availability /
calendar-scope checks across every participant resource. A slot that overlaps an
existing booking for a participant is rejected with 409 Conflict — the app
would have refused the same booking. Telehealth services additionally
auto-provision a telemedicine session inside the same transaction.
The target status on a PUT selects the operation; the two terminal
data-removal states route to different app methods with different permissions:
FHIR status |
App operation | Permission | Guards |
|---|---|---|---|
cancelled |
cancel | appointments.delete |
Requires a cancelationReason that resolves to one of the org's active cancellation reasons (by coding code or SNOMED coding); blocked 409 when a clinical encounter is linked to the appointment; comment maps to the cancellation note. |
entered-in-error |
status → entered-in-error | appointments.edit |
A data-entry correction, not a cancellation; no reason required. |
| anything else | reschedule / edit | appointments.edit |
Re-runs conflict/availability/scope checks. |
This closes the prior drift where a PUT status=cancelled bypassed the cancel
permission, the required-reason check, and the linked-encounter block.
A successful create / cancel / edit updates open staff calendars live and records an audit-trail entry. (Reminder and confirmation-email dispatch are tracked as a follow-up — every scheduling guard, the audit trail, and the live calendar update ship on the FHIR path today.)
GET /Appointment supports the following search parameters (cursor-paginated; see the
CapabilityStatement):
| Parameter | Type | Filters on |
|---|---|---|
_id |
token | The appointment UUID. |
patient |
reference | Patient/{id}. |
practitioner |
reference | Practitioner/{id} (the schedulable-resource id emitted on read). |
date |
date | The appointment start (prefix-aware: eq/gt/ge/lt/le). |
status |
token | The FHIR appointment status code. |
appointment-type |
token | The service's appointment-type code (e.g. administrative, routine) — lets a client list only administrative meetings or only clinical bookings. |
The keyset search path emits the
huli-appointment-title/huli-appointment-all-dayextensions but NEVER thehuli-appointment-external-attendeeextensions — external invitee emails are PII and surface only on the single-resourceGET/create/update responses.
| Huli concept | Reason |
|---|---|
| Operational notes | Not exposed (D-008); staff-only operational notes are never surfaced through this profile |
| Soft-delete status | Not exposed; internal lifecycle |
| Recurrence pattern | Not exposed; no FHIR R4 recurrence model |
| Recurrence exception flag | Not exposed; scheduling internal |
| Recurrence original start | Not exposed; scheduling internal |
| Reschedule count | Not exposed; analytics metric |
| Custom color | Not exposed; pure UI concern |
| Overbook flag | Not exposed; operational detail |
| Courtesy flag | Not exposed; billing detail |
| Advance-request flag | Not exposed; workflow detail |
| Insurance snapshot | Carried by the HuliInsuranceSnapshot Appointment extension (see Extensions) |
| Attachments | Not modelled through FHIR in this IG |
| Custom fields | Unstructured; no FHIR mapping |
| Created-by | Audit metadata (emitted as the huli-created-by extension) |
| Modified-by | Audit metadata (emitted as the huli-modified-by extension) |
| Cancelled-by | Audit metadata (emitted in the huli-cancellation-info extension) |
| Cancelled-on | Audit metadata (emitted in the huli-cancellation-info extension) |
| Cancellation notes | Not exposed; operational detail |
| New-patient flag | Not exposed; computed flag |
| Warnings | Not exposed; runtime computed |
| Location | Not exposed; derived from the participant's schedule |
| Telemedicine | Not modelled through FHIR in this IG |
| Tags | Not modelled through FHIR in this IG |