Changelog de la API
Historial de cambios del contrato /api/v1. Los cambios
aditivos (campos nuevos) no requieren acción: tu
integración los ignora si no los usas. Los marcados como
cambio de comportamiento sí conviene revisarlos.
Política de versionado: mientras no se elimine ni se
renombre nada, la API sigue siendo v1. Si alguna vez hay
que romper compatibilidad, se publica /api/v2 y
v1 queda disponible al menos 6 meses,
anunciando el retiro con los headers Deprecation y
Sunset en las respuestas. Los cambios de comportamiento
se avisan por correo a los integradores registrados antes de
desplegarse.
Precio, antelación mínima y ventana de reserva: lo que decide el servidor
Publicado el 2026-10-18
-
Cambio de comportamiento
POST /api/v1/reservationspriceeis_includeddejan de ser campos del cuerpo: el precio y la inclusión salen de la tarifa de la categoría del huésped en ese restaurante, igual que en elPATCHdesde el 2026-09-19. Antes, si el restaurante no tenía tarifa para esa categoría, se guardaba lo que mandara el integrador. Mandarlos no da error: se ignoran. Sin tarifa, la reserva queda conprice: nulleis_included: null, y el hotel la corrige asignándole la categoría.Qué revisar: Si mandabas
priceois_included, deja de hacerlo y lee el precio de la respuesta delPOST. Si no los mandabas, no tienes que hacer nada. -
Corregido
GET /api/v1/restaurants/{id}/availability, POST /api/v1/reservationsEl mínimo de minutos antes del turno (
app_advance_booking_min_minutes) se cuenta en la zona horaria del hotel. Se contaba en la del servidor, así que en un hotel con otra zona (por ejemplo, Ciudad de México) se aceptaban o se rechazaban reservas con una hora de diferencia. Los hoteles en la zona de Cancún no cambian. -
Corregido
GET /api/v1/restaurants/{id}/availabilityLa ventana máxima de anticipación es la más corta entre
max_advance_booking_daysyapp_max_advance_booking_days, la misma que ya aplicaba elPOST. Antes la disponibilidad tomaba la de la app aunque la general fuera menor, ofrecía días que elPOSTdespués rechazaba con409 APP_ADVANCE_VIOLATION, y ahora esa consulta responde ese mismo error.
Una key acotada a restaurantes ya no ve reservas de los demás
Publicado el 2026-10-18
-
Cambio de comportamiento
GET /api/v1/reservations, GET/PATCH/DELETE /api/v1/reservations/{id}Si la key está acotada a ciertos restaurantes, el listado ya no incluye reservas de otros restaurantes del hotel, y leer, editar o cancelar una de ellas responde
404 NOT_FOUND. Antes el alcance solo se aplicaba al crear y al consultar disponibilidad. Una key sin restricción de restaurantes no cambia.Qué revisar: Si tu integración gestiona reservas de varios restaurantes con una key acotada, pide que la amplíen o usa una key por restaurante.
El límite de visitas por estancia y la cuota también valen al mover y por el canal del asistente
Publicado el 2026-09-19
-
Cambio de comportamiento
PATCH /api/v1/reservations/{id}Al cambiar la
datede una reserva se vuelve a evaluar el límite de visitas incluidas por estancia: si la habitación ya usó las visitas de esa categoría alrededor de la fecha nueva, responde409 STAY_LIMIT_REACHED(antes solo se evaluaba al crear, así que el límite se podía esquivar creando la reserva en otra fecha y moviéndola). Editar sin cambiar la fecha no se ve afectado. Solo aplica en hoteles que configuraronmax_per_stay.Qué revisar: Si tu app deja mover una reserva de fecha, trata
STAY_LIMIT_REACHEDen el PATCH igual que en el POST: ofrece otra fecha. -
Cambio de comportamiento
PATCH /api/v1/reservations/{id}En restaurantes que cobran un recargo por visita extra (en vez de bloquearla), mover una reserva incluida hacia una estancia con las visitas ya usadas también responde
409 STAY_LIMIT_REACHED: mover no recotiza el precio, y la reserva habría conservado "Incluido" donde corresponde el recargo. Una reserva que ya paga el recargo se mueve sin problema.Qué revisar: Para esa fecha, cancela la reserva y créala de nuevo: el POST aplica el recargo.
-
Corregido
Documentación (OpenAPI) de POST, PATCH y DELETE /api/v1/reservationsEl spec declaraba obligatorios campos que la API nunca exigió:
channel,is_manual_entry,is_exceptional,allergen_idsyrestriction_idsen el POST;id,allergen_idsyrestriction_idsen el PATCH; yopera_reservation_iden el DELETE. La API no cambia: cambia lo que el documento dice que es obligatorio.Qué revisar: Si validas tus cuerpos contra el OpenAPI, vuelve a bajar
/api/docs/spec. -
Corregido
Documentación (OpenAPI) de GET /api/v1/restaurants/{id}/availabilityLa
metade cuota (quota_exhausted,quota_limit_pax,quota_used_pax,quota_remaining_pax) estaba descrita solo en el texto de la ruta; ahora el esquemaAvailabilityResponsela declara (componenteAvailabilityQuotaMeta). La respuesta no cambia:metasigue viniendo solo si el hotel te configuró un tope en ese restaurante, y con la cuota agotada no traequota_remaining_pax.Qué revisar: Ninguna. Si generas tipos o validas contra el OpenAPI, vuelve a bajar
/api/docs/spec. -
Corregido
PATCH /api/v1/reservations/{id}Lo que no mandes ya no se borra. Antes, un PATCH sin
email,celebrations,description,children,allergen_idsorestriction_idslos dejaba vacíos (los niños en 0), aunque solo quisieras cambiar los comensales — y el GET no devuelve correo, celebraciones ni notas para poder reenviarlos. Ahora se conservan; una clave presente reemplaza, también vacía (allergen_ids: []quita todos).Qué revisar: Si reenviabas la reserva completa en cada PATCH para no perder datos, puedes dejar de hacerlo. Si dependías de que omitir
allergen_idslos borrara, manda[]explícito. -
Corregido
PATCH /api/v1/reservations/{id}Editar una reserva ya no altera su precio congelado. Antes, cualquier PATCH dejaba
priceeis_includedennull, y si el cuerpo los traía, los escribía. Ahora se ignoran: el precio lo fija el servidor al crear. -
Corregido
POST /api/v1/reservations · GET /api/v1/reservationsLas reservas creadas con
X-Mesatto-Channel: assistantahora cuentan contra la cuota diaria del integrador (APP_QUOTA_EXCEEDED) y aparecen en el listado sinopera_reservation_id, igual que las del canalapp. Antes quedaban fuera de las dos cosas. -
Cambio de comportamiento
Todos los endpoints (rate limit)El límite de requests por minuto (
429 RATE_LIMIT_EXCEEDED) se cuenta por ORGANIZACIÓN y no por API key: todas las keys de una organización comparten la ventana de 60 s. El número es el mismo de siempre (rate_limit_per_minenGET /api/v1/me); antes cada key tenía ese cupo para sí sola.Qué revisar: Si tu organización usa varias keys (app, kiosco, asistente), reparte el tráfico pensando en un cupo común y respeta
Retry-After. Si no alcanza, pídele al hotel que suba de plan. -
Nuevo
POST /api/v1/reservations · PUT /api/v1/restaurants/{id}/floor-planCódigo nuevo
409 IDEMPOTENCY_IN_PROGRESS: dos pedidos SIMULTÁNEOS con la mismaIdempotency-Keyya no se ejecutan los dos. El primero se procesa; el segundo recibe este 409 y no escribe nada. Antes, si llegaban a la vez, podían crear dos reservas.Qué revisar: Trátalo como reintentable: espera uno o dos segundos y repite el pedido con la misma key; recibirás la respuesta del original con
Idempotent-Replayed: true. -
Cambio de comportamiento
Todos los endpoints que validan un cuerpo o un queryLos mensajes de
fieldErrorsque antes salían con el texto por defecto en inglés (Invalid input: expected string, received undefined,Invalid UUID…) ahora salen en español, como el resto del contrato; un campo requerido ausente dice "Este campo es obligatorio". Los códigos (error) y los nombres de campo no cambian.Qué revisar: Si tu integración compara el TEXTO de
fieldErrors, deja de hacerlo: el contrato estable eserror+ el nombre del campo. -
Corregido
PATCH /api/v1/reservations/{id}Mover una reserva a un día (o turno) de cierre responde
409 RESTAURANT_CLOSED, igual que el POST. Antes respondía500 INTERNAL_ERROR. -
Corregido
POST · PATCH /api/v1/reservations · GET /api/v1/restaurants/{id}/availabilitySi el servidor no puede leer los cierres del restaurante responde
500 INTERNAL_ERRORy no escribe ni ofrece nada, en vez de tratar el día como abierto. Es transitorio: reintenta con backoff. -
Nuevo
GET /api/v1/hotelsCada hotel trae
timezone, su zona horaria IANA (por ejemploAmerica/Cancun). Las fechas de las reservas son locales al hotel: úsala para calcular su "hoy" en vez del reloj de UTC, que por la tarde ya va un día adelante. Campo nuevo; no rompe a nadie.
Zona preferida al reservar
Publicado el 2026-09-17
-
Nuevo
GET /api/v1/restaurants/{id}Campo
areas(id,name): las zonas que el huésped puede pedir, como "Terraza" o "Interior". Solo en restaurantes que venden por mesa; en los demás llega vacío. -
Nuevo
GET /api/v1/restaurants/{id}/availabilityParámetro opcional
area_id: con él,fitsymax_available_single_table_capacitymiran solo las mesas de esa zona. Una zona que el restaurante no ofrece responde422 VALIDATION_ERROR. -
Nuevo
POST /api/v1/reservationsCampo opcional
preferred_area_id: la mesa se busca SOLO en esa zona; si no queda lugar ahí, respondeRESERVATION_TABLES_FULL(409) aunque haya mesas en otra. Sin el campo, todo sigue igual. Nunca se elige una mesa concreta.
Los restaurantes con plano de sala pueden vender por mesa
Publicado el 2026-09-17
-
Nuevo
GET /api/v1/reservations · GET /api/v1/reservations/{id}Campo
tablecon la mesa o combinación asignada (kind,code,area,assignment:autosi la eligió el sistema,manualsi una persona). Solo lectura: la API no asigna mesas.nullsi el restaurante no usa plano o la reserva todavía no tiene mesa. -
Cambio de comportamiento
GET /api/v1/restaurants/{id}/availability · POST /api/v1/reservations · PATCH /api/v1/reservations/{id}Un restaurante puede activar la **disponibilidad por mesas**: cada reserva recibe una mesa concreta y solo se vende lo que cabe en las mesas en servicio (respetando el mínimo y el máximo de personas de cada mesa y las combinaciones que el restaurante definió). En esos restaurantes
fitspuede serfalseaunqueremainingalcance —por ejemplo, 2 personas cuando la única mesa libre es para 3 a 4—,max_available_single_table_capacitysale de las mesas reales, y elPOST/PATCHrespondeRESERVATION_TABLES_FULL(409) cuando no hay mesa. Nada cambia hasta que el restaurante lo active.Qué revisar: Filtra los turnos por
fitscon elpaxreal del grupo, no porremaining: es lo único que coincide con lo que elPOSTva a aceptar. TrataRESERVATION_TABLES_FULLcomo «sin disponibilidad para ese tamaño de grupo».
Las excepciones de un turno también pueden cerrar el día anterior
Publicado el 2026-09-16
-
Cambio de comportamiento
GET /api/v1/restaurants/{id}/availability · POST /api/v1/reservationsEl corte propio de un turno para la API puede ser una hora fija del **día anterior** (antes solo del mismo día). Un turno así deja de listarse para una fecha a esa hora del día previo y el
POSTrespondeAPP_CUTOFF_PASSED(409), como cualquier otro corte. Enapp_policy_summary.same_day_cutoff_timessolo aparecen los cortes del mismo día. Nada cambia hasta que el hotel lo configure en un turno.Qué revisar: No hay que cambiar código: el código de error y el envelope son los mismos. Si cacheas la disponibilidad de fechas futuras, ten en cuenta que un turno puede cerrarse un día antes.
-
Cambio de comportamiento
DELETE /api/v1/reservations/{id}Un turno puede tener su propia hora fija de cancelación (del mismo día o del anterior), que reemplaza a la del restaurante para las reservas de ese turno. Convive con las horas antes del turno: gana la que cierre antes, y pasado ese momento la respuesta sigue siendo
CANCEL_DEADLINE_PASSED(409).Qué revisar: Si le anuncias al huésped el plazo de cancelación leyendo
booking_policydel restaurante, ten en cuenta que un turno puede tener uno distinto: la respuesta delDELETEes la que manda.
El plano de sala se lee y se importa por la API
Publicado el 2026-09-15
-
Nuevo
GET /api/v1/restaurants/{id}/floor-planEl plano de un restaurante: áreas, mesas numeradas con su posición y capacidad, elementos fijos (barra, fuente, columna…), qué mesas están en servicio en cada turno con el cupo que de ahí se **deriva**, y los metadatos de la imagen de fondo. Dos banderas mandan:
has_floor_plan(el restaurante dibujó su sala; sin eso trabaja con tipos y cantidades a mano) yenabled(la organización usa planos). Mismo alcance queGET /restaurants/{id}: no pide permiso extra. -
Nuevo
PUT /api/v1/restaurants/{id}/floor-planImporta un plano completo en **una sola operación atómica**: o queda todo o no queda nada. Requiere el permiso
restaurants.floor_plan(compruébalo enGET /meantes de ofrecer la función). Las áreas se referencian por nombre y las mesas por código, así que no hace falta conocer ningún id antes de importar, ystrategy: "merge"es idempotente. **Empieza siempre condry_run: true**: devuelve el resumen, los avisos ylosing_assignment—las reservas futuras que se quedarían sin mesa sireplaceborra la suya— sin escribir ni una fila. AceptaIdempotency-Key. -
Nuevo
PUT · DELETE /api/v1/restaurants/{id}/floor-plan/backgroundSube, reemplaza o quita la imagen sobre la que se calca el plano, en base64. Límites duros y sin conversión del lado del servidor: 1 MiB de binario, 1600 px por lado y 1.5 MiB de cuerpo; fuera de ahí, el código nuevo
PAYLOAD_TOO_LARGE(413) dice qué límite se pasó y cuál es. El tipo se comprueba por el encabezado real del archivo. El binario **entra pero no sale**: ningún endpoint público sirve la imagen. -
Nuevo
PUT /api/v1/restaurants/{id}/floor-plan · /floor-plan/backgroundCódigo nuevo
FLOOR_PLAN_DISABLED(409): la organización o el hotel tiene el plano de sala apagado en sus reglas del negocio. Se responde también endry_run— un importador que simulara igual mostraría el resumen de algo que nunca va a poder aplicar. La lectura sigue funcionando y devuelveenabled: false.
Los cierres y el plazo de cancelación son del restaurante, no solo del turno
Publicado el 2026-09-15
-
Nuevo
GET /api/v1/hotels/{id}/restaurants · GET /api/v1/restaurants/{id}Objeto
booking_policycon la política del restaurante tal como está configurada:same_day_cutoff_time+cutoff_days_before(el corte fijo puede ser del **día anterior**, no solo del mismo día),app_advance_booking_min_minutes,cancel_deadline_hours,cancel_cutoff_timeycancel_cutoff_days_before. Los turnos ya no son la única fuente: el restaurante define el default y el turno solo lo afina — cuando los dos aplican, gana el más exigente. -
Nuevo
GET /api/v1/hotels/{id}/restaurantsapp_policy_summarypasa a combinar restaurante **y** turnos (antes resumía solo los turnos):min_minutes_beforeymin_cancel_hours_beforeson el máximo de las dos capas, ysame_day_cutoff_timessuma la hora del restaurante cuando es del mismo día. Es un prechequeo orientativo; la verdad de cada fecha sigue siendoGET /restaurants/{id}/availability. -
Cambio de comportamiento
GET /api/v1/restaurants/{id}/availability · POST /api/v1/reservationsUn restaurante con
cutoff_days_before = 1cierra las reservas de una fecha a la hora fija del **día anterior**: los turnos de esa fecha dejan de listarse antes que antes y elPOSTrespondeAPP_CUTOFF_PASSED(409). Además,app_advance_booking_min_minutesdel **restaurante** ahora se evalúa aunque el turno no lo tenga configurado. Ningún restaurante cambia de comportamiento hasta que el hotel configure esos campos.Qué revisar: No hay que cambiar código: los códigos de error y el envelope son los mismos. Si cacheas la disponibilidad, revisa que el TTL no sea mayor al corte — y si muestras la política al huésped, léela de
booking_policyen vez de derivarla de los turnos. -
Cambio de comportamiento
DELETE /api/v1/reservations/{id}El plazo de cancelación combina las dos capas: las horas antes del turno (el turno pisa al restaurante) **y** la hora fija
cancel_cutoff_timedel día que indiquecancel_cutoff_days_before. Gana el que cierre antes; pasado ese momento la respuesta sigue siendoCANCEL_DEADLINE_PASSED(409).Qué revisar: Si le mostrabas al huésped «puedes cancelar hasta N horas antes» calculándolo con
app_cancel_deadline_hoursdel turno, usabooking_policydel restaurante junto con el del turno: con una hora fija configurada, el plazo real puede cerrar antes de lo que decía tu cuenta. -
Corregido
GET /api/v1/hotels/{id}/restaurantsEl esquema publicado del listado declaraba cuatro campos (
id,name,hotel_id,is_open,adults_only) mientras la respuesta traía ademásdress_code,max_guests_per_reservation,max_advance_booking_days,app_max_advance_booking_daysyapp_policy_summary. La doc ya dice exactamente lo que devuelve el endpoint; nada cambió en la respuesta. -
Corregido
GET /api/v1/restaurants/{id}El detalle devolvía la política del restaurante en seis campos sueltos y el listado en un objeto
booking_policy: dos formas del mismo dato. Ahora el detalle devuelve **también**booking_policy, idéntico al del listado. Los seis campos sueltos siguen ahí y no se van a quitar env1, pero quedan marcados como deprecados en la doc.
Una fecha pasada deja de confundirse con la ventana de reserva
Publicado el 2026-09-10
-
Cambio de comportamiento
GET /api/v1/restaurants/{id}/availabilityConsultar una fecha **ya pasada** ahora responde
RESERVATION_PASTen vez deAPP_ADVANCE_VIOLATION. El HTTP sigue siendo **409** y elmessageno cambia. Eran dos problemas distintos bajo el mismo código: "esa fecha no existe" y "esa fecha está fuera de tu ventana de reserva". El segundo se resuelve proponiendo otra fecha; el primero no se resuelve nunca, y los clientes que ramificaban por el código reintentaban en bucle.Qué revisar: Si ramificas por el
errorde este endpoint, agregaRESERVATION_PASTa la rama que ya usas paraAPP_ADVANCE_VIOLATION.APP_ADVANCE_VIOLATIONsigue existiendo, pero ahora significa solo lo que dice: la fecha excedeapp_max_advance_booking_days. Si no ramificas por código, no tienes que hacer nada.
Un hotel sin PMS deja de responder 503
Publicado el 2026-09-10
-
Cambio de comportamiento
POST /api/v1/opera/lookupCuando el hotel no tiene una integración con PMS activa, la respuesta pasa de
INTERNAL_ERROR(HTTP **503**) aPMS_NOT_CONFIGURED(HTTP **409**). No es una falla del servicio: es cómo está configurado ese hotel, y no se arregla reintentando. El 503 decía lo contrario y los clientes reintentaban en bucle.Qué revisar: Si tu integración reintenta ante 5xx, deja de reintentar
POST /opera/lookuppara ese hotel: no va a cambiar hasta que el hotel conecte un PMS. Trátalo como 409 permanente y sigue el flujo sin datos de estancia (el huésped los escribe a mano).
Capacidades de la key y canal para asistentes de IA (MCP)
Publicado el 2026-09-09
-
Nuevo
GET /api/v1/meDescribe la key que llama: organización, hoteles visibles,
restaurant_ids, permisos efectivos y límite por minuto. Sirve para validar una integración sin adivinar y es lo que usa el **servidor MCP de Mesatto** para exponer solo las herramientas que la key puede ejecutar. -
Nuevo
POST /api/v1/reservationsHeader opcional
X-Mesatto-Channel: assistant(reservado para asistentes de IA conectados por MCP): la reserva queda conchannel='assistant'en reportes y auditoría, con la misma política de la app por turno. Sin el header nada cambia (channel='app'). Los hoteles pueden apagar el canal en Configuración → Canales.
Reglas por hotel: canal, inclusión por estancia y noches mínimas
Publicado el 2026-09-08
-
Nuevo
Todos los endpointsCódigo
ORG_SUSPENDED(HTTP **403**) cuando la organización dueña de la key está suspendida o cancelada (prueba vencida sin plan, pago pendiente por más de 14 días o baja). Se responde en cualquier endpoint, también en lecturas.Qué revisar: Tratarlo como permanente hasta que el administrador del hotel regularice la suscripción; no reintentar en bucle.
-
Cambio de comportamiento
Todos los endpointsLa API corre con la identidad de la **cuenta de servicio** dueña de la key (RLS), no con un rol de servidor: solo se ven la organización, los hoteles y los restaurantes asignados a esa cuenta. Una key puede además acotarse a una lista de restaurantes (
restaurant_ids): fuera de ella, restaurantes y disponibilidad responden404y crear/modificar reservas responde403 FORBIDDEN_SCOPE.Qué revisar: Verificar que la cuenta de servicio tenga acceso a todos los hoteles y restaurantes que la integración usa (panel → Usuarios → cuenta de servicio). Nada cambia si ya lo tenía.
-
Cambio de comportamiento
POST /api/v1/reservationsTres códigos de error nuevos (HTTP **409**), según la configuración de cada hotel:
CHANNEL_DISABLED(el hotel apagó el canal app para ese restaurante),STAY_LIMIT_REACHED(la categoría del huésped incluye N visitas por estancia y la habitación ya las usó) yMIN_NIGHTS_NOT_MET(la categoría exige una estancia mínima ycheck_in_date/check_out_dateno la cumplen). Sin esa configuración nada cambia. Cuando el hotel cobra un recargo tras el límite, la reserva se acepta yprice/child_pricereflejan el recargo.Qué revisar: Tratar los tres como 409 permanentes para esa reserva (no reintentar igual). Mostrar el
messageal huésped o localizar porerror. Enviarcheck_in_date/check_out_datecuando se conozcan: sin ellos la estancia se estima por habitación en una ventana de ±7 días. -
Cambio de comportamiento
Todos los endpointsRate limit por **plan de la organización** (
Retry-AfteryX-RateLimit-*en el 429): 60/min en prueba, 300 starter, 1,000 pro, 5,000 enterprise, siempre por debajo del techo global de la plataforma.Qué revisar: Respetar
Retry-Afteren 429 y cachear catálogos; si el plan queda corto, se ajusta desde la plataforma. -
Cambio de comportamiento
WebhooksLos destinos deben ser **https://** y resolver a direcciones públicas; se verifica al guardar y en cada entrega, y no se siguen redirecciones (un 3xx cuenta como fallo). Un destino privado o interno marca la entrega como fallida sin reintentos.
Qué revisar: Publicar el receptor en un host público con TLS. Sin cambios si ya era así.
-
Cambio de comportamiento
WebhooksLas cabeceras pasan a
X-Mesatto-Event,X-Mesatto-Delivery-IdyX-Mesatto-Signature(mismo HMAC-SHA256 del body),User-Agent: Mesatto-Webhook/2.0, y el envelope incluyeversion: 2. No hay periodo de doble cabecera: no existen suscripciones activas anteriores a este cambio.Qué revisar: Leer la firma desde
X-Mesatto-Signature. El cálculo del HMAC no cambia. -
Cambio de comportamiento
POST /api/v1/reservationsFechas y cortes (
APP_CUTOFF_PASSED, antelación) se evalúan en la **zona horaria del hotel** de la reserva, no en una zona única de plataforma.Qué revisar: Ninguna si operas un solo hotel. Con hoteles en zonas distintas, no asumas una misma hora de corte para todos.
Paginación por cursor en el listado de reservas — offset queda deprecated
Publicado el 2026-08-06
-
Nuevo
GET /api/v1/reservationsPaginación por **cursor** (keyset): la primera página se pide sin parámetros nuevos y ahora incluye
meta.next_cursor(string opaco, onullsi no hay más páginas); las siguientes se piden concursor=<meta.next_cursor>manteniendo los mismos filtros. En modo cursor la respuesta traemeta { limit, has_more, next_cursor }— sintotalnioffset(el total se informa una vez, en la primera página). A diferencia deoffset, el cursor **no duplica ni omite filas** cuando se crean o cancelan reservas entre página y página. El cursor es opaco: úsalo tal cual, sin parsearlo ni fabricarlo. Código de error nuevoINVALID_CURSOR(HTTP **422**) para cursors malformados — la recuperación es reiniciar la paginación sincursor. -
En desuso
GET /api/v1/reservationsoffsetqueda **deprecated**. Sigue funcionando exactamente igual que hasta ahora y **no hay fecha de remoción** — cuando la haya, se avisará con antelación por este canal.cursoryoffsetjuntos en el mismo request responden422 VALIDATION_ERROR(son excluyentes).Qué revisar: Sin urgencia: nada se rompe hoy. Recomendado migrar a
cursorcuando toques ese código — es más eficiente del lado del servidor y elimina los duplicados/salteos entre páginas. -
Cambio de comportamiento
GET /api/v1/reservationsEl orden del listado ahora es **determinístico**: las reservas con la misma fecha+hora se devuelven en orden estable entre requests (desempate interno por id). Antes el orden entre esos empates era indefinido y podía variar de un request al siguiente — en el borde de página, eso podía duplicar u omitir filas al paginar con
offset.Qué revisar: Si tu integración dependía del orden relativo entre reservas con la misma fecha+hora, revísala: el orden es estable a partir de este cambio, pero puede ser distinto del que venías observando. Si no depende de eso (lo habitual), no hay nada que hacer.
release_minutes entra en vigor — los turnos cierran minutos antes de empezar
Publicado el 2026-08-06
-
Cambio de comportamiento
GET /api/v1/restaurants/{id}/availability · POST /api/v1/reservations · PATCH /api/v1/reservations/{id}Los campos
release_minutes(del restaurante y de cada turno), que la API ya exponía, **ahora se evalúan**: cada turno deja de aceptar reservas para HOY esos minutos antes de sustart_time. El corte efectivo de un turno es el **más temprano** entre este release y los cutoffs absolutos ya vigentes (same_day_cutoff_timedel restaurante yapp_same_day_cutoff_timedel turno). Pasado el corte,GET .../availabilitydeja de listar ese turno para HOY, y elPOST(o unPATCHque mueva la reserva a ese turno) respondeAPP_CUTOFF_PASSED(HTTP **409**) — el mismo código del cutoff absoluto. A diferencia del cutoff por restaurante, esto rige DESDE EL DESPLIEGUE: los turnos ya tienen 5-10 minutos configurados por el hotel, así que la disponibilidad de última hora que veías hasta ahora (reservar un turno ya empezado) desaparece. Las fechas futuras no cambian en ningún caso.Qué revisar: Nada que cambiar en el código si ya tratas
APP_CUTOFF_PASSEDcomo un 409 permanente del día (recomendación del aviso de agosto). Revisa cachés de availability: un turno que a las 17:50 aparecía listado puede no estar a las 17:56.GET /api/v1/restaurants/{id}y los turnos de availability ya exponenrelease_minutes— puedes anticipar el cierre en tu UI en vez de esperar el 409.
Cutoff del mismo día por restaurante — menos disponibilidad intradía
Publicado el 2026-08-04
-
Cambio de comportamiento
GET /api/v1/restaurants/{id}/availability · POST /api/v1/reservations · PATCH /api/v1/reservations/{id}El hotel puede configurar, por restaurante, una **hora fija a partir de la cual no se aceptan reservas para el mismo día** (
same_day_cutoff_time, en la zona horaria del hotel). A diferencia del cutoff que ya existía por turno —que solo aplicaba a este canal— este aplica a **todos los turnos del restaurante y a todos los canales**, incluido el panel del concierge. Pasada esa hora,GET .../availabilitydeja de listar los turnos de HOY (puede devolverdatavacío) y elPOSTlos rechaza conAPP_CUTOFF_PASSED(HTTP **409**). Cuando conviven el cutoff del restaurante y el del turno, **gana el más temprano**. El campo se expone enGET /api/v1/restaurants/{id}comosame_day_cutoff_time(null= sin cutoff). Es opt-in: mientras el hotel no configure una hora, nada cambia.Qué revisar: Trata
APP_CUTOFF_PASSEDcomo un 409 permanente, sin reintentos — volver a intentar el mismo día no va a funcionar. Undatavacío en availability para HOY ya no significa necesariamente que el restaurante esté lleno o cerrado: puede ser el cutoff. Si muestras horarios cacheados, refresca availability antes de confirmar. Copy sugerido para el huésped: «Las reservas para hoy en este restaurante ya cerraron. Elige otra fecha.» -
Nuevo
GET /api/v1/restaurants/{id}Campo nuevo
same_day_cutoff_time(stringHH:MM:SSonull) con la hora de cierre de reservas del mismo día. Sirve para anticiparle al huésped el corte antes de que intente reservar, en vez de esperar el 409.
Una reserva por habitación y día — código nuevo RESERVATION_DUPLICATE_ROOM
Publicado el 2026-07-31
-
Cambio de comportamiento
POST /api/v1/reservations · PATCH /api/v1/reservations/{id}El hotel definió que una habitación solo puede tener una reserva de restaurante activa por día, **en todos los restaurantes del hotel**. Hasta ahora la regla existía solo cuando la reserva traía
opera_reservation_id; a partir de este cambio se evalúa también por número de habitación, así que puede rechazarse una reserva que antes pasaba: por ejemplo, si esa habitación ya tiene una reserva cargada por el concierge en otro restaurante. La respuesta trae el códigoRESERVATION_DUPLICATE_ROOM(HTTP **409**) conmessagebilingüe (español y luego inglés) listo para mostrarle al huésped. La regla solo se evalúa cuando la reserva llevaroom_number; si se omite el campo, no aplica.Qué revisar: Trata
RESERVATION_DUPLICATE_ROOMcomo un 409 permanente, sin reintentos, igual queRESERVATION_DUPLICATE_DAY. Si tu flujo permite elegir restaurante, conviene avisarle al huésped que ya tiene una reserva ese día antes de llegar al error. Recomendado: sigue enviandoroom_numbersiempre (hoy lo haces en el 100% de los casos) — es lo que permite detectar el choque.
El schema ApiError de la doc ahora declara RESERVATION_DUPLICATE_DAY
Publicado el 2026-07-31
-
Corregido
GET /api/docs/specEl enum
errordel schemaApiErroren el spec OpenAPI omitíaRESERVATION_DUPLICATE_DAY, aunque la API lo emite desde el 2026-07-29 (ver la entrada de esa fecha). Solo se corrige la documentación — el comportamiento de la API no cambia.
Código nuevo RESERVATION_DUPLICATE_DAY + mensajes bilingües al huésped
Publicado el 2026-07-29
-
Cambio de comportamiento
POST /api/v1/reservations · PATCH /api/v1/reservations/{id}Cuando el huésped ya tiene una reserva de restaurante activa ese día (una reserva por día y por habitación), la respuesta ahora trae el código semántico
RESERVATION_DUPLICATE_DAY(HTTP **409**). Antes esta regla salía como **500** sin código estable. Además, elmessagede este caso y el deAPP_QUOTA_EXCEEDEDvienen **bilingües** (español y luego inglés, separados por línea en blanco), listos para mostrarle al huésped. El PATCH también aplica esta validación al mover una reserva a una fecha con colisión (antes devolvía el error crudo de la base de datos).Qué revisar: Si tratabas el 500 de este caso como error de servidor con reintentos, cámbialo: es un 409 permanente con código
RESERVATION_DUPLICATE_DAY. Recomendado: localiza tu propio copy contra elerrorcode; si prefieres, elmessageya viene en ES + EN.
PATCH: mover una reserva a una fecha pasada se rechaza
Publicado el 2026-07-29
-
Cambio de comportamiento
PATCH /api/v1/reservations/{id}Antes solo se rechazaba EDITAR una reserva cuya fecha ya había pasado. Ahora también se rechaza MOVER una reserva vigente hacia una fecha anterior a hoy (zona horaria del hotel,
America/Cancun): respondeRESERVATION_PAST(409). Mover a hoy o al futuro sigue permitido.Qué revisar: Si tu app permite cambiar la fecha de una reserva, no ofrezcas fechas anteriores a hoy — el 409
RESERVATION_PASTya cubría editar reservas pasadas; ahora también cubre eldatede destino.
Reservas sin opera_reservation_id dejan de ser inalcanzables
Publicado el 2026-07-29
-
Nuevo
GET/PATCH/DELETE /api/v1/reservations/{id} · GET /api/v1/reservationsopera_reservation_idpasa a ser OPCIONAL en el ownership claim. Para una reserva creada **sin** ese campo, la identidad ahora se prueba con que la reserva la haya creado TU API key vía este canal (antes esas reservas quedaban inalcanzables: no existía valor que satisficiera el claim). Las reservas que SÍ tienenopera_reservation_idsiguen exigiéndolo — el comportamiento existente no cambia. En el listado, omitir el parámetro devuelve solo tus reservas huérfanas deopera_reservation_id.Qué revisar: Nada que cambiar si siempre envías
opera_reservation_id(recomendado). Si creas reservas sin él, ahora puedes leerlas/editarlas/cancelarlas omitiendo ese campo del claim. Ojo: la identidad del fallback es TU service account — si algún día se migra a un service account nuevo, las reservas huérfanas del anterior dejan de ser alcanzables.
Cuota de capacidad diaria por integrador
Publicado el 2026-07-29
-
Cambio de comportamiento
GET /api/v1/restaurants/{id}/availabilityEl hotel puede topar cuánta capacidad diaria de un restaurante ocupa cada integrador. Donde haya tope configurado, el
remainingde cada turno viene limitado por lo que te queda de cuota ese día, y la respuesta trae un objetometaconquota_exhausted,quota_limit_pax,quota_used_paxyquota_remaining_pax. Al agotarse la cuota,dataviene vacío conmeta.quota_exhausted: true. **Por defecto ningún restaurante tiene tope**: mientras el hotel no active uno, nada cambia.Qué revisar: No trates un
datavacío como error ni como restaurante cerrado: revisameta.quota_exhausted. Si estrue, muéstrale al huésped que no hay disponibilidad para esa fecha por tu canal y ofrécele otra fecha. -
Cambio de comportamiento
POST /api/v1/reservations · PATCH /api/v1/reservations/{id}Si la reserva no entra en la cuota que te queda ese día en ese restaurante, se rechaza con
APP_QUOTA_EXCEEDED(409). Cancelar una reserva libera cuota automáticamente.Qué revisar: Maneja
APP_QUOTA_EXCEEDEDcomo un 409 más. El mensaje sugerido al huésped es: «No hay disponibilidad para esta fecha en este canal. Intenta con otra fecha.»
Ronda de ajustes del hotel — acomodo de mesas, ocupación de Opera y solo-adultos
Publicado el 2026-07-28
-
Cambio de comportamiento
GET /api/v1/restaurants/{id}/availabilityEl flag
fitsahora exige, además de cupo de personas (remaining >= pax), que exista un acomodo de mesas posible. Antes solo miraba el cupo, así que devolvíatruepara grupos que elPOST /api/v1/reservationsrechazaba después conRESERVATION_TABLES_FULL.Qué revisar: Si filtras turnos por
remaining >= pax, cambia afits === true. El resultado neto es que verás menos turnos ofrecidos, pero los que se ofrecen sí aceptan la reserva. -
Nuevo
GET /api/v1/restaurants/{id}/availabilityCampo
max_available_single_table_capacitypor turno: capacidad de la mesa individual más grande que sigue libre.null= el turno no tiene mesas configuradas (solo aplica el cupo);0= no queda ninguna mesa individual libre y habría que combinar. -
Cambio de comportamiento
POST /api/v1/reservations · PATCH /api/v1/reservations/{id}Se valida el calendario del turno: días de la semana en que aplica, vigencia (fecha de inicio/fin) y turno activo. Antes solo se filtraba al listar disponibilidad, así que era posible crear o mover una reserva a un día en que el restaurante no abre. Rechaza con
SEATING_UNAVAILABLE(409).Qué revisar: Usa siempre los turnos que devuelve el endpoint de disponibilidad para esa fecha; no reutilices un
seating_idde otra fecha. -
Cambio de comportamiento
PATCH /api/v1/reservations/{id}Las reservas de fechas pasadas ya no se pueden editar: responde
RESERVATION_PAST(409). Cancelar una reserva pasada sigue disponible.Qué revisar: Si tu app permite editar el histórico, deshabilita la edición para reservas con fecha anterior a hoy (zona horaria del hotel, America/Cancun).
-
Cambio de comportamiento
PATCH /api/v1/reservations/{id}El
seating_idenviado debe pertenecer al mismo restaurante de la reserva. Antes un turno de otro restaurante pasaba la validación y dejaba la reserva en un estado inconsistente. Rechaza conVALIDATION_ERROR(422). -
Nuevo
POST /api/v1/opera/lookupCampos
adult_countychild_countdentro deguest: la ocupación registrada en Opera para esa habitación. Vienennullsi el PMS no los informa.Qué revisar: Opcional: úsalos para precargar el número de comensales (
guests = adult_count + child_count) en vez de pedirlo desde cero. -
Nuevo
GET /api/v1/hotels/{id}/restaurants · GET /api/v1/restaurants/{id}Campo
adults_only(boolean): el restaurante admite solo adultos (18+). La API **no** rechaza reservas con menores por este flag — es informativo.Qué revisar: Muestra el aviso al huésped antes de confirmar la reserva en restaurantes con
adults_only: true. -
Nuevo
TodosCódigos de error nuevos:
SEATING_UNAVAILABLEyRESERVATION_PAST(ambos HTTP 409).Qué revisar: Agrégalos a tu manejo de errores. Son condiciones permanentes: no tiene sentido reintentar la misma request.
-
Corregido
GET /api/v1/reservations/{id}El campo
currencydevolvía siemprenullpor leer una columna equivocada. Ahora trae la moneda configurada (el listado ya la devolvía bien). -
Corregido
POST · PATCH /api/v1/reservationsVarias reglas de negocio respondían HTTP 500
INTERNAL_ERRORen lugar de un 409 con código semántico, lo que sugería una falla transitoria y invitaba a reintentar. Ahora devuelven el código correcto. -
Corregido
GET /api/v1/reservations · GET /api/v1/reservations/{id}Nombres de turno (
seating.name) que habían quedado desincronizados de su hora real tras editarlos (p. ej. mostraba14:00con hora real 15:00).
¿Dudas sobre un cambio? Escríbenos a soporte@mesatto.com.