Errores
Casi todos los errores llevan un código estable, legible por máquina:
{
"message": "Not found.",
"error_code": "NOT_FOUND"
}Hay que comparar contra error_code. message es prosa escrita para una
persona y se puede reescribir en cualquier momento sin aviso. Ramificar sobre él
se va a romper.
Los errores de validación no traen error_code
error_codeEsta es la excepción que agarra a la gente desprevenida. Una falla corriente de
validación de campos devuelve 422 con otra forma — un message y un objeto
errors por campo, y ninguna llave error_code:
{
"message": "The start field is required. (and 1 more error)",
"errors": {
"start": ["The start field is required."],
"end": ["The end field is required."]
}
}Hay que leer errors, no message — message es genérico y no trae nada
accionable.
Así que un 422 puede tener cualquiera de las dos formas. Conviene revisar
primero si hay error_code y, si no, caer a errors.
Los códigos
Credenciales
| Código | Estado | Significado |
|---|---|---|
APIKEY_MISSING | 401 | No se mandó key. |
APIKEY_INVALID | 401 | Desconocida, expirada, revocada o desactivada — se responden igual a propósito. |
APIKEY_SCOPE_MISSING | 403 | Key válida, falta el alcance. El mensaje lo nombra. |
APIKEY_NOT_FOUND | 401 | La key no llegó a resolverse en la petición. En la práctica se ve igual que una key inválida. |
TENANT_INACTIVE | 403 | La cuenta del hotel está desactivada. El hotel puede arreglarlo. |
Direccionamiento
No existe un NOT_FOUND genérico en esta API. Cada 404 nombra qué fue lo que
no se encontró, para que un manejador pueda distinguir "el id de habitación está
mal" de "el id de reserva está mal" sin leer prosa.
| Código | Estado | Significado |
|---|---|---|
PROPERTY_NOT_IDENTIFIED | 422 | Una key de varias propiedades omitió X-Property. |
PROPERTY_NOT_FOUND | 404 | La propiedad está fuera de la lista de la key, o no existe. Una sola respuesta para ambas. |
UNIT_NOT_FOUND | 404 | No hay esa habitación en esta propiedad — o existe y no es vendible por esta API. |
RATE_PLAN_NOT_FOUND | 404 | El plan tarifario no está en esta propiedad o no es vendible por esta API. |
GUEST_NOT_FOUND | 404 | El guest_id que se mandó no existe en esta cuenta. |
BOOKING_NOT_FOUND | 404 | No hay esa reserva en esta propiedad. |
INVALID_CURSOR | 422 | El cursor de paginación es inservible. |
Crear una reserva
| Código | Estado | Significado |
|---|---|---|
IDEMPOTENCY_KEY_REQUIRED | 422 | Falta el header. Ver Idempotencia. |
IDEMPOTENCY_KEY_REUSED | 409 | La key ya se usó con otro cuerpo. |
IDEMPOTENCY_IN_FLIGHT | 409 | Una petición con esta key no ha terminado. |
Confirmar y cancelar
| Código | Estado | Significado |
|---|---|---|
BOOKING_HOLD_EXPIRED | 409 | El reloj del hold venció antes de que llegara el confirm. La habitación volvió al mercado y puede estar vendida. Hay que cotizar de nuevo. |
BOOKING_NOT_CONFIRMABLE | 422 | Sólo una reserva pending se puede confirmar. Cualquier otra cosa — ya cancelada, ya iniciada, o un hold que produjo otro sistema — es decisión del hotel, no de la integración. |
BOOKING_NOT_CANCELLABLE | 422 | La estadía ya empezó o ya terminó. Desde ahí, cancelar es decisión del hotel. |
Ni confirm ni cancel necesitan Idempotency-Key: son idempotentes por
estado. Reenviar el mismo confirm a una reserva ya confirmada, o el mismo
cancel a una ya cancelada, devuelve 200 con el mismo cuerpo. Un reintento
después de una respuesta perdida es el caso normal, no un error.
Por qué no se pudo vender la estadía
Todos responden 422 salvo UNIT_NOT_AVAILABLE, que en POST /bookings es
409: la habitación estaba libre al cotizar y dejó de estarlo. Son los que más
seguido va a ver una integración real.
Llevan además los números detrás del rechazo, que son la parte accionable: un
motor de reservas puede decir "mínimo 3 noches" en vez de "no se pudo reservar".
| Código | Qué pasó |
|---|---|
UNIT_NOT_AVAILABLE | 409. No hay nada libre para esas fechas — se vendió después de cotizar. Hay que volver a consultar la disponibilidad. |
OCCUPANCY_OUT_OF_BOUNDS | La ocupación pedida excede la capacidad de la unidad. |
INVALID_BOOKING_QUANTITY | La cantidad pedida no es válida para esa unidad. |
RATE_PLAN_MIN_STAY | La estadía es más corta que el mínimo de su noche de llegada (puede venir de una temporada o de un precio diario, no solo del plan). Trae min_stay y nights. |
RATE_PLAN_MAX_STAY | La estadía es más larga que el máximo de su noche de llegada. Trae max_stay y nights. |
RATE_PLAN_CLOSED_TO_ARRIVAL | No se puede llegar ese día. Trae date, el día de llegada cerrado. |
RATE_PLAN_CLOSED_TO_DEPARTURE | No se puede salir ese día. Trae date, el día de salida cerrado. |
RATE_PLAN_OCCUPANCY_NOT_CONFIGURED | El plan no tiene precio para esa ocupación. Trae requested_adults y configured_adults. |
RATE_PLAN_UNIT_NOT_LINKED | Ese plan no está enganchado a esa unidad. |
RATE_PLAN_WRONG_SALES_CHANNEL | El plan no se vende en el canal api. |
RATE_PLAN_INACTIVE · RATE_PLAN_PARENT_INACTIVE | El plan, o el padre del que deriva, está apagado. |
RATE_PLAN_NOT_USABLE | El plan no se puede usar por una razón que no cae en las anteriores. |
BOOKING_CUTOFF_PASSED | La estadía llega hoy y el hotel ya dejó de recibir reservas para el mismo día. Trae cutoff_time y property_today. Ver La hora de corte. |
{
"message": "This rate plan has a 3 night minimum stay.",
"error_code": "RATE_PLAN_MIN_STAY",
"min_stay": 3,
"nights": 2
}El objeto hay que tratarlo como abierto: qué campos extra vienen depende del
motivo, así que se leen los que uno reconozca y se ignora el resto.
La hora de corte
Un hotel puede fijar una hora de corte: a partir de esa hora, en su propio
reloj, deja de aceptar llegadas para ese mismo día. Pasada la hora,
POST /quote y POST /bookings responden 422 BOOKING_CUTOFF_PASSED para
cualquier estadía cuyo start_date sea hoy:
{
"message": "The property stopped taking same-day bookings at 20:00.",
"error_code": "BOOKING_CUTOFF_PASSED",
"property_today": "2027-03-01",
"cutoff_time": "20:00"
}- "Hoy" es el día del hotel, no el tuyo ni el UTC.
property_todaydice
cuál es, ycutoff_timees la hora local del hotel en formatoHH:MM. - Mañana se sigue vendiendo. Lo que corresponde es ofrecer la estadía a
partir deproperty_today+ 1, no reintentar. GET /availabilityya lo refleja: una consulta que empieza hoy, pasada la
hora de corte, devuelve cada unidad conavailable: falsey
remaining_capacity: 0.- Un hold creado antes de la hora de corte se respeta. El corte se revisa al
crear la reserva, no al confirmarla, así queconfirmnunca responde este
código. Ver Holds.
Límites de uso
Hay dos clases de 429 y no se comportan igual.
Un 429 de límite de peticiones — la cuota por minuto de tu key, o el techo
anónimo por IP — no trae error_code. Contra ése hay que comparar el
estado.
Un 429 de techo de inventario sí lo trae, y además dice cuánto:
| Código | Estado | Significado |
|---|---|---|
KEY_BOOKING_QUOTA_EXCEEDED | 429 | La key creó su máximo de reservas en las últimas 24 horas. |
KEY_HOLD_LIMIT_REACHED | 429 | La key ya mantiene su máximo de holds vivos a la vez. |
{
"message": "This API key is already holding its maximum of 25 rooms.",
"error_code": "KEY_HOLD_LIMIT_REACHED",
"limit": 25,
"used": 25
}Los dos traen Retry-After. Ver Límites de uso.
Webhooks
| Código | Estado | Significado |
|---|---|---|
WEBHOOK_SUBSCRIPTION_NOT_FOUND | 404 | La suscripción no existe, la creó el hotel desde su pantalla, o cubre propiedades fuera del alcance de la key. Una sola respuesta para las tres. |
WEBHOOK_EVENT_NOT_PERMITTED | 403 | La suscripción incluye eventos booking.* y la key no tiene bookings:read:all. Aplica al crearla y a cualquier cambio posterior. Ver Recibir eventos por webhook. |
Una URL insegura — no https, o que resuelve a una dirección privada — no
tiene código propio: es un error de validación sobre url, con la forma de
errors de arriba.
Propiedad en hibernación
Un hotel puede poner una propiedad en hibernación durante su temporada baja.
Mientras dure, toda operación sobre esa propiedad responde 403, sea cual
sea el plan de hibernación que eligió el hotel. Las reservas existentes se
conservan; simplemente no se pueden leer ni cambiar desde esta API hasta que el
hotel la reactive.
| Código | Estado | Significado |
|---|---|---|
PROPERTY_HIBERNATED | 403 | La propiedad está en pausa total. |
NOT_AVAILABLE_IN_HIBERNATION | 403 | La propiedad está en el plan básico de hibernación, que conserva los canales propios del hotel pero no las integraciones. |
PROPERTY_HIBERNATION_SCHEDULED | 422 | Sólo en POST /bookings. La propiedad va a entrar en pausa y ya no vende estadías que lleguen a la fecha de inicio. Una estadía que sale ese día o antes sí se vende. |
Reintentar no sirve. Lo que corresponde es dejar de sincronizar esa
propiedad, avisarle a quien opera la integración y retomarla cuando las llamadas
dejen de responder 403. GET /properties la sigue listando: es la única
lectura que no apunta a una propiedad concreta.
El "no encontrado" dice qué, no dice por qué
El código nombra qué tipo de cosa no se encontró — habitación, plan, huésped,
reserva, propiedad. Lo que deliberadamente no distingue es por qué, y dentro
de cada tipo un 404 cubre tres situaciones:
- el recurso no existe;
- existe pero pertenece a otra propiedad;
- existe y no está expuesto a esta API.
Distinguirlas le permitiría a quien llama mapear la cuenta de otro sondeando ids.
Por eso UNIT_NOT_FOUND responde igual para una habitación inventada que para una
del hotel de al lado.
Cuando llega un 404 inesperado sobre un id en el que uno cree, lo primero que
hay que revisar no es si el registro existe — es si la habitación tiene un plan
tarifario activo del canal api y si uno está apuntando a la propiedad correcta.
Relacionado
Updated 6 days ago