Check-ins

El recurso central de la API: un check-in por reserva, identificado siempre por tu reservationId.

El objeto check-in

Objeto
{
  "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.

Estados (status)
CampoTipoDescripción
pendingestadoCreado; ningún huésped registrado aún.
in_progressestadoAl menos un huésped registrado.
completedestadoTodos los huéspedes registrados.
signedestadoContrato firmado por todo el grupo.
submittedestadoParte de viajeros enviado a SES (lote asignado).
confirmedestadoSES confirmó todas las comunicaciones.
errorestadoRequiere intervención — ver errorReason.
cancelledestadoReserva cancelada.

Crear un check-in

POST/checkins
Body
CampoTipoDescripción
reservationIdrequeridostringId de la reserva en tu sistema. Es el identificador público del check-in.
listingIdrequeridostringId 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.
confirmationCoderequeridostringCódigo de confirmación que ve el huésped.
checkInDaterequeridoAAAA-MM-DDFecha de llegada.
checkOutDaterequeridoAAAA-MM-DDFecha de salida.
numGuestsrequeridointeger 1-50Nº de huéspedes esperado (el grupo puede ajustarlo).
arrivalTimehh:mmHora de entrada (por defecto 16:00).
departureTimehh:mmHora de salida (por defecto 11:00).
sourcestringCanal 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.
leadGuestobjeto{ name, email, phone } del huésped principal, para prellenar su registro.
localestringIdioma 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ó.
returnUrlstringURL 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.
maxGuestsinteger 1-50Capacidad 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.
contractTemplateIdstringPlantilla de contrato de esta reserva. Tiene prioridad sobre la asignada a la propiedad; sin ninguna se usa la predeterminada del tenant.
houseRulesIdstringJuego 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.
tipoPagostringCó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.
skipContractbooleantrue = 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.
manualSubmitbooleantrue = 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
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
  }'
201 Created
{
  "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.

Qué normas salen en el contrato (de más concreto a más general)
CampoTipoDescripción
houseRulesId de la reserva1.º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 propiedad2.º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 tenant3.ºEl que tiene isDefault en /v1/house-rules. Cubre a las propiedades que no tienen ninguno asignado.
normas heredadas4.º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.

Cascada de tipoPago (de más concreto a más general)
CampoTipoDescripción
tipoPago de la reserva1.º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 propiedad2.ºCómo se cobra habitualmente ese alojamiento.
tipoPago de las credenciales SES3.º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

GET/checkins
Query
CampoTipoDescripción
fromAAAA-MM-DDLlegada desde (inclusive).
toAAAA-MM-DDLlegada hasta (inclusive).
statusstringFiltra por estado (ver tabla de estados).
listingIdstringSolo check-ins de ese listing.
ownerIdstringSolo 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.
limitinteger 1-200Tamaño de página (por defecto 50).
offsetintegerDesplazamiento de paginación.
curl
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"
200 OK
{
  "results": [ { …check-in… }, … ],
  "hasMore": false
}

Consultar un check-in

GET/checkins/{reservationId}

Devuelve el check-in con el array guests incluido (mismo formato que el recurso Huéspedes).

curl
curl https://api.kinapi.es/api/v1/checkins/R-2026-0412 \
  -H "ocp-apim-subscription-key: TU_API_KEY"

Actualizar un check-in

PATCH/checkins/{reservationId}
Body (todos opcionales)
CampoTipoDescripción
checkInDateAAAA-MM-DDNueva 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.
checkOutDateAAAA-MM-DDNueva 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.
arrivalTimehh:mmNueva hora de entrada.
departureTimehh:mmNueva hora de salida.
numGuestsinteger 1-50Nuevo nº de huéspedes esperado.
leadGuestobjetoCorrige 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.
localestringNuevo idioma del formulario, del contrato y de los correos. Vacío devuelve la reserva al idioma por defecto del alojamiento.
returnUrlstringURL 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.
maxGuestsinteger 1-50Capacidad 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.
contractTemplateIdstringCambia 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.
houseRulesIdstringCambia 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ó.
tipoPagostringCambia 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á.
skipContractbooleantrue = 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.
manualSubmitbooleantrue = 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.
sourcestringCorrige 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
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.

Qué responde el PATCH al cambiar las fechas de una reserva comunicada
CampoTipoDescripción
refiling200Anulada 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_vuelo409El 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_fallida503SES 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.
200 OK — nueva entrada ya pasada: anulada y vuelta a comunicar
{
  …check-in con las fechas nuevas…,
  "refiling": { "anulados": 2, "reenviado": true, "lote": "…" }
}
409 Conflict — parte aún sin confirmar por SES: no se ha guardado nada
{
  "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 — esta reserva se cobró por transferencia y vuelve a las normas de la propiedad
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

DELETE/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í.

Los dos desenlaces (campo action)
CampoTipoDescripción
deletedactionNadie 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).
cancelledactionHabí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.
Por qué no se pudo borrar (campo reason)
CampoTipoDescripción
firmadoreasonHay 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.
comunicadoreasonSus partes ya salieron hacia el Ministerio. Borrar aquí dejaría allí una comunicación sin nada que la explique ni forma de anularla.
facturadoreasonLa 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
curl -X DELETE https://api.kinapi.es/api/v1/checkins/R-2026-0412 \
  -H "ocp-apim-subscription-key: TU_API_KEY"
200 OK — sin rastro que conservar: borrada
{
  "action": "deleted",
  "reservationId": "R-2026-0412",
  "deletedGuests": 2
}
200 OK — con parte ya comunicado: cancelada y anulada
{
  …check-in con "status": "cancelled"…,
  "action": "cancelled",
  "reason": "comunicado",
  "ses": { "anulados": 3 }
}