On this page

Beta

The `$book` operation is currently in [beta](/content/docs/compliance/alpha-beta/index.html).

The `$book` operation books an [`Appointment`](/content/docs/api/fhir/resources/appointment/index.html) by atomically creating the Appointment, one or more busy [`Slot`](/content/docs/api/fhir/resources/slot/index.html) 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)

- **Direct booking**: Book an appointment directly from a `$find` result, 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 [​](/content/docs/scheduling/appointment-book#invoke-the-book-operation "Direct link to invoke-the-book-operation"/index.html)

```text
[base]/R4/Appointment/$book
```

- TypeScript
- cURL

```ts
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'));
```

```bash
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`](/content/docs/scheduling/appointment-find/index.html). The Appointment must include `contained` Slot resources that describe when to book each Schedule.

```json
{

"resourceType": "Parameters",

}
```

For multi-resource bookings, include multiple Slot resources in `Appointment.contained`:

```json
{

"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)

- 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)
- The `serviceType` attribute must reference the HealthcareService you are trying to schedule via the `https://medplum.com/fhir/service-type-reference` extension
- The input `Appointment` must not already contain `slot` references (these are set by `$book`)

The easiest way to meet these requirements is to use a result from a [`$find` operation](/content/docs/scheduling/appointment-find/index.html).

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

Returns `201 Created` with a [`Bundle`](/content/docs/api/fhir/resources/bundle/index.html) wrapping all persisted resources:

- One [`Appointment`](/content/docs/api/fhir/resources/appointment/index.html) with `status: "booked"`
- One `Slot` per contained Slot with `status: "busy"`
- Zero or more buffer `Slot` resources with `status: "busy-unavailable"` (when `bufferBefore` or `bufferAfter` scheduling parameters are set)

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

```json
{

"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)

```json
{

"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)

```json
{

"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)

```json
{

"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](/content/docs/compliance/alpha-beta/index.html) release of the scheduling API is expected to gain additional capabilities.

- `bookingLimit`  An upcoming scheduling parameter that will allow you to express how often a given service type may be added to a schedule. This is not yet enforced in `$book`.
