Cómo usar la API REST pública de Nowistay

Actualizado

La API REST pública de Nowistay conecta tus scripts y las integraciones de partners con tu cuenta. Puedes leer y actualizar los ajustes de los alojamientos, los calendarios, las reservas y las misiones; gestionar fichas de huéspedes, conversaciones, conocimiento privado y guías de bienvenida; solicitar pagos de reservas y reembolsos por cancelación; y leer upsells, compras e informes de cartera. Consulta los esquemas exactos de solicitudes y respuestas en /public/docs y /public/openapi.json.

Esta guía te presenta tres caminos: la forma más rápida de probar la API (el playground de la documentación interactiva), la forma más sencilla de ejecutar tus propios scripts (una credencial de API personal creada en la aplicación) y el flujo OAuth estándar que usarás al crear una aplicación para otros anfitriones.

Qué puedes hacer con la API

  • Alojamientos y calendarios: buscar y leer alojamientos, actualizar ajustes operativos y gestionar la disponibilidad diaria, las tarifas y las restricciones de estancia.
  • Reservas y pagos: crear reservas directas, actualizar reservas elegibles, solicitar el pago durante la creación, ordenar reembolsos por cancelación y leer el estado seguro de las solicitudes de pago.
  • Huéspedes: leer tu base de clientes, crear fichas de huéspedes y consultar el historial de estancias.
  • Misiones: crear, actualizar y asignar misiones, filtrar cargas de trabajo y leer los detalles y los informes de finalización de las misiones.
  • Conversaciones: encontrar conversaciones con huéspedes, leer sus mensajes y enviar una respuesta del anfitrión por el canal de envío seleccionado.
  • Conocimiento privado: leer, añadir y actualizar preguntas y respuestas de los alojamientos, o fusionarlas con otros alojamientos.
  • Guías de bienvenida: editar introducciones localizadas y páginas estructuradas, cambiar la posición de las páginas y subir imágenes.
  • Upsells e informes: leer el catálogo de upsells y las compras de las reservas, y exportar listas de reservas o de misiones en formato de tabla.
  • Herramientas existentes: los parámetros de reserva personalizados, la lectura de miembros del equipo, los feeds de actividad de los alojamientos y los webhooks de reservas siguen disponibles.

Antes de empezar

