> ## Documentation Index
> Fetch the complete documentation index at: https://build.practicehub.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Booking

> Find a free slot, book it, and handle the clash if someone got there first

External booking is a two-step conversation with the diary: ask what is free, then book one of the answers. Both steps run on the same engine as the PracticeHub calendar and online booking, so a slot the API offers is a slot the practice would book.

<Steps>
  <Step title="Find free slots">
    ```bash theme={null}
    curl -g "https://your-clinic.your-region.practicehub.io/v3/api/availability?location_id=81&appointment_type_id=1&from=2026-09-01&to=2026-09-07" \
      -H "Authorization: Bearer $KEY"
    ```

    ```json theme={null}
    {
      "data": [
        {
          "start": "2026-09-01 09:00:00",
          "end": "2026-09-01 09:30:00",
          "practitioner_id": 109,
          "practitioner_name": "Ada Lovelace",
          "location_id": 81,
          "appointment_type_id": 1,
          "resource_id": null,
          "type": "individual",
          "group_master_id": null,
          "online_fee": 45
        }
      ],
      "meta": {
        "timezone": "Europe/London",
        "from": "2026-09-01 00:00:00",
        "to": "2026-09-07 23:59:59",
        "visibility": "online",
        "unavailable_dates": ["2026-09-06"]
      }
    }
    ```

    Slots are in the clinic timezone, earliest first, at most 50 per day. `unavailable_dates` lists days in the range with nothing free — handy for greying out a calendar without a request per day.
  </Step>

  <Step title="Book one of them">
    ```bash theme={null}
    curl -X POST https://your-clinic.your-region.practicehub.io/v3/api/appointments \
      -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
      -H "Idempotency-Key: booking-7f3a" \
      -d '{
        "patient_id": 340, "location_id": 81, "appointment_type_id": 1, "practitioner_id": 109,
        "start": "2026-09-01 09:00:00", "end": "2026-09-01 09:30:00"
      }'
    ```

    Copy `start`, `end`, `practitioner_id`, `location_id` and `appointment_type_id` straight from the slot. For a `group` slot send its `group_master_id` instead of `start`/`end` — you are taking a place in an existing session.
  </Step>

  <Step title="Handle a clash">
    Someone may book the same time between your two calls. `POST /appointments` checks the practitioner's diary at the moment of writing and answers `409` with code `slot_unavailable` if the time is no longer free (or `group_full` for a group). Re-query availability and offer the next slot — the [`Idempotency-Key`](/guides/writing#retrying-safely-idempotency-keys) makes a retry of the *same* booking safe, but a new time is a new request.
  </Step>
</Steps>

## Parameters

| Parameter                    | Required | Meaning                                                                                                                                                                                                                                  |
| ---------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `location_id`                | Yes      | Location to search                                                                                                                                                                                                                       |
| `appointment_type_id`        | Yes      | Appointment type to book                                                                                                                                                                                                                 |
| `practitioner_id`            | No       | Limit to one practitioner; omit for any practitioner who provides the type                                                                                                                                                               |
| `from`                       | No       | Range start, `YYYY-MM-DD` or `YYYY-MM-DD HH:MM:SS` in the clinic timezone. Default: start of today                                                                                                                                       |
| `to`                         | No       | Range end (inclusive; a date means the end of that day). Default: 7 days from `from`; at most **31 days** — page by week or month for wider views                                                                                        |
| `visibility`                 | No       | `online` (default) or `all` — see below                                                                                                                                                                                                  |
| `max_per_day`                | No       | Cap on slots per day (1–50)                                                                                                                                                                                                              |
| `rescheduled_appointment_id` | No       | When finding a new time for an existing appointment: the time it occupies is treated as free for overlapping alternatives (and its own duration/columns are used), but its exact current start is not offered — that would not be a move |

## Two views of the diary

<CardGroup cols={2}>
  <Card title="visibility=online (default)" icon="globe">
    What a patient sees when booking online. The location, appointment type and (if given) practitioner must be enabled for online booking — otherwise `422 not_online_bookable` — and every online-booking rule applies: minimum advance notice, how far ahead, daily caps, online-only availability guides, slot clustering. Use this for websites, apps and marketplaces.
  </Card>

  <Card title="visibility=all" icon="users">
    What the front desk could book: every open slot for practitioners who provide the type, subject only to physical capacity (columns / resources). No online-booking rules. Use this for staff-facing tools and internal automations.
  </Card>
</CardGroup>

## Deliberate overbooking

The clash check exists to protect the diary from integrations that cannot see it. If you *mean* to book over an existing appointment — a data migration, a practice that double-books on purpose — send `"allow_clash": true` and the appointment is written wherever you say, exactly as a staff member clicking the calendar would.

## Not the same as `practitioner_availabilities`

`practitioner_availabilities` is a resource: the raw working-hours records (who is in, where, when). `GET /availability` is a computation over those records, existing appointments, closures, resources and the appointment type — the free time that is left. Read the first to know when someone works; call the second to know when they can be booked.

<Tip>
  Rate limits are per API key. A booking widget serving many visitors should fetch a week at a time and re-check only when the visitor picks a day, rather than polling per day.
</Tip>
