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

Esta 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ódigoEstadoSignificado
APIKEY_MISSING401No se mandó key.
APIKEY_INVALID401Desconocida, expirada, revocada o desactivada — se responden igual a propósito.
APIKEY_SCOPE_MISSING403Key válida, falta el alcance. El mensaje lo nombra.
APIKEY_NOT_FOUND401La key no llegó a resolverse en la petición. En la práctica se ve igual que una key inválida.
TENANT_INACTIVE403La 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ódigoEstadoSignificado
PROPERTY_NOT_IDENTIFIED422Una key de varias propiedades omitió X-Property.
PROPERTY_NOT_FOUND404La propiedad está fuera de la lista de la key, o no existe. Una sola respuesta para ambas.
UNIT_NOT_FOUND404No hay esa habitación en esta propiedad — o existe y no es vendible por esta API.
RATE_PLAN_NOT_FOUND404El plan tarifario no está en esta propiedad o no es vendible por esta API.
GUEST_NOT_FOUND404El guest_id que se mandó no existe en esta cuenta.
BOOKING_NOT_FOUND404No hay esa reserva en esta propiedad.
INVALID_CURSOR422El cursor de paginación es inservible.

Crear una reserva

CódigoEstadoSignificado
IDEMPOTENCY_KEY_REQUIRED422Falta el header. Ver Idempotencia.
IDEMPOTENCY_KEY_REUSED409La key ya se usó con otro cuerpo.
IDEMPOTENCY_IN_FLIGHT409Una petición con esta key no ha terminado.

Confirmar y cancelar

CódigoEstadoSignificado
BOOKING_HOLD_EXPIRED409El 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_CONFIRMABLE422Só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_CANCELLABLE422La 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ódigoQué pasó
UNIT_NOT_AVAILABLE409. No hay nada libre para esas fechas — se vendió después de cotizar. Hay que volver a consultar la disponibilidad.
OCCUPANCY_OUT_OF_BOUNDSLa ocupación pedida excede la capacidad de la unidad.
INVALID_BOOKING_QUANTITYLa cantidad pedida no es válida para esa unidad.
RATE_PLAN_MIN_STAYLa 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_STAYLa estadía es más larga que el máximo de su noche de llegada. Trae max_stay y nights.
RATE_PLAN_CLOSED_TO_ARRIVALNo se puede llegar ese día. Trae date, el día de llegada cerrado.
RATE_PLAN_CLOSED_TO_DEPARTURENo se puede salir ese día. Trae date, el día de salida cerrado.
RATE_PLAN_OCCUPANCY_NOT_CONFIGUREDEl plan no tiene precio para esa ocupación. Trae requested_adults y configured_adults.
RATE_PLAN_UNIT_NOT_LINKEDEse plan no está enganchado a esa unidad.
RATE_PLAN_WRONG_SALES_CHANNELEl plan no se vende en el canal api.
RATE_PLAN_INACTIVE · RATE_PLAN_PARENT_INACTIVEEl plan, o el padre del que deriva, está apagado.
RATE_PLAN_NOT_USABLEEl plan no se puede usar por una razón que no cae en las anteriores.
BOOKING_CUTOFF_PASSEDLa 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_today dice
    cuál es, y cutoff_time es la hora local del hotel en formato HH:MM.
  • Mañana se sigue vendiendo. Lo que corresponde es ofrecer la estadía a
    partir de property_today + 1, no reintentar.
  • GET /availability ya lo refleja: una consulta que empieza hoy, pasada la
    hora de corte, devuelve cada unidad con available: false y
    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í que confirm nunca 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ódigoEstadoSignificado
KEY_BOOKING_QUOTA_EXCEEDED429La key creó su máximo de reservas en las últimas 24 horas.
KEY_HOLD_LIMIT_REACHED429La 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ódigoEstadoSignificado
WEBHOOK_SUBSCRIPTION_NOT_FOUND404La 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_PERMITTED403La 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ódigoEstadoSignificado
PROPERTY_HIBERNATED403La propiedad está en pausa total.
NOT_AVAILABLE_IN_HIBERNATION403La propiedad está en el plan básico de hibernación, que conserva los canales propios del hotel pero no las integraciones.
PROPERTY_HIBERNATION_SCHEDULED422Só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


Did this page help you?