ediverse Explore the platform

Spotlight PEPPOL BIS Billing 3.0 The EU e-invoicing mandate is here — France Sept 2026, Belgium Jan 2026, Germany 2025.

Appointment — Booking of a healthcare event

The FHIR scheduling building block: medical appointments, physical therapy sessions, telehealth visits, operating-room bookings. It binds patient, practitioner, location and time slot into a single resource.

Purpose of the resource

Appointment represents the booking of a healthcare event for one or more patients, practitioners, RelatedPersons and/or devices at a specific date and time. It accommodates both clinical scheduling and community-style appointment systems (walk-in clinics, follow-up calls) and supports recurring appointments via recurrenceTemplate.

The resource is purposely orthogonal to Schedule (the practitioner's availability calendar) and Slot (a time window inside that calendar). The canonical pattern: a Schedule exposes free Slots, the client app creates an Appointment that references the consumed Slot(s), and those Slot(s) flip to busy.

Key fields

The R5 StructureDefinition publishes 32 top-level elements. The most relevant in practice:

FieldTypeCardinalityRole
identifierIdentifier[]0..*External identifiers of the appointment (EHR business number, voucher, patient barcode).
statuscode1..1Lifecycle status. Modifier — changes the meaning of the appointment.
cancellationReasonCodeableConcept0..1Cancellation reason. Only present when status=cancelled or noshow.
classCodeableConcept[]0..*Appointment classification (ambulatory, inpatient, emergency…).
serviceCategoryCodeableConcept[]0..*Broad category (General Practice, Dental, Imaging…).
serviceTypeCodeableReference[]0..*Specific service (tooth extraction, brain MRI…).
specialtyCodeableConcept[]0..*Practitioner specialty (SNOMED CT 394814009 = General practice).
appointmentTypeCodeableConcept0..1Functional type (FOLLOWUP, ROUTINE, WALKIN, EMERGENCY… — v2-0276).
reasonCodeableReference[]0..*Clinical reason (may reference a Condition or an Observation).
priorityCodeableConcept0..1Priority (R = Routine, EM = Emergency… — v3-ActPriority).
startinstant0..1Effective start datetime. instant = second precision + mandatory timezone.
endinstant0..1End datetime.
minutesDurationpositiveInt0..1Duration in minutes (redundant with start/end but useful for not-yet-positioned appointments).
slotReference(Slot)[]0..*The Slot(s) consumed by this appointment (a long appointment can consume multiple consecutive Slots).
requestedPeriodPeriod[]0..*Requested time window when the appointment is in proposed but not yet positioned.
subjectReference(Patient | Group)0..1Primary subject — typically the patient.
participantBackboneElement[]1..*All actors involved — patient, practitioner, location, device. Each participant carries actor, required, status.
basedOnReference(CarePlan | DeviceRequest | MedicationRequest | ServiceRequest)[]0..*The appointment is created in execution of a prior care request.
recurrenceTemplateBackboneElement[]0..*Recurrence definition (weekly, monthly…) using an RRULE-like rule.
virtualServiceVirtualServiceDetail[]0..*Video-conferencing details (URL, service type, access code) for telehealth.
patientInstructionCodeableReference[]0..*Patient-facing instructions (fast before, bring prescription, arrive 10 min ahead…).

Lifecycle statuses

The status code is mandatory and carries the appointment's business meaning. HL7 publishes the following values (Required binding):

CodeMeaning
proposedAppointment suggested but no date set. Awaiting confirmation by participants.
pendingAppointment positioned, awaiting acceptance by all participants.
bookedConfirmed. Slot locked.
arrivedPatient has arrived (check-in to the waiting room).
fulfilledAppointment took place (the associated Encounter is usually already created).
cancelledCancelled. cancellationReason carries the reason.
noshowPatient did not show up. Distinct from cancelled for reporting purposes.
entered-in-errorErroneous entry — fix upstream.
checked-inFront-desk check-in (alternative to arrived, used by IT workflows).
waitlistWait list — patient will be contacted on cancellation.

JSON example

A 20-minute follow-up appointment in a GP practice's system, on 22 May 2026 at 09:00 Paris time, between Dr Adam Careful and patient Peter Chalmers, in the "South Wing, second floor" location.

json appointment-example.json
{
  "resourceType": "Appointment",
  "id": "example",
  "status": "booked",
  "serviceCategory": [{
    "coding": [{
      "system": "http://example.org/service-category",
      "code": "gp",
      "display": "General Practice"
    }]
  }],
  "serviceType": [{
    "concept": {
      "coding": [{
        "system": "http://example.org/service-type",
        "code": "52",
        "display": "General Discussion"
      }]
    }
  }],
  "specialty": [{
    "coding": [{
      "system": "http://snomed.info/sct",
      "code": "394814009",
      "display": "General practice"
    }]
  }],
  "appointmentType": {
    "coding": [{
      "system": "http://terminology.hl7.org/CodeSystem/v2-0276",
      "code": "FOLLOWUP",
      "display": "A follow up visit from a previous appointment"
    }]
  },
  "reason": [{
    "concept": {
      "coding": [{
        "system": "http://snomed.info/sct",
        "code": "413095006",
        "display": "Clinical mental health assessment"
      }]
    }
  }],
  "priority": {
    "coding": [{
      "system": "http://terminology.hl7.org/CodeSystem/v3-ActPriority",
      "code": "R",
      "display": "Routine"
    }]
  },
  "description": "Follow-up on last month's blood test results",
  "start": "2026-05-22T09:00:00+02:00",
  "end": "2026-05-22T09:20:00+02:00",
  "minutesDuration": 20,
  "slot": [{ "reference": "Slot/example" }],
  "created": "2026-05-14T10:30:00+02:00",
  "patientInstruction": [{
    "concept": {
      "text": "Please arrive 10 minutes ahead of your appointment time"
    }
  }],
  "participant": [{
    "actor": { "reference": "Patient/example", "display": "Peter James Chalmers" },
    "required": true,
    "status": "accepted"
  }, {
    "type": [{
      "coding": [{
        "system": "http://terminology.hl7.org/CodeSystem/v3-ParticipationType",
        "code": "ATND"
      }]
    }],
    "actor": { "reference": "Practitioner/example", "display": "Dr Adam Careful" },
    "required": true,
    "status": "accepted"
  }, {
    "actor": { "reference": "Location/1", "display": "South Wing, second floor" },
    "required": true,
    "status": "accepted"
  }]
}

Worth pointing out in this example:

  • The participant[] array lists three actors: patient, practitioner, location. Each has its own status (accepted/declined/tentative).
  • slot references a Slot resource; that Slot must flip to busy on the practitioner's Schedule.
  • start and end are instant with timezone: essential for multi-timezone apps.
  • patientInstruction carries free text shown to the patient.

Booking cycle

The canonical FHIR booking pattern combines three resources and two standard operations:

  1. The client app queries GET /Slot?schedule=Schedule/123&status=free&start=ge2026-05-22 to fetch the practitioner's free Slots.
  2. The user picks a slot; the app posts an Appointment via POST /Appointment with status=pending and a slot reference.
  3. The server validates availability, flips the referenced Slot to status=busy, and flips the Appointment to status=booked once all required participant[].status values are accepted.
  4. The server may expose a $find or $book operation as a convenience that bundles those steps (see US Core and Argonaut Scheduling profiles).
  5. When the patient arrives, the EHR sets the status to arrived or checked-in. Once the encounter has taken place, the status flips to fulfilled and an Encounter resource is created.

Common pitfalls

  • Slot not locked — creating an Appointment without flipping the referenced Slot(s) to busy allows double-booking. This synchronization is the server's responsibility, not the client's.
  • start/end without timezone — the FHIR instant type requires an offset. Dropping the timezone (2026-05-22T09:00:00 without suffix) is rejected by the validator.
  • participant[].required=true with status=declined — business inconsistency: a required participant that has declined can't leave the Appointment in booked.
  • Telehealth without virtualService — a video-mode Appointment must carry at least one virtualService entry with channelType and connection URL, otherwise the patient has no technical way to join the call.
  • Inconsistent recurrenceTemplate — a recurring template must describe occurrences reachable within requestedPeriod. An RRULE pointing outside that range is silently ignored by some servers.
  • Confusion cancelled / noshowcancelled = explicit cancellation before the appointment. noshow = patient did not come and did not cancel. They count differently in reporting and billing.
  • Schedule — the availability calendar of a practitioner, device or location.
  • Slot — each free/busy slot exposed by a Schedule.
  • AppointmentResponse — asynchronous response from a participant to an invitation.
  • Encounter — the healthcare interaction created from the appointment once the patient is present.
  • Patient, Practitioner — the main actors referenced from participant.
  • ServiceRequest, CarePlan, DeviceRequest — care orders that may trigger the Appointment via basedOn.

See also: the FHIR R5 index for the full resource list.