Holds

Entre que un huésped elige una habitación y termina de pagarla pasa algo real: se
autoriza una tarjeta, se llena una segunda pantalla, alguien va por su pasaporte.
Durante ese rato la habitación sigue a la venta para todos los demás.

Un hold la aparta. POST /bookings acepta un campo opcional:

{
  "unit_id": "…",
  "rate_plan_id": "…",
  "start_date": "2027-03-01",
  "end_date": "2027-03-04",
  "adults": 2,
  "hold_minutes": 20,
  "guest": { "first_name": "Ada", "last_name": "Lovelace", "email": "[email protected]" }
}

La reserva se crea igual, pero marcada como retenida y con un reloj:

{
  "data": {
    "id": "…",
    "status": "pending",
    "is_hold": true,
    "hold_expires_at": "2027-02-10T14:20:00+00:00"
  }
}

hold_minutes va de 1 a 30. El tope es de la plataforma: una habitación
retenida es inventario que nadie más puede vender.

Omitir hold_minutes deja todo exactamente como estaba. Una integración
que nunca lo mande se comporta igual que antes de que los holds existieran:
is_hold viene en false, hold_expires_at en null, y la reserva no
caduca. Nada de esto es un cambio incompatible.

Cerrar el hold

Dos salidas, y hay que tomar una:

POST /bookings/{booking}/confirm — convierte el hold en venta. Quita el
reloj y deja la reserva en confirmed. Necesita bookings:write, el mismo
alcance con el que se creó.

POST /bookings/{booking}/cancel — devuelve la habitación al mercado ahora.
Necesita bookings:cancel, que es un alcance aparte a propósito: una key emitida
para vender no debería poder cancelar.

Y si no se toma ninguna, el hold se devuelve solo. Un barrido corre cada
cinco minutos y cancela todo hold cuyo hold_expires_at ya pasó. No hay que
hacer nada para limpiarlo, y no hay forma de dejar una habitación bloqueada por
olvido.

Las dos llamadas son idempotentes por estado

Ninguna necesita Idempotency-Key. Reenviar el mismo confirm a una reserva ya
confirmada devuelve 200 con el mismo cuerpo; lo mismo cancel sobre una ya
cancelada. Un reintento después de una respuesta perdida es el caso normal, no
un error.

Esto es distinto de POST /bookings, donde la key sí
es obligatoria — ahí el reintento podría crear una segunda reserva, y aquí no hay
nada que duplicar.

Un hold retiene la habitación, no el precio

Es la confusión que más cuesta. hold_expires_at acota cuánto tiempo la
habitación queda fuera del mercado. No congela la tarifa: el precio que se cobra
es el que trae la reserva, y hay que leerlo de ahí y no de la cotización que se
le mostró antes al huésped.

Tampoco resuelve la carrera entre cotizar y reservar. Entre POST /quote y
POST /bookings sigue sin haber nada retenido, y esa llamada puede responder
409 UNIT_NOT_AVAILABLE como siempre. El hold empieza cuando la reserva existe.

Los holds cuentan contra un techo

Cada key tiene un tope de holds vivos simultáneos — 25 por defecto. Al toparlo,
crear otro responde 429 KEY_HOLD_LIMIT_REACHED.

Confirmar o cancelar libera el cupo de inmediato. Dejar que el reloj venza
también, pero tarda lo que le quede. Una integración que cierra sus holds en vez
de abandonarlos rara vez ve este techo — ver
Límites de uso.

Errores

CódigoEstadoSignificado
BOOKING_HOLD_EXPIRED409El reloj venció antes de que llegara el confirm. La habitación volvió al mercado y puede estar vendida. Hay que cotizar de nuevo.
BOOKING_NOT_CONFIRMABLE422Sólo una reserva pending se confirma. Si ya empezó la estadía, o el hold lo produjo otro sistema, es decisión del hotel.
BOOKING_NOT_CANCELLABLE422La estadía ya empezó o ya terminó.
KEY_HOLD_LIMIT_REACHED429La key ya mantiene su máximo de holds vivos.

Relacionado

  • Idempotencia — por qué POST /bookings sí exige una key.
  • Límites de uso — los dos techos y las dos clases de 429.
  • Alcances — por qué bookings:cancel está separado.

Did this page help you?