El acceso se comprueba según la titularidad y el derecho de asistente del alojamiento. Se necesita una conexión PMS activa para las operaciones que dependen de ella, como la gestión del calendario y la creación de reservas directas. Los demás recursos no necesitan una conexión PMS solo para leerse o editarse.

  • Necesitas una suscripción activa de AI Channel Manager en los alojamientos que quieras exponer. La API solo devuelve los alojamientos con ese derecho activo.
  • Inicias sesión como anfitrión o gestor de alojamientos. La API nunca expone alojamientos que no son tuyos.
  • La URL base es https://api.nowistay.com
  • Todos los endpoints bajo /public/v1/* requieren un token de acceso OAuth2, enviado como Authorization: Bearer ....

La forma más rápida: probar desde el playground de la documentación

Abre https://api.nowistay.com/public/docs en tu navegador. La página es una interfaz Swagger UI completa de la API, con un playground OAuth integrado que registra un cliente por ti automáticamente. No hace falta programar nada para probar llamadas.

  1. Actualiza la página sin caché (Cmd+Shift+R en Mac, Ctrl+F5 en Windows).
  2. Espera a que el pequeño aviso de la parte superior indique OAuth playground ready.
  3. Haz clic en el botón verde Authorize (Autorizar), arriba a la derecha.
  4. En el cuadro de diálogo, en la sección OAuth2, haz clic en Authorize (Autorizar).
  5. Se te redirige a la página de inicio de sesión y consentimiento de Nowistay.
  6. Inicia sesión como anfitrión o gestor de alojamientos con la suscripción de AI Channel Manager.
  7. Revisa los scopes que solicita el playground y apruébalos.
  8. Vuelves a /public/docs, ya autenticado.
  9. Abre GET /public/v1/properties, haz clic en Try it out (Probar) y después en Execute (Ejecutar).
  10. Deberías ver tus alojamientos en la respuesta.

A partir de ahí puedes probar cada endpoint con un clic, ver el JSON exacto que devuelve Nowistay y copiar el comando cURL de cualquier llamada. Es la forma recomendada de explorar la API antes de escribir código.

Importante. El cliente del playground vive solo en tu navegador. Sirve para probar, pero para una integración real debes crear una credencial de API personal o registrar tu propio cliente OAuth (ambas opciones se explican más abajo).

Tus propios scripts: crea una credencial de API personal

Si automatizas tu propia cuenta (un script de informes, una sincronización con tu herramienta de contabilidad, un pequeño panel), no necesitas el flujo OAuth completo. Crea una credencial de API personal en la aplicación e intercámbiala por un token en una sola llamada. Sin pantalla de consentimiento y sin refresh tokens que gestionar.

  1. En la aplicación, abre el menú y ve a Mi cuenta > Desarrollador, o abre directamente app.nowistay.com/hub/user/developer.
  2. Haz clic en Nueva credencial.
  3. Ponle un nombre claro (por ejemplo Reporting script) y marca solo los permisos que necesita.
  4. Haz clic en Crear credencial. La clave secreta se muestra una sola vez: cópiala ahora y guárdala en un lugar seguro. No podrás volver a verla.

Tu script intercambia entonces la credencial por un token de acceso (válido durante una hora) y llama a la API:

curl -X POST https://api.nowistay.com/public/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "client_id=YOUR_CLIENT_ID" \
  --data-urlencode "client_secret=YOUR_CLIENT_SECRET"

Cuando el token caduca, solicita uno nuevo del mismo modo. En este flujo no hay refresh token, y esa es la idea: dos líneas de código, nada que vigilar.

Gestionar tus credenciales

  • Último uso indica cuándo llamó cada credencial a la API por última vez. Es la forma más fácil de detectar un script antiguo que sigue funcionando o una credencial que puedes eliminar sin riesgo.
  • Rotar clave genera una nueva clave secreta si la actual pudo filtrarse, o cuando rotas las claves periódicamente. La nueva clave se muestra una sola vez; la anterior sigue funcionando durante 7 días para que tu script pueda migrar y después deja de funcionar.
  • Eliminar borra una credencial que ya no usas. Todo lo que la siga usando deja de funcionar de inmediato.

Puedes crear hasta 10 credenciales, cada una con su propio nombre y sus permisos. Una credencial solo lee y escribe los datos a los que puede acceder tu propia cuenta, y necesita la misma suscripción activa de AI Channel Manager que el resto de la API.

Cómo añadir scopes a un token de API existente

No puedes editar los scopes de un token ni de una credencial personal existentes. Crea una credencial de sustitución con el conjunto completo de permisos que necesita tu integración. Los clientes, tokens de acceso, códigos de autorización y refresh tokens existentes conservan sus permisos originales; no se añade ningún permiso automáticamente.

Credenciales personales: sustituir, probar y cambiar

  1. Abre Mi cuenta > Desarrollador, o usa https://app.nowistay.com/hub/user/developer. Anota los permisos que usa la integración actual.
  2. Haz clic en Nueva credencial. Ponle a la sustituta un nombre reconocible y selecciona los permisos existentes que sigues necesitando más los nuevos scopes. Por ejemplo, las actualizaciones de precios del calendario necesitan calendar.write; enviar mensajes necesita messages.write; solicitar el pago de una reserva necesita bookings.write y payments.write.
  3. Haz clic en Crear credencial y copia de forma segura el Client ID y el Client secret que se muestran una sola vez.
  4. Solicita un nuevo token de acceso a POST /public/oauth/token con grant_type=client_credentials y las credenciales de sustitución. Comprueba el valor scope devuelto y prueba un endpoint permitido por esos scopes. GET /public/v1/properties requiere específicamente properties.read.
  5. Actualiza las credenciales de tu integración y el token de acceso en caché. Verifica que funcionan tanto sus operaciones existentes como la nueva operación habilitada.
  6. Cuando todos los consumidores hayan cambiado, usa Eliminar en la credencial anterior. Al eliminarla, las integraciones que sigan usándola dejan de funcionar de inmediato.

Rotar clave no añade scopes. Solo sustituye la clave secreta de la misma credencial; la clave anterior sigue siendo válida durante 7 días. Actualizar o solicitar otro token de acceso no puede ampliar el permiso original. Los scopes de escritura no incluyen automáticamente los de lectura.

Aplicaciones OAuth de partners: nuevo registro y nuevo consentimiento

  1. Envía POST /public/oauth/register con el conjunto completo de scopes deseado y los datos habituales de tu aplicación. Un registro equivalente con exactamente el mismo conjunto de scopes reutiliza el cliente correspondiente; si cambia el conjunto de scopes, se crea un nuevo registro. Guarda las credenciales de cliente devueltas.
  2. Inicia un nuevo flujo de código de autorización con PKCE para ese registro. Pide al anfitrión que apruebe los scopes deseados en la página de consentimiento y después intercambia el nuevo código por tokens.
  3. Cambia la integración a esos tokens tras probarlos. Los refresh tokens y códigos de autorización existentes conservan sus scopes originales y no sirven para añadir permisos.

En el playground de la documentación, actualiza la página y vuelve a autorizar con los scopes necesarios. Los nuevos permisos se muestran con etiquetas legibles y sus identificadores en inglés, francés, español, alemán, neerlandés e italiano.

OAuth, en palabras sencillas

La API usa el estándar OAuth 2.1 con el flujo de código de autorización y PKCE. En una frase: el usuario abre una página de Nowistay, dice que sí, tú recibes un código corto, lo intercambias por un token de acceso y llamas a la API con ese token. Los tokens caducan y pueden renovarse sin volver a molestar al usuario.

Los tres endpoints OAuth que vas a usar:

  • Registrar tu aplicación (una sola vez): /public/oauth/register (POST)
  • Enviar al usuario a dar su consentimiento: /public/oauth/authorize (GET, redirección del navegador)
  • Intercambiar el código por tokens: /public/oauth/token (POST)

También puedes obtener los metadatos de OAuth en /.well-known/oauth-authorization-server/public-api, que enumera todos los endpoints y las funciones compatibles.

Las aplicaciones nuevas empiezan como no verificadas

Cualquiera puede registrar un cliente OAuth, así que los anfitriones necesitan una forma de distinguir una aplicación recién creada de una integración consolidada. Por eso toda aplicación recién registrada empieza como no verificada:

  • La pantalla de consentimiento muestra a los anfitriones un aviso claro de que Nowistay no ha verificado la aplicación.
  • Una aplicación no verificada puede conectarse como máximo a 3 cuentas de anfitrión.
  • Los clientes registrados que nunca completan una conexión se eliminan automáticamente a los 30 días.

¿Estás creando algo para más de 3 cuentas? Escribe a soporte con el nombre de tu aplicación, tu sitio web y tu caso de uso. Cuando tu aplicación esté verificada, el aviso desaparece y se levanta el límite de cuentas.

Scopes disponibles

La API REST admite 25 scopes, incluidos diez permisos adicionales para ediciones de alojamientos, mensajes, guías, conocimiento, creación de huéspedes, upsells y pagos. Selecciona solo los permisos que necesita tu integración.

  • properties.read: leer los detalles y los ajustes operativos de los alojamientos.
  • properties.write: actualizar los detalles y los ajustes operativos de los alojamientos.
  • calendar.read: leer la disponibilidad, las tarifas por noche y las restricciones de estancia.
  • calendar.write: actualizar la disponibilidad, las tarifas por noche y las restricciones de estancia.
  • bookings.read: leer reservas, detalles financieros y el estado seguro de las solicitudes de pago.
  • bookings.sensitive.read: incluir en las respuestas de reservas los datos de contacto del huésped, las direcciones postales y los códigos de acceso; para las lecturas requiere también bookings.read.
  • bookings.write: crear y actualizar reservas directas. Las solicitudes de pago y los reembolsos requieren además payments.write.
  • booking_parameters.read: leer las definiciones y los valores de los parámetros de reserva personalizados.
  • booking_parameters.write: crear y actualizar las definiciones y los valores de los parámetros.
  • missions.read: leer misiones e informes de finalización.
  • missions.write: crear, actualizar y asignar misiones.
  • activity.read: leer los registros de actividad de los alojamientos.
  • team_members.read: leer miembros del equipo, datos de contacto y asignaciones a alojamientos.
  • webhooks.read: leer las suscripciones a webhooks.
  • webhooks.write: crear, actualizar, pausar y eliminar suscripciones a webhooks.
  • customers.read: leer fichas de huéspedes, datos de contacto e historial de estancias.
  • customers.write: crear fichas de huéspedes.
  • upsells.read: leer el catálogo de upsells y las compras de las reservas.
  • payments.write: solicitar el pago durante la creación de una reserva y ordenar reembolsos totales o parciales por cancelación; requiere además bookings.write.
  • messages.read: leer conversaciones y mensajes con huéspedes.
  • messages.write: enviar mensajes a los huéspedes.
  • guides.read: leer guías de bienvenida y el contenido de las páginas.
  • guides.write: editar guías, crear y actualizar páginas, cambiar la posición de las páginas y subir imágenes.
  • knowledge.read: leer preguntas y respuestas privadas de los alojamientos.
  • knowledge.write: crear y actualizar el conocimiento privado. Copiar requiere además knowledge.read.

Los ocho scopes REST predeterminados siguen siendo properties.read, calendar.read, bookings.read, booking_parameters.read, missions.read, activity.read, team_members.read y webhooks.read. Los valores predeterminados no amplían los tokens existentes.

Operaciones que requieren dos scopes

  • Solicitudes de pago y reembolsos totales o parciales explícitos por cancelación: bookings.write + payments.write. La cancelación sin reembolso usa únicamente bookings.write.
  • Historial de reservas de un huésped: customers.read + bookings.read.
  • Compras de upsells de una reserva: upsells.read + bookings.read. Para leer el catálogo basta upsells.read.
  • Copia de conocimiento: knowledge.read + knowledge.write.
  • Un PATCH de reserva que mezcla campos principales y valores de parámetros personalizados: bookings.write + booking_parameters.write. Un PATCH solo de parámetros mantiene booking_parameters.write y también funciona en reservas de OTA.

Los nuevos endpoints de escritura devuelven resultados de acción mínimos, salvo que el token tenga también el scope de lectura correspondiente. Se mantiene la redacción actual de las reservas. Los endpoints de misiones y conversaciones incluyen el contexto de reserva relacionado según sus propios scopes.

Paso a paso: crea tu propia integración

Estos ejemplos usan IDs y credenciales de ejemplo. Sustitúyelos por tus propios valores. Este flujo es para aplicaciones de partners; las credenciales personales usan client_credentials, como se ha visto antes.

Paso 1. Registra tu cliente OAuth

Llama a POST /public/oauth/register con los permisos que necesitas. Guarda de forma segura el client_id y el client_secret devueltos. El ejemplo solicita lecturas de alojamientos, reservas y misiones.

curl -X POST https://api.nowistay.com/public/oauth/register \
  -H "Content-Type: application/json" \
  -d '{"clientName":"My integration","redirectUris":["https://my-app.example.com/oauth/callback"],"grantTypes":["authorization_code","refresh_token"],"responseTypes":["code"],"tokenEndpointAuthMethod":"client_secret_post","scope":"properties.read bookings.read missions.read"}'

Para un cliente público sin backend, usa "tokenEndpointAuthMethod": "none" y PKCE, sin client secret.

Paso 2. Envía al usuario a la URL de autorización

Genera un verifier de PKCE, su challenge S256 y un state aleatorio. Redirige al usuario al endpoint de autorización. La siguiente URL va en una sola línea; codifica en URL los valores de la consulta.

https://api.nowistay.com/public/oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=https%3A%2F%2Fmy-app.example.com%2Foauth%2Fcallback&scope=properties.read%20bookings.read%20missions.read&state=RANDOM_STATE&code_challenge=BASE64URL_S256_HASH&code_challenge_method=S256

El anfitrión inicia sesión, revisa los permisos y aprueba o deniega el acceso. Si aprueba, el callback contiene un code de un solo uso y tu state. Rechaza cualquier callback cuyo state no coincida.

https://my-app.example.com/oauth/callback?code=AUTHORIZATION_CODE&state=RANDOM_STATE

Paso 3. Intercambia el código por tokens

curl -X POST https://api.nowistay.com/public/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "code=AUTHORIZATION_CODE" \
  --data-urlencode "redirect_uri=https://my-app.example.com/oauth/callback" \
  --data-urlencode "client_id=YOUR_CLIENT_ID" \
  --data-urlencode "client_secret=YOUR_CLIENT_SECRET" \
  --data-urlencode "code_verifier=ORIGINAL_PKCE_VERIFIER"

La respuesta incluye access_token, token_type, expires_in, refresh_token y scope. Guarda los tokens de forma segura. Los tokens de acceso suelen durar una hora; usa la caducidad devuelta.

Paso 4. Llama a la API

Este ejemplo requiere properties.read. Lee los campos data y pagination de la respuesta.

curl "https://api.nowistay.com/public/v1/properties" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Paso 5. Renueva el token cuando caduque

curl -X POST https://api.nowistay.com/public/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=refresh_token" \
  --data-urlencode "refresh_token=YOUR_REFRESH_TOKEN" \
  --data-urlencode "client_id=YOUR_CLIENT_ID" \
  --data-urlencode "client_secret=YOUR_CLIENT_SECRET"

Guarda el nuevo refresh token que devuelve la respuesta de renovación. Si la renovación falla, reinicia la autorización. Renovar mantiene los scopes concedidos anteriormente. Las credenciales personales no tienen refresh token: solicita un nuevo token de acceso con client_credentials.

Ejemplos de endpoints

Leer disponibilidad, tarifas y restricciones de estancia

GET /public/v1/properties/{property_id}/calendar requiere calendar.read. Indica from y to como fechas inclusivas; una solicitud puede cubrir hasta 730 días.

GET /public/v1/properties/101/calendar?from=2026-12-18&to=2026-12-20

Cada día puede devolver availability, rate, minStay, maxStay, stopSell, closedToArrival, closedToDeparture, restricted y restrictionReasons. availability es el inventario vendible restante tras las reservas confirmadas. Los campos de restricción explican por qué una fecha o una acción de check-in o check-out está restringida.

Actualizar el calendario de un alojamiento

PATCH /public/v1/properties/{property_id}/calendar requiere calendar.write. La solicitud es parcial: envía solo los controles que deben cambiar y los campos omitidos permanecen sin cambios.

{
  "from": "2026-12-18",
  "to": "2026-12-31",
  "availability": 1,
  "rate": 145.0,
  "minStay": 2,
  "maxStay": 14,
  "stopSell": false,
  "closedToArrival": false,
  "closedToDeparture": false,
  "daysOfWeek": [0, 1, 2, 3, 4]
}

daysOfWeek es opcional: 0 es lunes y 6 es domingo. Omítelo para actualizar todas las fechas del rango. La respuesta indica daysUpdated y la actualización normalizada que se aplicó. Los cambios se sincronizan a través de Nowistay PMS.

Las operaciones de calendario requieren titularidad, el derecho de asistente y la gestión de calendario de Nowistay activa. Para habilitar los scopes de calendario en una integración existente, sigue Cómo añadir scopes a un token de API existente, más arriba; renovar un token o rotar su clave secreta no basta.

Listar las reservas de un alojamiento en un rango de fechas

Los filtros admitidos incluyen propertyId, propertyIds repetido, status, channel, fromDate y toDate. La paginación JSON estándar usa limit (máximo 100) y offset. Los filtros de fechas de las reservas mantienen su semántica de solapamiento: departure >= fromDate y arrival <= toDate.

curl "https://api.nowistay.com/public/v1/bookings?propertyId=101&fromDate=2026-12-01&toDate=2026-12-31&status=confirmed" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Obtener el detalle de una reserva con los datos de contacto del huésped

curl "https://api.nowistay.com/public/v1/bookings/9001" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Requiere el scope adicional bookings.sensitive.read. Sin él, se omiten email, phone y el código de la cerradura inteligente.

Actualizar una reserva directa

Las ediciones principales de una reserva requieren bookings.write y una reserva directa elegible. Las reservas de OTA siguen siendo de solo lectura en los campos principales. Un PATCH que contenga únicamente customParameters también admite reservas de OTA y usa booking_parameters.write.

curl -X PATCH "https://api.nowistay.com/public/v1/bookings/9001" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "notes": "Guest requested a baby cot.",
  "reservationNumber": "DIRECT-9001",
  "silent": true
}'

Las solicitudes PATCH de reservas existentes usan por defecto silent=true. Establece silent=false para activar las notificaciones habituales de actualización. La creación de reservas usa por defecto silent=false. Los campos omitidos permanecen sin cambios y los valores null admitidos siguen vaciando campos. La sincronización de disponibilidad sigue formando parte de la operación compartida.

Añadir un campo personalizado a tus reservas

Los parámetros de reserva personalizados son útiles cuando quieres reflejar en cada reserva de Nowistay datos de tu propio sistema.

curl -X POST "https://api.nowistay.com/public/v1/booking-parameters" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "key": "external_status",
  "name": "External status"
}'

curl -X PATCH "https://api.nowistay.com/public/v1/bookings/9001" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "customParameters": {
    "external_status": "ready"
  }
}'

Ambos ejemplos usan booking_parameters.write. Las actualizaciones mixtas de campos principales y parámetros necesitan ambos scopes de escritura de reservas; si falla la validación, no se modifica ninguna de las dos partes.

Envía null como valor para vaciar un campo personalizado de una reserva.

Crear una misión de limpieza y asignar un miembro del equipo

Requiere missions.write. Los flujos de asignación y notificación también se ejecutan para las misiones creadas por API.

curl -X POST "https://api.nowistay.com/public/v1/missions" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "propertyId": 101,
  "bookingId": 9001,
  "missionType": "cleaning",
  "title": "Checkout cleaning",
  "scheduledAt": "2026-12-18T11:30:00Z",
  "timezone": "Europe/Paris",
  "assignedTeamMemberId": 301
}'

Leer el feed de actividad de un alojamiento

Requiere activity.read.

curl "https://api.nowistay.com/public/v1/properties/101/activity?limit=20" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Los eventos se agrupan en tres categorías: booking, mission y automation (mensajes a huéspedes, eventos de cerraduras inteligentes).

Endpoints y ejemplos adicionales

Todas las rutas de esta sección son relativas a /public/v1. Sustituye los IDs de ejemplo por recursos que pertenezcan a la cuenta conectada. El esquema OpenAPI enumera todos los campos, enums y formas de respuesta admitidos.

Detalles y ajustes operativos de los alojamientos

Usa GET /properties/{id} con properties.read y PATCH /properties/{id} con properties.write. Las listas de alojamientos admiten search. Las ediciones admitidas incluyen nombre, dirección, capacidad, tipo de alojamiento, enlaces de alquiler, portada, horas de check-in y check-out, plazos de cierre de reservas y ajustes predeterminados de precios y estancia.

curl -X PATCH "https://api.nowistay.com/public/v1/properties/101" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "capacity": 4,
  "defaultMinStay": 2,
  "cutOffDays": 1
}'

Crear una reserva y solicitar el pago

POST /bookings requiere bookings.write, el derecho de asistente y una conexión PMS activa. Incluye las fechas, el correo del huésped y sus datos, junto con los campos de precios y operativos admitidos. requestPayment=true requiere además payments.write y crea la reserva pendiente de pago.

curl -X POST "https://api.nowistay.com/public/v1/bookings" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "propertyId": 101,
  "arrival": "2026-12-18",
  "departure": "2026-12-20",
  "guestFirstName": "Maya",
  "guestLastName": "Martin",
  "guestEmail": "maya@example.com",
  "adults": 2,
  "amount": "290.00",
  "currency": "EUR",
  "requestPayment": true
}'

La creación puede tener éxito aunque falle la generación del enlace de pago. La respuesta sigue incluyendo la reserva persistida o su ID, con paymentRequest.sent=false y un error explícito. Comprueba este resultado antes de reintentar para no crear una reserva duplicada. Solo una acción de solicitud de pago autorizada devuelve una URL de checkout.

Lee GET /bookings/{id}/payment-request con bookings.read para obtener el estado seguro de la reserva y de la solicitud de pago. No devuelve URLs de checkout. Esta versión no añade una API independiente de administración de pagos.

Reembolsos y restricciones de reservas relacionadas con pagos

El PATCH de reservas existentes admite reservationNumber, cancellationRefundMode (none, full o partial) y cancellationRefundAmount. Un reembolso total o parcial explícito por cancelación requiere bookings.write y payments.write. La cancelación sin reembolso requiere solo bookings.write.

curl -X PATCH "https://api.nowistay.com/public/v1/bookings/9001" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "status": "cancelled",
  "cancellationRefundMode": "partial",
  "cancellationRefundAmount": "50.00"
}'

Una reserva cancelada no demuestra que el reembolso se haya completado. Comprueba el estado del pago antes de decirle al huésped que su reembolso se ha realizado. payments.write autoriza las solicitudes de pago y los reembolsos por cancelación, no el reajuste de precio de un checkout existente.

  • Una vez que existe una orden de pago, se rechazan los cambios de fechas, total, tarifa por noche, importe de limpieza, impuestos o moneda. Esto también se aplica a órdenes históricas fallidas o caducadas. Los datos de precio aceptados que no suponen ningún cambio siguen permitidos.
  • Una reserva confirmada y pagada no puede volver a pendiente. Una reserva cancelada y reembolsada no puede reactivarse.
  • Los datos de contacto, las direcciones postales, las notas y las ediciones admitidas de check-in y check-out siguen permitidos.
  • Las ediciones simultáneas y la creación de la solicitud de pago usan una instantánea coherente de la reserva: la solicitud de pago usa una edición anterior que se completó correctamente, o la edición en conflicto se rechaza una vez que existe la orden.

Fichas de huéspedes e historial de estancias

POST /guests usa customers.write; la creación duplicada de un huésped devuelve 409. Las lecturas de clientes a nivel de propietario siguen disponibles. Las listas de huéspedes admiten propertyId, propertyIds repetido y los valores de orderBy admitidos: created_at, updated_at, full_name, bookings_count, total_amount, last_stay. Añade el prefijo - para orden descendente; el valor predeterminado es -updated_at.

curl -X POST "https://api.nowistay.com/public/v1/guests" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "firstName": "Maya",
  "lastName": "Martin",
  "email": "maya@example.com",
  "language": "en"
}'

GET /guests/{id}/bookings requiere customers.read y bookings.read y devuelve el historial de estancias elegibles.

Detalles, informes y filtros de misiones

GET /missions/{id} y GET /missions/{id}/report usan missions.read. Una misión válida sin informe de finalización devuelve 200 con report: null. Las listas de misiones admiten filtros inclusivos de fechas programadas en UTC fromDate/toDate, propertyIds y teamMemberIds repetidos, además de los filtros existentes.

curl "https://api.nowistay.com/public/v1/missions?propertyIds=101&propertyIds=102&teamMemberIds=301&fromDate=2026-12-01&toDate=2026-12-31" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Leer conversaciones y enviar una respuesta del anfitrión

GET /conversations admite los filtros propertyId y bookingId. GET /conversations/{id}/messages lee el historial de mensajes. Ambos requieren messages.read. Envía una respuesta con POST /messages y messages.write, indicando exactamente uno de conversationId o bookingId. channelKind o threadChannelId, opcionales, seleccionan un canal; no indiques ambos selectores.

curl -X POST "https://api.nowistay.com/public/v1/messages" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "bookingId": 9001,
  "message": "Hello Maya, your arrival instructions are ready."
}'

Los mensajes siguen la vía normal de entrega de las respuestas del anfitrión, incluido el enrutamiento de reservas con varias habitaciones. Un mensaje persistido cuya entrega falla devuelve su ID y el estado de entrega: inspecciona el resultado antes de reintentar. Estos endpoints no admiten archivos adjuntos en los mensajes a huéspedes.

Preguntas y respuestas privadas de los alojamientos

Usa GET /properties/{id}/knowledge-questions y GET /properties/{id}/knowledge-questions/{questionId} con knowledge.read. Usa POST sobre la colección y PATCH sobre una pregunta individual con knowledge.write.

curl -X POST "https://api.nowistay.com/public/v1/properties/101/knowledge-questions" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "question": "Where is the recycling area?",
  "answer": "In the courtyard, next to the bicycle shelter."
}'

POST /properties/{id}/knowledge-questions/copy trata {id} como origen y requiere knowledge.read más knowledge.write. Envía targetPropertyIds para fusionar con otros alojamientos. La copia nunca elimina ni sobrescribe las respuestas de destino; todos los destinos se validan antes de empezar a escribir.

curl -X POST "https://api.nowistay.com/public/v1/properties/101/knowledge-questions/copy" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "targetPropertyIds": [102, 103]
}'

Guías de bienvenida localizadas, páginas e imágenes

  • GET/PATCH /properties/{id}/welcome-guide: leer o editar la guía, incluidas las introducciones localizadas.
  • GET/POST /properties/{id}/welcome-guide/pages: listar o crear páginas.
  • GET/PATCH /properties/{id}/welcome-guide/pages/{pageId}: leer o editar una página individual y su posición.
  • POST /properties/{id}/welcome-guide/images: subir una imagen desde una URL pública admitida o desde una entrada en base64.

Las lecturas requieren guides.read; las escrituras requieren guides.write. Al editar páginas estructuradas, conserva los bloques content/contents de EditorJS y sus IDs, el formato anidado y los medios. Los idiomas omitidos permanecen sin cambios. Se rechaza sustituir una página estructurada por texto plano. No se devuelven los tokens de las guías ni los enlaces de acceso de los huéspedes.

curl -X PATCH "https://api.nowistay.com/public/v1/properties/101/welcome-guide" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "intros": {
    "en": "Welcome! Your practical information is below.",
    "fr": "Bienvenue ! Retrouvez les informations pratiques ci-dessous."
  }
}'

Las descargas de imágenes deben usar direcciones accesibles públicamente. Se comprueban las redirecciones y se aplican límites de tamaño. Consulta el esquema de la solicitud de imagen para ver los campos de URL o base64 admitidos y sus límites.

Leer upsells y compras

GET /upsells requiere upsells.read. GET /bookings/{id}/upsell-purchases requiere upsells.read y bookings.read. Estos endpoints son de solo lectura.

curl "https://api.nowistay.com/public/v1/bookings/9001/upsell-purchases" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Exportar tablas de reservas y de misiones

Añade responseFormat=table a las listas de reservas o de misiones para obtener columns, rows y pagination, con limit de hasta 500. El JSON estándar sigue siendo el valor predeterminado y mantiene su máximo de 100. Usa offset para obtener las páginas siguientes. Si se indican propertyId y varios propertyIds, se intersectan.

curl "https://api.nowistay.com/public/v1/bookings?propertyIds=101&propertyIds=102&fromDate=2026-12-01&toDate=2026-12-31&responseFormat=table&limit=500&offset=0" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Las tablas de reservas usan bookings.read; las de misiones usan missions.read. Las asignaciones de limpieza de los informes de reservas requieren además missions.read, y la presencia en conversaciones requiere messages.read. Los filtros de fechas de las reservas conservan la semántica de solapamiento; los filtros de fechas de las misiones usan fechas programadas en UTC.

Compatibilidad con las integraciones existentes

Las rutas REST existentes, los valores JSON estándar por defecto, la paginación, la redacción de las reservas, el vaciado con null admitido y los valores predeterminados de notificación siguen disponibles. Las restricciones de reservas relacionadas con pagos descritas más arriba se aplican ahora por primera vez. REST y MCP comparten las operaciones subyacentes y sus efectos secundarios, mientras que los contratos de las herramientas MCP y el comportamiento de vista previa y confirmación no cambian.

Cómo estructurar los bloques de las páginas de la guía de bienvenida

Una página de Nowistay contiene un documento EditorJS por idioma. El array blocks define el orden de visualización. Cada objeto de ese array es un bloque; coloca los ejemplos siguientes dentro de content.blocks o contents.<locale>.blocks. Los valores de ejemplo son ficticios y deben adaptarse a tu alojamiento.

Estructura común: id es una cadena estable, única dentro del documento; conserva los IDs existentes y asigna uno nuevo a cada bloque nuevo. type es el nombre exacto del bloque, sensible a mayúsculas y minúsculas. data es un objeto cuyos campos dependen del tipo. Conserva también los tunes y los metadatos del documento existentes que devuelve una lectura, en lugar de reconstruirlos sin necesidad.

Solicitud de creación: POST /public/v1/properties/{id}/welcome-guide/pages con guides.write. Usa locale + title + content para un idioma, o titles + contents para varios. icon es info por defecto. position admite start, end o after_page; este último requiere afterPageId. Aquí tienes un cuerpo de solicitud completo en inglés y francés:

{
  "titles": {
    "en": "Arrival",
    "fr": "Arrivée"
  },
  "contents": {
    "en": {
      "blocks": [
        {
          "id": "arrival001",
          "type": "header",
          "data": {
            "text": "Getting here",
            "level": 2
          }
        },
        {
          "id": "arrival002",
          "type": "paragraph",
          "data": {
            "text": "Use the <b>main entrance</b>."
          }
        }
      ]
    },
    "fr": {
      "blocks": [
        {
          "id": "arrival001",
          "type": "header",
          "data": {
            "text": "Venir au logement",
            "level": 2
          }
        },
        {
          "id": "arrival002",
          "type": "paragraph",
          "data": {
            "text": "Utilisez l’<b>entrée principale</b>."
          }
        }
      ]
    }
  },
  "position": "end"
}

Encabezados (header)

data.text es el texto del encabezado. Usa data.level: 2, igual que el editor de guías. El título de la propia página va en title o titles, fuera de los bloques.

{
  "id": "guide00001",
  "type": "header",
  "data": {
    "text": "Arrival / Arrivée",
    "level": 2
  }
}

Párrafos y formato en línea (paragraph)

data.text es una cadena. Usa el marcado en línea <b>, <i> y <a href="https://…"> para negrita, cursiva y enlaces. Escapa las comillas dobles dentro de las cadenas JSON. No envíes una página HTML completa ni Markdown como content.

{
  "id": "guide00002",
  "type": "paragraph",
  "data": {
    "text": "<b>Wi-Fi</b>: GuestNetwork. <i>Welcome / Bienvenue !</i> <a href=\"https://example.com/\">Information</a>"
  }
}

Listas con viñetas y anidadas (list)

Usa style: unordered. items contiene objetos, no cadenas. Cada elemento tiene content (texto con formato), meta (un objeto) e items (un array de elementos hijos, vacío en las hojas). Repite la misma estructura para el anidamiento.

{
  "id": "guide00003",
  "type": "list",
  "data": {
    "style": "unordered",
    "meta": {},
    "items": [
      {
        "content": "Kitchen / Cuisine",
        "meta": {},
        "items": [
          {
            "content": "Coffee machine / Machine à café",
            "meta": {},
            "items": []
          }
        ]
      }
    ]
  }
}

Listas numeradas (list)

Usa style: ordered con meta.counterType: numeric en la raíz. Mantén la misma estructura de elementos y arrays anidados que en una lista con viñetas.

{
  "id": "guide00004",
  "type": "list",
  "data": {
    "style": "ordered",
    "meta": {
      "counterType": "numeric"
    },
    "items": [
      {
        "content": "Close the windows / Fermez les fenêtres",
        "meta": {},
        "items": []
      },
      {
        "content": "Return the keys / Rendez les clés",
        "meta": {},
        "items": []
      }
    ]
  }
}

Listas de verificación (list)

Usa style: checklist. Pon el estado booleano inicial de la casilla en meta.checked de cada elemento. Es contenido de la guía, no un informe de finalización de una misión.

{
  "id": "guide00005",
  "type": "list",
  "data": {
    "style": "checklist",
    "meta": {},
    "items": [
      {
        "content": "Windows closed / Fenêtres fermées",
        "meta": {
          "checked": false
        },
        "items": []
      }
    ]
  }
}

Citas y destacados (quote)

data.text es la cita, caption es su atribución (o una cadena vacía) y alignment es el ajuste de alineación del editor. Usa left; la vista del huésped aplica su propio diseño de cita.

{
  "id": "guide00006",
  "type": "quote",
  "data": {
    "text": "Please respect the neighbours / Merci de respecter le voisinage.",
    "caption": "Your host / Votre hôte",
    "alignment": "left"
  }
}

Separadores (delimiter)

Un separador visual tiene un objeto data vacío. Su posición en blocks determina dónde aparece la separación.

{
  "id": "guide00007",
  "type": "delimiter",
  "data": {}
}

Tablas (table)

data.content es un array de filas; cada fila es un array de cadenas de celdas. Mantén el mismo número de celdas en todas las filas. Con withHeadings: true, la primera fila aporta los encabezados de columna. Usa false cuando todas las filas contienen datos normales.

{
  "id": "guide00008",
  "type": "table",
  "data": {
    "withHeadings": true,
    "content": [
      [
        "Service",
        "Time / Horaire"
      ],
      [
        "Check-in / Arrivée",
        "16:00"
      ],
      [
        "Check-out / Départ",
        "10:00"
      ]
    ]
  }
}

Imágenes (image)

data.file.url es la URL de la imagen subida, no un nombre de archivo local ni una cadena en base64. caption es una cadena; withBorder, withBackground y stretched son booleanos. Primero envía la imagen por POST a /public/v1/properties/{id}/welcome-guide/images y después inserta en content.blocks el editorJsImageBlock devuelto. Conserva los campos devueltos y usa un ID de bloque único. La URL de abajo es ilustrativa: sustitúyela por la URL real devuelta.

{
  "id": "guide00009",
  "type": "image",
  "data": {
    "file": {
      "url": "https://images.example.com/replace-with-uploaded-image.jpg"
    },
    "caption": "Entrance / Entrée",
    "withBorder": false,
    "withBackground": false,
    "stretched": false
  }
}

Vídeos y Shorts de YouTube (video)

Usa service: youtube para un vídeo normal de YouTube, o youtubeShort para un Short. source es su URL original; embed es la URL de inserción de YouTube correspondiente. width y height son números y caption es una cadena. El editor usa 580 × 320 para un vídeo normal y 315 × 560 para un Short. Sustituye VIDEO_ID_11 en ambas URLs por el mismo ID real de 11 caracteres del vídeo; en los Shorts, la ruta de origen es /shorts/VIDEO_ID_11. El HTML arbitrario de iframe no es el formato del bloque.

{
  "id": "guide00010",
  "type": "video",
  "data": {
    "service": "youtube",
    "source": "https://www.youtube.com/watch?v=VIDEO_ID_11",
    "embed": "https://www.youtube.com/embed/VIDEO_ID_11",
    "width": 580,
    "height": 320,
    "caption": "Arrival instructions / Instructions d’arrivée"
  }
}

Bloques embed existentes (embed)

embed usa los mismos campos data que video. Conserva el tipo existente al editar una página; ambos muestran YouTube y YouTube Shorts. No des por hecho que Nowistay admite todos los proveedores de inserción compatibles con EditorJS.

{
  "id": "guide00011",
  "type": "embed",
  "data": {
    "service": "youtubeShort",
    "source": "https://www.youtube.com/shorts/VIDEO_ID_11",
    "embed": "https://www.youtube.com/embed/VIDEO_ID_11",
    "width": 315,
    "height": 560,
    "caption": ""
  }
}

Catálogo de upsells (upsellCatalog)

Usa un objeto data vacío para colocar aquí el catálogo de upsells elegibles. Los productos, los precios y la disponibilidad proceden del catálogo configurado y del contexto del huésped; no se incrustan en este bloque.

{
  "id": "guide00012",
  "type": "upsellCatalog",
  "data": {}
}

Un producto de upsell (upsellProduct)

data.itemId es el ID numérico de un upsell existente. Encuentra el ID correcto con GET /public/v1/upsells usando upsells.read; sustituye el 401 ficticio de abajo. Conserva el ID original salvo que quieras seleccionar otro producto a propósito. Escribir la página usa guides.write; este bloque no crea un producto, no fija un precio ni lo compra.

{
  "id": "guide00013",
  "type": "upsellProduct",
  "data": {
    "itemId": 401
  }
}

Validación REST: la creación y las actualizaciones de páginas rechazan con HTTP 422 los tipos de bloque no admitidos y los datos mal formados. Se comprueba cada idioma indicado antes de escribir nada, incluidos los elementos de listas anidadas, las celdas de tablas y los campos multimedia. El error identifica el documento y el bloque implicados. Los IDs siguen siendo opcionales en los documentos existentes; cuando se indican, deben ser cadenas únicas y no vacías. Se conservan los metadatos y el formato. Esta validación estructural no comprueba si una imagen es accesible ni si un upsell existe.

Controles y formatos del editor que se deben conservar

imageLibrary es el control selector de imágenes del editor, no un bloque de galería de la API. Usa bloques image. linkPreview es un ajuste de bloque (tunes), no un tipo de contenido independiente; conserva los datos existentes. No inventes bloques html, code o button ni proveedores de vídeo no documentados: que se acepte un objeto JSON no garantiza que se muestre en la guía.

Editar una página sin perder contenido

Lee primero GET /public/v1/properties/{id}/welcome-guide/pages/{pageId} con guides.read para obtener content y contents. Trabaja sobre una copia del documento completo del idioma que estás editando, cambia solo los bloques deseados y envía después ese documento por PATCH a la misma URL con guides.write. Si envías un solo bloque, el documento se sustituye por ese único bloque; los bloques no se fusionan por id. Los idiomas omitidos permanecen sin cambios. Para mover una página existente, usa order, no position.

Los campos text y texts no sustituyen al documento estructurado. Sigue las indicaciones simpleTextUpdateAllowed, recommendedUpdateMode y recommendedUpdateField de la respuesta de lectura; una página estructurada rechaza la sustitución por texto plano. Los ejemplos individuales de esta sección describen bloques, no solicitudes PATCH completas para una página existente. Vuelve a leer la página tras editarla y comprueba idiomas, IDs, enlaces y medios. Con solo guides.write, la respuesta de escritura es mínima; añade guides.read para recibir los detalles.

Webhooks (eventos en tiempo real)

Si no quieres consultar la API de forma repetida para detectar cambios, puedes suscribirte a webhooks. Nowistay llamará entonces a una URL que indiques cada vez que se cree o actualice una reserva en el alojamiento, y enviará los mismos datos de reserva que devuelve GET /public/v1/bookings/{id}.

Hoy se admiten dos eventos:

  • booking.created: se ha registrado una nueva reserva en el alojamiento.
  • booking.updated: una reserva existente ha cambiado (fechas, estado, datos del huésped, código de la cerradura inteligente, etc.).

Cada webhook está vinculado a un único alojamiento y puedes registrar hasta tres por alojamiento. Las entregas van firmadas (HMAC-SHA256) para que puedas verificar que realmente proceden de Nowistay, se reintentan con espera exponencial si fallan y el endpoint se desactiva automáticamente si sigue fallando durante más de 72 horas. Los payloads de los webhooks no contienen campos sensibles del huésped (correo, teléfono, dirección, código de la cerradura inteligente). Cuando los necesites, llama a la API con el scope bookings.sensitive.read.

Los detalles completos de configuración y de verificación de la firma están en el artículo dedicado: API pública: recibe webhooks de reservas en tiempo real.

Límites de frecuencia

  • Endpoints de lectura: 120 solicitudes por minuto por cliente y por ruta.
  • Endpoints de escritura (POST, PATCH): 30 solicitudes por minuto por cliente y por ruta.
  • Endpoints OAuth: 20 solicitudes por minuto por IP o cliente.

Cuando alcances el límite, la API devuelve 429 Rate limit exceeded. Espera y vuelve a intentarlo tras unos segundos.

Formato de los errores

Todos los errores siguen la misma forma, con un code estable, un mensaje legible y un requestId que puedes incluir en tus solicitudes a soporte.

{
  "error": {
    "code": "validation_error",
    "message": "The request contains an invalid value.",
    "requestId": "example-request-id"
  }
}

400: solicitud incorrecta (cuerpo mal formado, campo ausente).

  • 401: token bearer ausente o no válido.
  • 403: el token es válido pero le falta un scope necesario, o no tienes el derecho de AI Channel Manager.
  • 404: recurso no encontrado, o no tienes acceso a él.
  • 409: conflicto (por ejemplo, la clave del parámetro ya existe).
  • 422: la validación ha fallado.
  • 429: se ha alcanzado el límite de frecuencia.

Buenas prácticas

  • Pide el conjunto de scopes más pequeño posible. Añade nuevos scopes más adelante, a medida que crezca tu integración.
  • Guarda los tokens en el servidor. No envíes nunca un token de larga duración a un cliente público ni lo subas a un repositorio.
  • Gestiona el 401 renovando el token. Si la renovación también falla, devuelve al usuario al flujo de autorización.
  • Usa el indicador silent en las escrituras. Ponlo en true cuando sincronices datos en bloque y no quieras activar notificaciones a los huéspedes.
  • Guarda el requestId en tus registros. Acelera mucho la resolución de las solicitudes a soporte.

Siguientes pasos

  • Abre la documentación en vivo y prueba todos los endpoints: api.nowistay.com/public/docs.
  • Lee el esquema completo y descarga un archivo OpenAPI en /public/openapi.json.
  • ¿Necesitas un scope o un endpoint que aún no figura en la lista? Escribe a soporte y cuéntanos tu caso de uso.

¿Listo para poner tu alojamiento en piloto automático?

Únete a más de 1.000 gestores de alojamientos que ahorran horas cada semana con la comunicación con huéspedes impulsada por IA.