Skip to main content

Appointment $book

Alpha

The $book operation is currently in alpha. It supports only Schedules with a single actor.

The $book operation books an Appointment by atomically creating the Appointment, one or more busy Slot resources, and any required buffer Slots in a single FHIR transaction. The operation validates that the requested time is genuinely available before committing.

Use Cases

  • Single-provider booking: Book a patient into an open slot returned by $find
  • Multi-resource booking: Simultaneously book multiple Schedules (e.g., surgeon + OR room + anesthesiologist) for the same appointment time
  • Programmatic scheduling: Automate appointment creation from external systems while respecting provider availability rules

Invoke the $book operation

[base]/R4/Appointment/$book
import { MedplumClient } from '@medplum/core';
import type { Appointment, Bundle, Parameters, Slot } from '@medplum/fhirtypes';

const medplum = new MedplumClient();

const result = await medplum.post(
medplum.fhirUrl('Appointment', '$book'),
{
resourceType: 'Parameters',
parameter: [
{
name: 'slot',
resource: {
resourceType: 'Slot',
status: 'free',
start: '2026-03-10T09:00:00.000Z',
end: '2026-03-10T10:00:00.000Z',
schedule: { reference: 'Schedule/my-schedule-id' },
} satisfies Slot,
},
{
name: 'patient-reference',
valueReference: { reference: 'Patient/my-patient-id' },
},
],
}
) as Parameters;

const bundle = result.parameter?.[0]?.resource as Bundle;
const appointment = bundle.entry
?.find((e) => e.resource?.resourceType === 'Appointment')
?.resource as Appointment;

For multi-resource bookings, add additional slot parameters — one per Schedule:

const result = await medplum.post(
medplum.fhirUrl('Appointment', '$book'),
{
resourceType: 'Parameters',
parameter: [
{
name: 'slot',
resource: {
resourceType: 'Slot',
status: 'free',
start: '2026-03-11T08:00:00.000Z',
end: '2026-03-11T10:00:00.000Z',
schedule: { reference: 'Schedule/surgeon-schedule-id' },
serviceType: [{ coding: [{ code: 'bariatric-surgery' }] }],
} satisfies Slot,
},
{
name: 'slot',
resource: {
resourceType: 'Slot',
status: 'free',
start: '2026-03-11T08:00:00.000Z',
end: '2026-03-11T10:00:00.000Z',
schedule: { reference: 'Schedule/or-room-schedule-id' },
} satisfies Slot,
},
],
}
) as Parameters;

Parameters

NameTypeDescriptionRequired
slotResourceA Slot resource describing the desired booking time. Must include start, end, and schedule. Repeatable for multi-resource bookings.Yes (1+)
patient-referenceReferenceReference to a Patient to include as a participant on the AppointmentNo

Multi-resource Bookings

Pass multiple slot parameters (one per Schedule) to book all resources atomically. All slots must share the same start and end times.

{
"resourceType": "Parameters",
"parameter": [
{
"name": "slot",
"resource": {
"resourceType": "Slot",
"status": "free",
"start": "2026-03-11T08:00:00.000Z",
"end": "2026-03-11T10:00:00.000Z",
"schedule": { "reference": "Schedule/surgeon-schedule-id" },
"serviceType": [{ "coding": [{ "code": "bariatric-surgery" }] }]
}
},
{
"name": "slot",
"resource": {
"resourceType": "Slot",
"status": "free",
"start": "2026-03-11T08:00:00.000Z",
"end": "2026-03-11T10:00:00.000Z",
"schedule": { "reference": "Schedule/or-room-schedule-id" }
}
}
]
}

Constraints

  • All slot parameters must share the same start and end times
  • Each referenced Schedule must have exactly one actor
  • Each actor must have a timezone defined via the http://hl7.org/fhir/StructureDefinition/timezone extension
  • The requested time must match a valid slot duration from the Schedule's SchedulingParameters
  • No existing busy Slots may overlap the requested time window (including buffer windows)

Output

Returns 201 Created with a Parameters resource wrapping a Bundle containing all persisted resources:

  • One Appointment with status: booked
  • One Slot per input slot parameter with status: busy
  • Zero or more buffer Slot resources with status: busy-unavailable (created automatically from bufferBefore/bufferAfter settings on the Schedule)

Example Response

{
"resourceType": "Parameters",
"parameter": [
{
"name": "return",
"resource": {
"resourceType": "Bundle",
"type": "searchset",
"entry": [
{
"resource": {
"resourceType": "Appointment",
"id": "new-appointment-id",
"status": "booked",
"start": "2026-03-10T09:00:00.000Z",
"end": "2026-03-10T10:00:00.000Z",
"participant": [
{ "actor": { "reference": "Practitioner/dr-smith" }, "status": "tentative" },
{ "actor": { "reference": "Patient/my-patient-id" }, "status": "accepted" }
]
}
},
{
"resource": {
"resourceType": "Slot",
"id": "booked-slot-id",
"status": "busy",
"start": "2026-03-10T09:00:00.000Z",
"end": "2026-03-10T10:00:00.000Z",
"schedule": { "reference": "Schedule/my-schedule-id" }
}
}
]
}
}
]
}

Booking Logic

$book performs the following steps inside a serializable transaction:

  1. Validates that each proposed Slot's start/end matches a valid slot duration defined in the Schedule's SchedulingParameters
  2. Loads existing Slots in the time window (including buffer margins) for each Schedule
  3. Checks that no existing busy Slot overlaps the requested time
  4. Verifies the requested time falls within the Schedule's defined availability windows
  5. Creates the Appointment, busy Slot(s), and any buffer Slot(s) atomically
  6. Returns all created resources in the response Bundle

The transaction uses serializable isolation to prevent double-booking under concurrent requests.

Error Responses

Time No Longer Available

{
"resourceType": "OperationOutcome",
"issue": [{ "severity": "error", "code": "conflict", "details": { "text": "Requested time slot is no longer available" } }]
}

No Availability Found

{
"resourceType": "OperationOutcome",
"issue": [{ "severity": "error", "code": "invalid", "details": { "text": "No availability found at this time" } }]
}

Mismatched Slot Times

{
"resourceType": "OperationOutcome",
"issue": [{ "severity": "error", "code": "invalid", "details": { "text": "Mismatched slot start times" } }]
}

No Matching Scheduling Parameters

{
"resourceType": "OperationOutcome",
"issue": [{ "severity": "error", "code": "invalid", "details": { "text": "No matching scheduling parameters found" } }]
}

Actor Missing Timezone

{
"resourceType": "OperationOutcome",
"issue": [{ "severity": "error", "code": "invalid", "details": { "text": "No timezone specified" } }]
}