Appointment $book
The $book operation is currently in alpha.
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
- Direct booking: Book an appointment directly from a
$findresult, without a prior hold - 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
- TypeScript
- cURL
import { isResource, MedplumClient } from '@medplum/core';
import type { Appointment, Bundle, Slot } from '@medplum/fhirtypes';
const medplum = new MedplumClient();
// 1. Find available appointments
const findUrl = medplum.fhirUrl('Appointment', '$find');
findUrl.searchParams.append('start', '2026-03-10T00:00:00Z');
findUrl.searchParams.append('end', '2026-03-10T23:59:59Z');
findUrl.searchParams.append('service-type-reference', 'HealthcareService/my-healthcare-service-id');
findUrl.searchParams.append('schedule', 'Schedule/my-schedule-id');
const findBundle = (await medplum.get<Bundle<Appointment>>(findUrl)) as Bundle;
// 2. Pick a proposed appointment from the results
const proposedAppointment = findBundle.entry?.[0]?.resource as Appointment;
// 3. Book it
const bundle = await medplum.post<Bundle<Appointment | Slot>>(medplum.fhirUrl('Appointment', '$book'), {
resourceType: 'Parameters',
parameter: [
{
name: 'appointment',
resource: proposedAppointment,
},
],
});
// Use the newly created Appointment resource
const appointment = bundle.entry?.map((e) => e.resource)?.find((e) => isResource<Appointment>(e, 'Appointment'));
curl -X POST 'https://api.medplum.com/fhir/R4/Appointment/$book' \
-H "Content-Type: application/fhir+json" \
-H "Authorization: Bearer MY_ACCESS_TOKEN" \
-d '{
"resourceType": "Parameters",
"parameter": [
{
"name": "appointment",
"resource": {
"resourceType": "Appointment",
"status": "proposed",
"start": "2026-03-10T09:00:00.000Z",
"end": "2026-03-10T10:00:00.000Z",
"serviceType": [
{
"coding": [{ "code": "initial-visit" }],
"extension": [
{
"url": "https://medplum.com/fhir/service-type-reference",
"valueReference": { "reference": "HealthcareService/my-healthcareservice-id" }
}
]
}
],
"participant": [
{
"actor": { "reference": "Practitioner/dr-smith" },
"required": "required",
"status": "needs-action"
}
],
"contained": [
{
"resourceType": "Slot",
"status": "busy",
"schedule": { "reference": "Schedule/dr-smith-schedule" },
"start": "2026-03-10T09:00:00.000Z",
"end": "2026-03-10T10:00:00.000Z"
}
]
}
}
]
}'
Parameters
| Name | Type | Description | Required |
|---|---|---|---|
appointment | Appointment | A proposed Appointment resource (e.g. from $find). Must include start, end, and serviceType. Must have Slot resources in contained. | Yes |
Appointment Input
The appointment parameter accepts a proposed Appointment resource, exactly as returned by $find. The Appointment must include contained Slot resources that describe which Schedules to book.
{
"resourceType": "Parameters",
"parameter": [
{
"name": "appointment",
"resource": {
"resourceType": "Appointment",
"status": "proposed",
"start": "2026-03-10T09:00:00.000Z",
"end": "2026-03-10T10:00:00.000Z",
"serviceType": [
{
"coding": [{ "code": "initial-visit" }],
"extension": [
{
"url": "https://medplum.com/fhir/service-type-reference",
"valueReference": { "reference": "HealthcareService/my-healthcareservice-id" }
}
]
}
],
"participant": [
{ "actor": { "reference": "Practitioner/dr-smith" }, "required": "required", "status": "needs-action" }
],
"contained": [
{
"resourceType": "Slot",
"status": "busy",
"schedule": { "reference": "Schedule/dr-smith-schedule" },
"start": "2026-03-10T09:00:00.000Z",
"end": "2026-03-10T10:00:00.000Z"
}
]
}
}
]
}
For multi-resource bookings, include multiple Slot resources in Appointment.contained:
{
"resourceType": "Parameters",
"parameter": [
{
"name": "appointment",
"resource": {
"resourceType": "Appointment",
"status": "proposed",
"start": "2026-03-11T08:00:00.000Z",
"end": "2026-03-11T10:00:00.000Z",
"serviceType": [
{
"coding": [{ "code": "bariatric-surgery" }],
"extension": [
{
"url": "https://medplum.com/fhir/service-type-reference",
"valueReference": { "reference": "HealthcareService/my-healthcareservice-id" }
}
]
}
],
"participant": [
{ "actor": { "reference": "Practitioner/dr-smith" }, "required": "required", "status": "needs-action" },
{ "actor": { "reference": "Location/or-room-1" }, "required": "required", "status": "needs-action" }
],
"contained": [
{
"resourceType": "Slot",
"status": "busy",
"schedule": { "reference": "Schedule/surgeon-schedule-id" },
"start": "2026-03-11T08:00:00.000Z",
"end": "2026-03-11T10:00:00.000Z"
},
{
"resourceType": "Slot",
"status": "busy",
"schedule": { "reference": "Schedule/or-room-schedule-id" },
"start": "2026-03-11T08:00:00.000Z",
"end": "2026-03-11T10:00:00.000Z"
}
]
}
}
]
}
Constraints
- Each referenced Schedule must have exactly one actor
- Each actor must have a timezone defined via the
http://hl7.org/fhir/StructureDefinition/timezoneextension - 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)
- The
serviceTypeattribute must reference the HealthcareService you are trying to schedule via thehttps://medplum.com/fhir/service-type-referenceextension - The input
Appointmentmust not already containslotreferences (these are set by$book)
The easiest way to meet these requirements is to use a result from a $find operation.
Output
Returns 201 Created with a Bundle wrapping all persisted resources:
- One
Appointmentwithstatus: "booked" - One
Slotper contained Slot withstatus: "busy" - Zero or more buffer
Slotresources withstatus: "busy-unavailable"(whenbufferBeforeorbufferAfterscheduling parameters are set)
Example Response
{
"resourceType": "Bundle",
"type": "transaction-response",
"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" }
],
"slot": [{ "reference": "Slot/booked-slot-id" }]
}
},
{
"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/dr-smith-schedule" }
}
}
]
}
Booking Logic
$book performs the following steps atomically inside a database transaction:
- Validates that each proposed Slot's start/end matches a valid slot duration defined in the Schedule's
SchedulingParameters - Loads existing Slots in the time window (including buffer margins) for each Schedule
- Checks that no existing busy Slot overlaps the requested time
- Verifies the requested time falls within the Schedule's defined availability windows
- Creates the
Appointment, busySlot(s), and any bufferSlot(s) atomically - Returns all created resources in the response Bundle
The transaction uses serializable isolation to prevent double-booking under concurrent requests.
Error Responses
Time Not Available
{
"resourceType": "OperationOutcome",
"issue": [{ "severity": "error", "code": "invalid", "details": { "text": "Requested time slot is not available" } }]
}
Mismatched Slot Times
{
"resourceType": "OperationOutcome",
"issue": [{ "severity": "error", "code": "invalid", "details": { "text": "Mismatched slot start times" } }]
}
Actor Missing Timezone
{
"resourceType": "OperationOutcome",
"issue": [{ "severity": "error", "code": "invalid", "details": { "text": "No timezone specified" } }]
}
Related
- Appointment
$find- Find available Slots before booking - Appointment
$hold- Optionally reserve a slot before confirming - Defining Availability - How to configure
SchedulingParameterson a Schedule - Scheduling Overview - High-level scheduling concepts
AppointmentresourceSlotresource- FHIR Transaction Bundles