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

# Book an appointment end to end

> Find a free slot, book it, reschedule it and cancel it, with the same rules as Brelio's agenda.

This guide books a patient with a professional for a service, moves the appointment, and cancels it. It uses `curl` and `jq`; every step is the same over any HTTP client.

## Before you start

* An [organization API key](https://docs.brelio.com/en/authentication.md#organization-api-keys) with `services.read`, `professionals.read`, `calendar.read`, `appointments.read`, `appointments.create`, `appointments.reschedule` and `appointments.cancel`, plus `patients.administrative.read` to find the patient.
* The `agenda` and `team` [data areas](https://docs.brelio.com/en/concepts/data-areas.md) on (the default). Finding the patient by name needs the `patients` area too; if it is off, use a patient id you already have.

```bash theme={null}
export BRELIO_API_KEY="…"
export BRELIO=https://api.brelio.app/v1
```

## 1. Get the ids

```bash theme={null}
PROFESSIONAL_ID=$(curl -s "$BRELIO/professionals?status=active" \
  -H "Authorization: Bearer $BRELIO_API_KEY" | jq -r '.data[0].id')

SERVICE_ID=$(curl -s "$BRELIO/services?search=fisio" \
  -H "Authorization: Bearer $BRELIO_API_KEY" | jq -r '.data[0].id')

PATIENT_ID=$(curl -s "$BRELIO/patients?search=ana" \
  -H "Authorization: Bearer $BRELIO_API_KEY" | jq -r '.data[0].id')
```

If the clinic has more than one center, also take a `centerId` from `GET /v1/centers` and send it in the next steps.

## 2. Find free slots

```bash theme={null}
curl -s -G "$BRELIO/availability" \
  -H "Authorization: Bearer $BRELIO_API_KEY" \
  --data-urlencode "professionalId=$PROFESSIONAL_ID" \
  --data-urlencode "serviceId=$SERVICE_ID" \
  --data-urlencode "patientId=$PATIENT_ID" \
  --data-urlencode "from=2026-11-16T00:00:00Z" \
  --data-urlencode "to=2026-11-17T00:00:00Z"
```

```json Response theme={null}
{
  "data": [
    { "startAt": "2026-11-16T08:00:00.000Z", "endAt": "2026-11-16T08:45:00.000Z" },
    { "startAt": "2026-11-16T08:30:00.000Z", "endAt": "2026-11-16T09:15:00.000Z" },
    { "startAt": "2026-11-16T09:00:00.000Z", "endAt": "2026-11-16T09:45:00.000Z" }
  ],
  "durationMinutes": 45,
  "centerId": "82bae0e8-34b1-4f84-861a-472604262d3d"
}
```

* Slots follow the clinic's opening hours and agenda step, and leave out what the professional already has booked or blocked: exactly what Brelio's agenda would accept.
* Times are UTC. The clinic in this example is in Madrid (UTC+1), so `08:00Z` is 09:00 local time.
* The window is at most 31 days, and at most 50 slots come back. The length is the service's duration unless you send `durationMinutes`.

## 3. Book it

Send one of the `startAt` values as `start`, with a new `Idempotency-Key`:

```bash theme={null}
curl -s -X POST "$BRELIO/appointments" \
  -H "Authorization: Bearer $BRELIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d "{
    \"patientId\": \"$PATIENT_ID\",
    \"professionalId\": \"$PROFESSIONAL_ID\",
    \"serviceId\": \"$SERVICE_ID\",
    \"start\": \"2026-11-16T08:00:00.000Z\"
  }"
```

```json 201 Created theme={null}
{
  "id": "8bd8c358-6e46-41ae-adc2-03772033149f:2026-11-16T08:00:00.000Z",
  "seriesId": "8bd8c358-6e46-41ae-adc2-03772033149f",
  "start": "2026-11-16T08:00:00.000Z",
  "end": "2026-11-16T08:45:00.000Z",
  "durationMinutes": 45,
  "status": "scheduled",
  "professionalId": "bd548adb-6b1a-477b-8830-480c6330e028",
  "serviceId": "c3c8e275-a6b1-4fba-9b17-9c0a01e3ffc8",
  "patientId": "c588eb77-12e8-4042-8cce-48fb0555408f",
  "centerId": "82bae0e8-34b1-4f84-861a-472604262d3d",
  "recurring": false,
  "version": "1",
  "createdAt": "2026-11-09T17:59:34.308Z",
  "updatedAt": "2026-11-09T17:59:34.308Z"
}
```

Keep the `id`. It is opaque: it can contain `:` and a date, and the date in it does not change when the appointment moves. Use it as is.

If someone took the slot since you searched, the answer is `409 APPOINTMENT_OVERLAP_DETECTED`: search again and offer another time.

## 4. Read it

```bash theme={null}
APPOINTMENT_ID="8bd8c358-6e46-41ae-adc2-03772033149f:2026-11-16T08:00:00.000Z"

curl -s "$BRELIO/appointments/$APPOINTMENT_ID" \
  -H "Authorization: Bearer $BRELIO_API_KEY" | jq '{status, start, version}'
```

```json Response theme={null}
{ "status": "scheduled", "start": "2026-11-16T08:00:00.000Z", "version": "1" }
```

`GET /v1/agenda?from=…&to=…` lists the same appointments for a window, with the same ids.

## 5. Reschedule it

Send the `version` you just read as `expectedVersion`, and a new `start` from a new availability search:

```bash theme={null}
curl -s -X POST "$BRELIO/appointments/$APPOINTMENT_ID/reschedule" \
  -H "Authorization: Bearer $BRELIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"expectedVersion": "1", "start": "2026-11-16T10:00:00Z"}'
```

```json Response theme={null}
{
  "id": "8bd8c358-6e46-41ae-adc2-03772033149f:2026-11-16T08:00:00.000Z",
  "start": "2026-11-16T10:00:00.000Z",
  "end": "2026-11-16T10:45:00.000Z",
  "status": "scheduled",
  "version": "2",
  "…": "…"
}
```

If the appointment changed in Brelio since you read it, you get `409 VERSION_CONFLICT`: read it again and decide again. See [concurrency](https://docs.brelio.com/en/concepts/concurrency.md).

## 6. Cancel it

```bash theme={null}
curl -s -X POST "$BRELIO/appointments/$APPOINTMENT_ID/cancel" \
  -H "Authorization: Bearer $BRELIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"expectedVersion": "2", "reason": "Patient asked to cancel by phone"}'
```

```json Response theme={null}
{ "id": "8bd8c358-…:2026-11-16T08:00:00.000Z", "status": "cancelled", "version": "3", "…": "…" }
```

* `reason` is administrative only. Never put clinical information in it.
* Cancelling cannot be undone through the API.
* An appointment that already has a session (checked in or later) is cancelled in Brelio: `409 APPOINTMENT_CANCEL_IN_BRELIO`.

## Limits of this version

* Only one-off appointments. Recurring series answer `422 APPOINTMENT_RECURRING_UNSUPPORTED` on reschedule and cancel.
* Booking, rescheduling and cancelling are REST only; MCP assistants can read the agenda and free slots but not write yet.
* `patientId` in the agenda is `null` unless the clinic's `patients` area is on and your key was accepted for patient data.

## The whole flow in Node.js

```javascript book.mjs theme={null}
const BASE = 'https://api.brelio.app/v1'
const AUTH = { Authorization: `Bearer ${process.env.BRELIO_API_KEY}` }

async function api(path, { method = 'GET', body } = {}) {
  const response = await fetch(`${BASE}${path}`, {
    method,
    headers: body
      ? { ...AUTH, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID() }
      : AUTH,
    body: body && JSON.stringify(body),
  })
  const json = await response.json()
  if (!response.ok) throw Object.assign(new Error(`${json.code}: ${json.detail}`), { problem: json })
  return json
}

const [professional] = (await api('/professionals?status=active')).data
const [service] = (await api('/services?search=fisio')).data
const [patient] = (await api('/patients?search=ana')).data

const query = new URLSearchParams({
  professionalId: professional.id,
  serviceId: service.id,
  patientId: patient.id,
  from: '2026-11-16T00:00:00Z',
  to: '2026-11-17T00:00:00Z',
})
const [slot] = (await api(`/availability?${query}`)).data

const booked = await api('/appointments', {
  method: 'POST',
  body: { patientId: patient.id, professionalId: professional.id, serviceId: service.id, start: slot.startAt },
})
console.log('booked', booked.start)

const id = encodeURIComponent(booked.id)
const moved = await api(`/appointments/${id}/reschedule`, {
  method: 'POST',
  body: { expectedVersion: booked.version, start: '2026-11-16T10:00:00Z' },
})
console.log('moved to', moved.start)

const cancelled = await api(`/appointments/${id}/cancel`, {
  method: 'POST',
  body: { expectedVersion: moved.version, reason: 'Patient asked to cancel by phone' },
})
console.log(cancelled.status)
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.