Check-ins
El recurso central de la API: un check-in por reserva, identificado siempre por tu reservationId.
El objeto check-in
{
"reservationId": "R-2026-0412",
"listingId": "38291",
"confirmationCode": "HMXK4L",
"source": "direct",
"checkInDate": "2026-08-14",
"checkOutDate": "2026-08-18",
"arrivalTime": "16:00",
"departureTime": "11:00",
"numGuestsExpected": 4,
"numGuestsConfirmed": null,
"leadGuest": { "name": "Jan de Vries", "email": "jan@example.com", "phone": "+31600000000" },
"maxGuests": 6,
"status": "in_progress",
"locale": "nl",
"contractTemplateId": "9f2a…",
"houseRulesId": "b7c1…",
"tipoPago": "TRANS",
"url": "https://checkinsas…/c/aGb3xKf9…",
"contract": { "signed": false, "signedAt": null },
"createdAt": "2026-07-19T10:12:00.000Z",
"updatedAt": "2026-07-19T11:40:00.000Z"
}Los campos opcionales —maxGuests, returnUrl, contractTemplateId, houseRulesId, tipoPago, skipContract, manualSubmit y errorReason— solo aparecen cuando la reserva los lleva puestos. Su ausencia significa «esta reserva no lo lleva», no que se desconozca.
anonymizedAt solo aparece en una reserva cuyos datos personales se suprimieron al dar de baja a su titular: sigue en los listados, pero ya no tiene huéspedes, huésped principal ni ficheros. Ver Exportación y fin del servicio.
| Campo | Tipo | Descripción |
|---|---|---|
pending | estado | Creado; ningún huésped registrado aún. |
in_progress | estado | Al menos un huésped registrado. |
completed | estado | Todos los huéspedes registrados. |
signed | estado | Contrato firmado por todo el grupo. |
submitted | estado | Parte de viajeros enviado a SES (lote asignado). |
confirmed | estado | SES confirmó todas las comunicaciones. |
error | estado | Requiere intervención — ver errorReason. |
cancelled | estado | Reserva cancelada. |
Crear un check-in
/checkins| Campo | Tipo | Descripción |
|---|---|---|
reservationIdrequerido | string | Id de la reserva en tu sistema. Es el identificador público del check-in. |
listingIdrequerido | string | Id del listing — debe existir una propiedad con ese listingId. Si no existe ninguna, la reserva se crea igualmente en estado error y la respuesta trae warning con el motivo: nada se pierde en silencio. |
confirmationCoderequerido | string | Código de confirmación que ve el huésped. |
checkInDaterequerido | AAAA-MM-DD | Fecha de llegada. |
checkOutDaterequerido | AAAA-MM-DD | Fecha de salida. |
numGuestsrequerido | integer 1-50 | Nº de huéspedes esperado (el grupo puede ajustarlo). |
arrivalTime | hh:mm | Hora de entrada (por defecto 16:00). |
departureTime | hh:mm | Hora de salida (por defecto 11:00). |
source | string | Canal de la reserva: airbnb, booking, direct… Texto libre e informativo: se ve en el panel y vuelve en las respuestas, pero no va a SES. Sin él, la reserva queda como unknown. |
leadGuest | objeto | { name, email, phone } del huésped principal, para prellenar su registro. |
locale | string | Idioma del formulario, del contrato y de los correos al huésped: es, en, de, fr, nl, it o pt. Acepta variantes regionales (nl-NL → nl). Si NO lo mandas se usa el idioma por defecto del alojamiento, y si tampoco lo tiene, español. Lo que mandes aquí manda sobre el del alojamiento, porque va de un huésped concreto. Un idioma que no soportemos cae a español SIN dar error — mira el locale de la respuesta para saber cuál se aplicó. |
returnUrl | string | URL https a la que vuelve el huésped cuando termina el check-in, o cuando decide salir a medias. Vuelve PULSANDO un botón, nunca solo: así puede quedarse a revisar lo que ha enviado, y volver a abrir su enlace no le saca de él. Sirve para meter el registro dentro de un flujo propio. Solo https y sin credenciales embebidas; una URL inválida devuelve 400. Comprobamos la FORMA, no que funcione: mándala completa, con el token o el identificador final si los lleva, porque una URL bien formada pero incompleta se acepta y falla al final, cuando el huésped ya ha terminado. |
maxGuests | integer 1-50 | Capacidad real del alojamiento. El huésped puede ajustar el número del grupo desde su enlace, y este es el techo que no puede superar. Si el número enviado fue demasiado alto, siempre puede BAJARLO aunque supere el tope. Sin él se aplica solo el límite global. |
contractTemplateId | string | Plantilla de contrato de esta reserva. Tiene prioridad sobre la asignada a la propiedad; sin ninguna se usa la predeterminada del tenant. |
houseRulesId | string | Juego de normas de la casa de esta reserva. Es el otro eje del contrato: se combina con contractTemplateId, así que no hace falta una plantilla por cada cruce de articulado y normas. Tiene prioridad sobre el juego asignado a la propiedad. |
tipoPago | string | Cómo se cobró ESTA estancia: código del catálogo TIPO_PAGO de SES (PLATF, TRANS, EFECT…). Pisa al de la propiedad y al de las credenciales SES. Envíalo solo cuando esa reserva se cobró de otra forma que la habitual del alojamiento. |
skipContract | boolean | true = esta reserva NO lleva articulado: el documento sale con las normas de la casa y las condiciones legales, nada más. Para estancias que no necesitan contrato. Las condiciones legales NO se pueden quitar: llevan el aviso de comunicación al Ministerio y la información de protección de datos. |
manualSubmit | boolean | true = el parte de esta reserva NO sale en el envío nocturno a SES.Hospedajes, ni se vuelve a comunicar solo si cambian sus fechas. Se comunica cuando lo pidas: desde el panel con «Enviar manualmente», o llamando a POST /checkins/{reservationId}/ses. Para estancias que quieras revisar antes. El plazo legal del parte sigue corriendo igual. |
curl -X POST https://api.kinapi.es/api/v1/checkins \
-H "ocp-apim-subscription-key: TU_API_KEY" \
-H "content-type: application/json" \
-d '{
"reservationId": "R-2026-0412",
"listingId": "38291",
"confirmationCode": "HMXK4L",
"checkInDate": "2026-08-14",
"checkOutDate": "2026-08-18",
"numGuests": 4
}'{
"url": "https://checkinsas…/c/aGb3xKf9…",
"status": "pending",
"created": true,
"locale": "es",
"contractTemplateId": "9f2a…",
"houseRulesId": "b7c1…",
"tipoPago": "TRANS"
}contractTemplateId, houseRulesId y tipoPago solo aparecen cuando la reserva los lleva guardados: es la forma de confirmar que se guardaron. Los tres —y también skipContract y manualSubmit— vuelven a salir en el objeto check-in (GET y PATCH), así que lo que cambies después puedes releerlo sin guardarte la respuesta de la creación.
Idempotente: si el check-in ya existe, responde 200 con el existente (campo created: false) y la misma URL. Reintenta con libertad. El segundo envío no modifica la reserva que ya había: para cambiarle las fechas, la plantilla, las normas o el método de pago, PATCH.
Del returnUrl comprobamos la forma, no que funcione. Que sea https y no lleve credenciales dentro; ahí se acaba. No la visitamos ni verificamos que exista, porque en el momento de crear la reserva puede aún no existir y porque abrir conexiones a direcciones que nos dictan desde fuera es un riesgo que no vale lo que aporta.
La consecuencia práctica: una URL bien formada pero incompleta se acepta sin protestar y falla al final, cuando el huésped ya ha terminado, pulsa para volver y aterriza en un 404. Pasa con las direcciones que llevan un token o un identificador en el último tramo y se construyen a trozos — …/booking/R-2026-0412 en vez de …/booking/R-2026-0412/f9K2xQ—. Mándala entera, con el token incluido, y pruébala pegándola en el navegador antes de integrarla.
Contrato y normas: dos ejes que se combinan
El articulado legal es el mismo para casi todo el parque; lo que cambia de una casa a otra son las normas —piscina y barbacoa aquí, ascensor y horario de silencio allí—. Por eso las normas de la casa son una entidad propia y no una sección dentro de la plantilla de contrato: cada reserva elige la combinación que le toca con contractTemplateId (el articulado) y houseRulesId (las normas).
Así, M plantillas y N juegos de normas dan M × N contratos sin duplicar un solo párrafo. Con las normas metidas dentro de la plantilla, cambiar una sola regla de un apartamento obligaba a duplicar la plantilla entera —articulado incluido— y a mantener después el mismo texto legal en varias copias que acaban separándose. Los juegos se crean y se listan en /v1/house-rules, y a una propiedad se le asigna el suyo desde el panel; ver Contratos y Propiedades.
| Campo | Tipo | Descripción |
|---|---|---|
houseRulesId de la reserva | 1.º | El que envías en POST o PATCH /checkins. Es el único que puede decidirse llamada a llamada, ya con la reserva delante. |
houseRulesId de la propiedad | 2.º | El juego asignado al alojamiento, desde su ficha del panel (Datos del alojamiento) o por API: /v1/properties lo acepta en el alta y en el PATCH, y lo devuelve en el objeto propiedad. Cubre a todas sus reservas que no traigan el suyo. |
juego predeterminado del tenant | 3.º | El que tiene isDefault en /v1/house-rules. Cubre a las propiedades que no tienen ninguno asignado. |
normas heredadas | 4.º | Sin ningún juego que aplicar, las normas propias de la propiedad (campo houseRules, en desuso) o, si están vacías, las escritas dentro de la plantilla de contrato. A falta de todo lo anterior, las normas de fábrica de Kinapi. |
Un houseRulesId que apunta a un juego borrado, o a uno que se quedó sin texto en ningún idioma, no corta la cadena: se sigue por el candidato siguiente. Un borrado hecho meses después no puede dejar sin normas a las reservas que ya lo tenían asignado.
Método de pago de la reserva
tipoPago es el código del catálogo TIPO_PAGO del Ministerio que viaja en el parte de viajeros. Es un dato de la reserva y no del alojamiento: la misma casa cobra por la plataforma una semana y por transferencia la siguiente, y lo que SES pregunta es cómo se pagó esa estancia.
| Campo | Tipo | Descripción |
|---|---|---|
tipoPago de la reserva | 1.º | El que envías en POST o PATCH /checkins. Lo normal es no enviarlo y dejar que mande el del alojamiento. |
ses.tipoPago de la propiedad | 2.º | Cómo se cobra habitualmente ese alojamiento. |
tipoPago de las credenciales SES | 3.º | El del arrendador. Es obligatorio al crear las credenciales, así que la cascada siempre termina en un valor y ningún parte sale sin método de pago. |
Códigos válidos: EFECT, TARJT, PLATF, TRANS, MOVIL, DESTI, TREG y OTRO — la tabla con su significado está en Credenciales SES. El valor se lee al construir el parte, no al crear la reserva: un PATCH antes del envío cambia lo que se comunica. Después, cambiar solo el método de pago no rehace lo comunicado; cambiar las fechas sí, y la comunicación nueva sale con el valor vigente.
Listar check-ins
/checkins| Campo | Tipo | Descripción |
|---|---|---|
from | AAAA-MM-DD | Llegada desde (inclusive). |
to | AAAA-MM-DD | Llegada hasta (inclusive). |
status | string | Filtra por estado (ver tabla de estados). |
listingId | string | Solo check-ins de ese listing. |
ownerId | string | Solo las reservas de las propiedades de este propietario (id de /owners), también las de sus propiedades desactivadas y las que llegaron con un id de canal. Un propietario que no existe devuelve 400 validation. |
limit | integer 1-200 | Tamaño de página (por defecto 50). |
offset | integer | Desplazamiento de paginación. |
curl "https://api.kinapi.es/api/v1/checkins?from=2026-08-01&to=2026-08-31&status=confirmed" \ -H "ocp-apim-subscription-key: TU_API_KEY"
{
"results": [ { …check-in… }, … ],
"hasMore": false
}Consultar un check-in
/checkins/{reservationId}Devuelve el check-in con el array guests incluido (mismo formato que el recurso Huéspedes).
curl https://api.kinapi.es/api/v1/checkins/R-2026-0412 \ -H "ocp-apim-subscription-key: TU_API_KEY"
Actualizar un check-in
/checkins/{reservationId}| Campo | Tipo | Descripción |
|---|---|---|
checkInDate | AAAA-MM-DD | Nueva fecha de llegada. Si el parte ya se comunicó, Kinapi lo anula y lo vuelve a comunicar con la fecha nueva: ver más abajo. |
checkOutDate | AAAA-MM-DD | Nueva fecha de salida. Si el parte ya se comunicó, Kinapi lo anula y lo vuelve a comunicar con la fecha nueva: ver más abajo. |
arrivalTime | hh:mm | Nueva hora de entrada. |
departureTime | hh:mm | Nueva hora de salida. |
numGuests | integer 1-50 | Nuevo nº de huéspedes esperado. |
leadGuest | objeto | Corrige los datos del huésped titular: { name, email, phone }. Se FUSIONA con los que ya tiene la reserva, así que manda solo el campo que cambió y el resto se queda como estaba; una cadena vacía borra ese campo. El email importa más de lo que parece: es la dirección a la que van la invitación al huésped, el recordatorio y el contrato firmado, así que uno desactualizado deja al huésped sin su enlace sin que nada dé error. |
locale | string | Nuevo idioma del formulario, del contrato y de los correos. Vacío devuelve la reserva al idioma por defecto del alojamiento. |
returnUrl | string | URL https a la que vuelve el huésped cuando termina el check-in, o cuando decide salir a medias. Vuelve PULSANDO un botón, nunca solo: así puede quedarse a revisar lo que ha enviado, y volver a abrir su enlace no le saca de él. Sirve para meter el registro dentro de un flujo propio. Solo https y sin credenciales embebidas; una URL inválida devuelve 400. Comprobamos la FORMA, no que funcione: mándala completa, con el token o el identificador final si los lleva, porque una URL bien formada pero incompleta se acepta y falla al final, cuando el huésped ya ha terminado. Cadena vacía para quitarla. |
maxGuests | integer 1-50 | Capacidad real del alojamiento. El huésped puede ajustar el número del grupo desde su enlace, y este es el techo que no puede superar. Si el número enviado fue demasiado alto, siempre puede BAJARLO aunque supere el tope. Sin él se aplica solo el límite global. Cadena vacía (o 0) para quitarla. |
contractTemplateId | string | Cambia la plantilla de contrato de esta reserva. Cadena vacía para volver a la de la propiedad. Si el contrato ya estaba firmado, conserva la plantilla con la que se firmó y la respuesta incluye un warning: el cambio solo afecta a firmas posteriores. |
houseRulesId | string | Cambia el juego de normas de la casa de esta reserva. Cadena vacía para desasignarlo y devolver el mando a la propiedad. Sobre un contrato ya firmado no cambia nada: el PDF archivado conserva las normas con las que se firmó. |
tipoPago | string | Cambia el método de pago de esta reserva. Cadena vacía para desasignarlo y volver al de la propiedad. Útil cuando el cobro cambia después de crear la reserva: mientras el parte no se haya enviado, es el valor que se comunicará. |
skipContract | boolean | true = esta reserva NO lleva articulado: el documento sale con las normas de la casa y las condiciones legales, nada más. Para estancias que no necesitan contrato. Las condiciones legales NO se pueden quitar: llevan el aviso de comunicación al Ministerio y la información de protección de datos. |
manualSubmit | boolean | true = el parte de esta reserva NO sale en el envío nocturno a SES.Hospedajes, ni se vuelve a comunicar solo si cambian sus fechas. Se comunica cuando lo pidas: desde el panel con «Enviar manualmente», o llamando a POST /checkins/{reservationId}/ses. Para estancias que quieras revisar antes. El plazo legal del parte sigue corriendo igual. |
source | string | Corrige el canal de la reserva (airbnb, booking, direct…). Informativo: no cambia nada ante SES. La cadena vacía lo devuelve a unknown. |
Elegir el contrato según la duración de la estancia. Si tu normativa autonómica distingue regímenes por duración —en la Comunidad Valenciana, más de 10 días deja de ser vivienda de uso turístico y pasa a arrendamiento de temporada bajo la LAU—, la decisión la toma tu sistema, que es quien conoce tu caso y su interpretación legal:
contractTemplateId = noches > 10 ? PLANTILLA_TEMPORADA : PLANTILLA_TURISTICO
Y si la reserva se alarga y cruza el umbral, el mismo PATCH que cambia las fechas puede cambiar el contrato. En el panel, cada reserva muestra sus noches, su idioma y qué plantilla tiene asignada, para que puedas comprobar que tu lógica hizo lo que esperabas.
curl -X PATCH https://api.kinapi.es/api/v1/checkins/R-2026-0412 \
-H "ocp-apim-subscription-key: TU_API_KEY" \
-H "content-type: application/json" \
-d '{ "checkOutDate": "2026-08-19" }'Cambiar las fechas de una reserva ya comunicada la vuelve a comunicar. SES Hospedajes no tiene operación de modificación —solo alta, consulta y anulación— y el parte lleva las fechas dentro. Si mueves la entrada o la salida de una reserva cuyo parte ya salió, Kinapi anula esa comunicación ante el Ministerio y la vuelve a dar de alta con los huéspedes que ya tenía: en el acto si la nueva entrada es hoy o ya pasó, y si es futura, en el envío nocturno de esa noche, como cualquier otra reserva.
Primero se anula y solo después se guarda. Si la anulación no sale, la reserva se queda como estaba, aquí y en el Ministerio, y la respuesta lo dice: nunca quedan fechas nuevas en Kinapi y viejas en SES. Solo las fechas disparan esto; las horas, el número de huéspedes o el método de pago se guardan sin tocar lo comunicado.
| Campo | Tipo | Descripción |
|---|---|---|
refiling | 200 | Anulada y guardada. anulados: cuántas comunicaciones se anularon. reenviado: true si se volvió a comunicar en el acto, con el lote nuevo en lote; false si sale en el envío nocturno de la nueva entrada. Si el reenvío inmediato falló, error lo explica y el envío nocturno lo reintenta. retenida: true si la reserva es de envío manual (manualSubmit): se anuló, pero el parte nuevo no sale hasta que llames a POST /checkins/{reservationId}/ses. |
refiling_blocked · en_vuelo | 409 | El parte está enviado y SES aún no lo ha confirmado, así que todavía no se puede anular. No se ha guardado nada. Reintenta en una hora: la respuesta de SES se consulta cada hora. |
refiling_blocked · anulacion_fallida | 503 | SES no aceptó la anulación o no respondió. No se ha guardado nada: la reserva sigue con sus fechas de antes. Reintenta más tarde. |
{
…check-in con las fechas nuevas…,
"refiling": { "anulados": 2, "reenviado": true, "lote": "…" }
}{
"error": "refiling_blocked",
"code": "en_vuelo",
"details": "El parte está enviado y aún sin confirmar por SES; no se puede anular todavía. Reintenta en una hora."
}curl -X PATCH https://api.kinapi.es/api/v1/checkins/R-2026-0412 \
-H "ocp-apim-subscription-key: TU_API_KEY" \
-H "content-type: application/json" \
-d '{ "tipoPago": "TRANS", "houseRulesId": "" }'Vigencia del enlace del huésped
El enlace del huésped —el campo url de la respuesta— es una credencial portadora: quien lo tenga puede registrar huéspedes en esa reserva. Caduca 14 días después de la fecha de salida; a partir de ahí devuelve 410 y deja de servir datos y el PDF del contrato. Si necesitas reabrirlo, genera un check-in nuevo.
Dentro del enlace, los campos que identifican a una persona —número de documento, número de soporte y fecha de nacimiento— solo se muestran a quien la registró, y durante unas horas. Después se pueden corregir, pero no leer: el resto del grupo ve el documento enmascarado. Es lo que evita que un enlace reenviado sirva para consultar los datos de un tercero.
Cancelar o borrar un check-in
/checkins/{reservationId}En los dos casos la reserva sale del flujo: el enlace del huésped deja de aceptar registros y no se comunicará ningún parte. Lo que cambia es si queda rastro, y eso no lo eliges tú: una reserva que todavía no ha dejado nada que haya que conservar se borra, y una que sí se cancela. La respuesta lo dice en action, para que no tengas que adivinarlo: lo borrado ya no aparece en GET /checkins y lo cancelado sí.
| Campo | Tipo | Descripción |
|---|---|---|
deleted | action | Nadie había firmado, no se había comunicado nada al Ministerio y la reserva no había generado cargo: se borra con sus huéspedes. La respuesta trae reservationId y deletedGuests (cuántos huéspedes se borraron con ella). |
cancelled | action | Había algo que conservar: la reserva pasa a estado cancelled y no se borra ningún dato. La respuesta trae el objeto check-in completo, reason con el motivo y ses con el resultado de la anulación. |
| Campo | Tipo | Descripción |
|---|---|---|
firmado | reason | Hay un contrato firmado, un huésped que ya firmó o prueba de firma archivada. Se conserva sellado tres años: un documento que puede desaparecer a petición de quien lo emitió no prueba nada. |
comunicado | reason | Sus partes ya salieron hacia el Ministerio. Borrar aquí dejaría allí una comunicación sin nada que la explique ni forma de anularla. |
facturado | reason | La reserva generó consumo cobrable, y eso es histórico contable que sostiene una factura. |
Si el parte de esa reserva ya se había comunicado —el caso comunicado, que siempre acaba en cancelación—, Kinapi lo anula ante SES Hospedajes en la misma llamada: el art. 6.3 del RD 933/2021 obliga a comunicar también la anulación, y sin eso en el Ministerio seguiría constando una estancia que no va a ocurrir. La respuesta trae ses.anulados con cuántas comunicaciones se anularon, y ses.anulacionError si el Ministerio no aceptó la anulación. La reserva queda cancelada igualmente y Kinapi reintenta la anulación cada hora, hasta 72 intentos (tres días); si no lo consigue, emite el webhook ses.cancel_failed. También puedes repetir el DELETE para reintentarla en el acto. Cuando el Ministerio acepta la anulación se emite el webhook ses.cancelled con reason: "cancelled". En el caso deleted no hay nada que anular por definición.
Cancelada con el parte en vuelo. Si el parte estaba enviado y SES aún no lo había confirmado, todavía no se puede anular: para el Ministerio aún no existe. La reserva se cancela igual, y en cuanto llega la confirmación Kinapi anula esas comunicaciones; la reserva sigue en cancelled.
Aceptar una anulación no es procesarla. SES procesa las anulaciones de forma asíncrona, como las altas: Kinapi consulta cada hora el lote de anulación y, si el Ministerio la rechazó, emite ses.cancel_failed. La comunicación sigue constando y hay que revisarla en SES.HOSPEDAJES. El estado de cada anulación está en el campo annulment de GET /checkins/{reservationId}/ses.
curl -X DELETE https://api.kinapi.es/api/v1/checkins/R-2026-0412 \ -H "ocp-apim-subscription-key: TU_API_KEY"
{
"action": "deleted",
"reservationId": "R-2026-0412",
"deletedGuests": 2
}{
…check-in con "status": "cancelled"…,
"action": "cancelled",
"reason": "comunicado",
"ses": { "anulados": 3 }
}