> ## 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.

# Reserva una cita de principio a fin

> Busca un hueco libre, resérvalo, reprográmalo y cancélalo, con las mismas reglas que la agenda de Brelio.

Esta guía reserva a un paciente con un profesional para un servicio, mueve la cita y la cancela. Usa `curl` y `jq`; cada paso es igual con cualquier cliente HTTP.

## Antes de empezar

* Una [clave de API de la organización](https://docs.brelio.com/es/authentication.md#claves-de-api-de-la-organización) con `services.read`, `professionals.read`, `calendar.read`, `appointments.read`, `appointments.create`, `appointments.reschedule` y `appointments.cancel`, y `patients.administrative.read` para encontrar al paciente.
* Las [áreas de datos](https://docs.brelio.com/es/concepts/data-areas.md) `agenda` y `team` activadas (lo están por defecto). Buscar al paciente por nombre necesita además el área `patients`; si está desactivada, usa un id de paciente que ya tengas.

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

## 1. Consigue los 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')
```

Si la clínica tiene más de un centro, toma también un `centerId` de `GET /v1/centers` y envíalo en los pasos siguientes.

## 2. Busca huecos libres

```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 Respuesta 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"
}
```

* Los huecos siguen el horario de la clínica y el intervalo de la agenda, y dejan fuera lo que el profesional ya tiene reservado o bloqueado: exactamente lo que aceptaría la agenda de Brelio.
* Las horas están en UTC. La clínica del ejemplo está en Madrid (UTC+1), así que `08:00Z` son las 09:00 hora local.
* La ventana es de 31 días como máximo y llegan como mucho 50 huecos. La duración es la del servicio salvo que envíes `durationMinutes`.

## 3. Resérvalo

Envía uno de los `startAt` como `start`, con una `Idempotency-Key` nueva:

```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"
}
```

Guarda el `id`. Es opaco: puede contener `:` y una fecha, y esa fecha no cambia cuando la cita se mueve. Úsalo tal cual.

Si alguien ha ocupado el hueco desde que buscaste, la respuesta es `409 APPOINTMENT_OVERLAP_DETECTED`: vuelve a buscar y ofrece otra hora.

## 4. Léela

```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 Respuesta theme={null}
{ "status": "scheduled", "start": "2026-11-16T08:00:00.000Z", "version": "1" }
```

`GET /v1/agenda?from=…&to=…` lista las mismas citas de una ventana, con los mismos ids.

## 5. Reprográmala

Envía la `version` que acabas de leer como `expectedVersion`, y un `start` nuevo de una nueva búsqueda de huecos:

```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 Respuesta 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",
  "…": "…"
}
```

Si la cita cambió en Brelio desde que la leíste, recibes `409 VERSION_CONFLICT`: vuelve a leerla y decide otra vez. Consulta la [concurrencia](https://docs.brelio.com/es/concepts/concurrency.md).

## 6. Cancélala

```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 Respuesta theme={null}
{ "id": "8bd8c358-…:2026-11-16T08:00:00.000Z", "status": "cancelled", "version": "3", "…": "…" }
```

* `reason` es solo administrativo. Nunca pongas información clínica.
* Cancelar no se puede deshacer por la API.
* Una cita que ya tiene sesión (paciente llegado o después) se cancela en Brelio: `409 APPOINTMENT_CANCEL_IN_BRELIO`.

## Límites de esta versión

* Solo citas sueltas. Las series recurrentes responden `422 APPOINTMENT_RECURRING_UNSUPPORTED` al reprogramar o cancelar.
* Reservar, reprogramar y cancelar es solo por REST; los asistentes MCP pueden leer la agenda y los huecos, pero todavía no escribir.
* `patientId` en la agenda es `null` salvo que el área `patients` de la clínica esté activada y tu clave esté aceptada para datos de pacientes.

## Todo el flujo en 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.