Appointment $book | Medplum

On this page

Beta

The $book operation is currently in beta.

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 [​](/content/docs/scheduling/appointment-book#use-cases "Direct link to Use Cases"/index.html)

Invoke the $book operation [​](/content/docs/scheduling/appointment-book#invoke-the-book-operation "Direct link to invoke-the-book-operation"/index.html)

[base]/R4/Appointment/$book
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 [​](/content/docs/scheduling/appointment-book#parameters "Direct link to Parameters"/index.html)

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 [​](/content/docs/scheduling/appointment-book#appointment-input "Direct link to Appointment Input"/index.html)

The appointment parameter accepts a proposed Appointment resource, exactly as returned by $find. The Appointment must include contained Slot resources that describe when to book each Schedule.

{

"resourceType": "Parameters",

}

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 [​](/content/docs/scheduling/appointment-book#constraints "Direct link to Constraints"/index.html)

The easiest way to meet these requirements is to use a result from a $find operation.

Output [​](/content/docs/scheduling/appointment-book#output "Direct link to Output"/index.html)

Returns 201 Created with a Bundle wrapping all persisted resources:

Example Response [​](/content/docs/scheduling/appointment-book#example-response "Direct link to Example Response"/index.html)

{

"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 [​](/content/docs/scheduling/appointment-book#booking-logic "Direct link to Booking Logic"/index.html)

$book performs the following steps atomically inside a database transaction, ensuring safety when concurrent booking requests are received.

  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 or existing slots with status free
  5. Creates the Appointment, busy Slot(s), and any buffer Slot(s)
  6. Returns all created resources in the response Bundle

Error Responses [​](/content/docs/scheduling/appointment-book#error-responses "Direct link to Error Responses"/index.html)

Time Not Available [​](/content/docs/scheduling/appointment-book#time-not-available "Direct link to Time Not Available"/index.html)

{

"resourceType": "OperationOutcome",

"issue": [{ "severity": "error", "code": "invalid", "details": { "text": "Requested time slot is not available" } }]

}

Mismatched Slot Times [​](/content/docs/scheduling/appointment-book#mismatched-slot-times "Direct link to Mismatched Slot Times"/index.html)

{

"resourceType": "OperationOutcome",

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

}

Actor Missing Timezone [​](/content/docs/scheduling/appointment-book#actor-missing-timezone "Direct link to Actor Missing Timezone"/index.html)

{

"resourceType": "OperationOutcome",

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

}

Beta Status [​](/content/docs/scheduling/appointment-book#beta-status "Direct link to Beta Status"/index.html)

The Scheduling API is under active development. This beta release of the scheduling API is expected to gain additional capabilities.