Contratos
Define qué dice el contrato combinando una plantilla con un juego de normas de la casa, y descarga el PDF firmado por el grupo con su rastro de evidencia.
El ciclo tiene dos mitades. Primero defines qué dice el contrato — el articulado, en una plantilla, y las normas del alojamiento, en un juego de normas de la casa — y eliges qué combinación usa cada propiedad o cada reserva. Después, cuando el grupo firma, Kinapi genera el contrato firmado de esa reserva y lo puedes descargar. Todo, por API.
El contrato son dos piezas
El documento que firma el huésped se arma con dos cosas que cambian por motivos distintos. El articulado legal (objeto del contrato, responsabilidad, ley aplicable) es prácticamente el mismo en todo tu parque; las normas de la casa cambian de un alojamiento a otro — el chalet con piscina no tiene las mismas que el ático del centro.
Por eso son dos recursos distintos: las plantillas de /contract-templates guardan el articulado, y los juegos de /house-rules guardan las normas. Se combinan al generar cada contrato, así que con M plantillas y N juegos tienes las M × N combinaciones sin duplicar nada. Antes las normas vivían dentro de la plantilla: cambiar una sola regla de un apartamento obligaba a duplicar la plantilla entera —articulado incluido— y a mantener el mismo texto legal en varias copias que se separan solas.
Lo que ya tenías sigue funcionando. Las normas escritas dentro de una plantilla (houseRules) o dentro de una propiedad se siguen imprimiendo cuando la reserva no resuelve a ningún juego. No hay nada que migrar para que tus contratos actuales salgan exactamente igual que ayer.
Dónde está esto en el panel. El articulado y las normas viven en la misma sección, Documentos, con dos pestañas: Contratos para el texto legal, que es uno para todo el parque, y Normas para los juegos de reglas, que cambian de una casa a otra. Están juntas porque se combinan en un solo documento; separadas en el menú parecían cosas sin relación.
Por qué varias plantillas
Una cuenta puede gestionar alojamientos con condiciones legales distintas — un edificio de apartamentos y una casa rural no siempre firman lo mismo. Cada plantilla se crea una vez y se asigna por id a cuantas propiedades la usen (contractTemplateId en /properties). La propiedad que no tenga ninguna asignada usa la marcada como isDefault del tenant.
Si lo único que cambia entre dos alojamientos son las normas, no necesitas una segunda plantilla: crea otro juego de normas y asígnaselo. Las plantillas solo se multiplican cuando cambia el articulado.
El objeto plantilla
{
"id": "8a2e1f0c-…",
"name": "Villas premium",
"isDefault": true,
"bodyMode": "clauses",
"showOwnerInfo": true,
"showOwnerDocument": true,
"showPropertyInfo": true,
"houseRules": [
{ "es": "No se permiten fiestas ni eventos.", "en": "No parties or events." }
],
"legalClauses": [
{ "es": "El huésped se compromete a respetar el aforo máximo indicado.", "en": "…" }
],
"createdAt": "2026-07-01T09:00:00.000Z",
"updatedAt": "2026-07-19T10:00:00.000Z"
}Cada cláusula es bilingüe: es es obligatorio, en opcional (el contrato generado siempre incluye ambos idiomas — si falta en, esa cláusula aparece solo en español).
El campo houseRules de la plantilla es el modelo anterior y se mantiene por compatibilidad: solo se imprime si la reserva no resuelve a ningún juego de normas. En una configuración nueva, deja este campo como esté y escribe las normas en su propio recurso.
Pegar un contrato ya redactado
Si ya tienes tu contrato escrito, no hace falta trocearlo en cláusulas. Pon bodyMode: "document" y manda el texto completo en body, con una entrada por idioma.
curl -X PATCH https://api.kinapi.es/api/v1/contract-templates/8a2e1f0c-… -H "ocp-apim-subscription-key: TU_API_KEY" -H "content-type: application/json" -d '{
"bodyMode": "document",
"body": {
"es": "# CONTRATO DE ARRENDAMIENTO
## Artículo 1. Objeto
Este contrato regula el **uso turístico**…",
"nl": "# HUUROVEREENKOMST
## Artikel 1. Voorwerp
Deze overeenkomst regelt het **toeristisch gebruik**…"
}
}'El marcado admitido es deliberadamente corto, porque todo lo que se acepta hay que saber dibujarlo en el PDF: # ## ### para títulos de capítulo, artículo y apartado; - y 1. para listas; **negrita** y *cursiva*. No hay tablas, imágenes, enlaces ni numeración multinivel: un «Artículo 3.2» se escribe como título.
La numeración de las listas se recalcula al imprimir, así que puedes escribir todos los puntos como 1. y saldrán 1, 2, 3. Insertar un artículo en medio no obliga a renumerar el resto.
El contrato se imprime siempre a dos columnas: el idioma de la reserva y el castellano. El inmueble está en España y el propio contrato se somete a juzgados españoles, así que la versión castellana no se puede quitar. Si la reserva ya es en castellano, la segunda columna es el inglés.
Un idioma que no hayas escrito cae al castellano entero, nunca a medias: nadie firma un contrato con la mitad de los artículos en un idioma y la otra mitad en otro. Y las condiciones legales (comunicación a SES Hospedajes, protección de datos y ley aplicable) se añaden al final en los dos modos.
En modo documento, el texto que pegas suele traer ya sus propias normas dentro, así que las del modelo anterior —las escritas dentro de la plantilla o dentro de la propiedad— no se añaden. Un juego de normas sí se imprime, en su propia sección detrás del cuerpo, cuando la reserva resuelve a uno: el suyo, el de la propiedad o el predeterminado del tenant. Es justo la combinación «articulado × normas» para la que existe la separación.
El idioma en que se emite el contrato es el de la reserva, y sale de una cadena de tres pasos: el locale que mandes en POST /checkins, si no el communicationLocale del alojamiento, y si tampoco, castellano. Es la misma cadena que decide el formulario del huésped y los correos que se le envían: un solo ajuste gobierna las tres cosas, para que no se pueda dar el caso de invitar en un idioma y hacer firmar en otro.
Con contractTemplateId en esa misma llamada puedes elegir una plantilla distinta por reserva, si prefieres una por idioma en vez de un único documento multilingüe.
La bandera que el huésped pulsa en su enlace cambia el idioma del formulario mientras se registra, pero no el del contrato: ese sigue siendo el de la reserva. Quien firma no puede cambiar el idioma del documento que firma, ni por descuido ni a propósito.
showOwnerInfo, showOwnerDocument y showPropertyInfo controlan qué secciones del PDF aparecen — no qué datos se usan (esos siempre salen de la propiedad y el propietario asignados). Por ejemplo, para no revelar el NIF/CIF del propietario en el contrato que firma el huésped, deja showOwnerInfo en true pero pon showOwnerDocument en false.
Qué plantilla se usa en cada contrato
De la más específica a la más general, se aplica la primera que exista:
- La plantilla de la reserva (
contractTemplateIddel check-in). - La plantilla de la propiedad (
contractTemplateIdde la propiedad). - La predeterminada del tenant (
isDefault). - Las cláusulas de fábrica de Kinapi, para la cuenta que aún no ha creado ninguna plantilla.
Un id que apunta a una plantilla borrada cae a la predeterminada del tenant, no a las cláusulas de fábrica. Si borras una plantilla que alguna reserva o propiedad seguía referenciando, esos contratos pasan a tu articulado predeterminado — antes salían con el texto genérico de Kinapi sin ningún aviso.
El id que se busca es uno solo: el de la reserva si lo hay, y si no el de la propiedad. Una reserva que referencia una plantilla borrada va directa a la predeterminada del tenant; no vuelve a la de la propiedad.
El contrato firmado llega por correo
Cuando el grupo termina de firmar, el PDF sale por correo a cada uno de los que lo firmaron, adjunto y con la marca del alojamiento. No hay que activarlo ni pedirlo: pasa siempre que el firmante dejara un correo. El enlace de descarga sigue funcionando igual que antes — esto se añade, no sustituye a nada.
Va un correo por persona, no uno con todos en «Para». Los huéspedes de una reserva son personas distintas, y juntarlos en la cabecera reparte la dirección de cada uno entre los demás sin que ninguno lo haya autorizado.
Se manda una sola vez por reserva, y el envío no bloquea la firma: si el correo falla, el huésped ya ha firmado y el contrato sigue descargable desde su enlace. La firma nunca depende de que un proveedor de correo responda.
Listar plantillas
/contract-templatescurl https://api.kinapi.es/api/v1/contract-templates \ -H "ocp-apim-subscription-key: TU_API_KEY"
Crear una plantilla
/contract-templates| Campo | Tipo | Descripción |
|---|---|---|
namerequerido | string | Nombre de la plantilla. |
isDefault | boolean | Marcarla como predeterminada del tenant. |
showOwnerInfo | boolean | Mostrar la sección de datos del propietario/sociedad en el PDF. Por defecto true. |
showOwnerDocument | boolean | Mostrar el NIF/CIF del propietario dentro de esa sección. Por defecto true; solo aplica si showOwnerInfo. |
showPropertyInfo | boolean | Mostrar la sección de datos de la propiedad (alojamiento y dirección). Por defecto true. |
showTouristLicense | boolean | Imprimir la licencia turística dentro de esa sección. Por defecto true; solo aplica si showPropertyInfo. Ponlo a false en una plantilla de arrendamiento de temporada: el alojamiento puede tener licencia y enseñarla ahí induce a error sobre el régimen legal del documento que se firma. Si la propiedad no tiene licencia, la línea no se imprime en ningún caso. |
titulo | {es, en} | Encabezado del PDF. Ausente ⇒ «CONTRATO DE ALQUILER TURÍSTICO / Tourist Rental Contract». Cámbialo cuando tus estancias no sean alquiler turístico — por ejemplo «CONTRATO DE ARRENDAMIENTO TEMPORAL» para un arrendamiento de temporada del Título III de la LAU. |
houseRules | [{es, en?}] | Normas escritas dentro de la plantilla (modelo anterior). Si se omite, se crea con las normas estándar de Kinapi. Solo se imprimen si la reserva no resuelve a ningún juego de /house-rules. |
legalClauses | [{es, en?}] | Si se omite, se crea con las cláusulas legales estándar de Kinapi. |
bodyMode | 'clauses' | 'document' | Cómo se escribe el cuerpo. 'clauses' (por defecto) usa houseRules; 'document' usa body. Las cláusulas se conservan al cambiar de modo. |
body | {es, en?, de?, fr?, nl?, it?, pt?} | Solo en modo documento: el contrato completo, un texto por idioma. `es` es OBLIGATORIO (el PDF siempre lleva castellano). Se fusiona por idioma —mandar solo `nl` no borra el `es` guardado— y una cadena vacía borra ese idioma. Máx. 60.000 caracteres por idioma. |
curl -X POST https://api.kinapi.es/api/v1/contract-templates \
-H "ocp-apim-subscription-key: TU_API_KEY" \
-H "content-type: application/json" \
-d '{ "name": "Villas premium", "isDefault": true }'Crear sin houseRules/legalClauses es la forma más rápida de arrancar: obtienes las cláusulas estándar de Kinapi y luego las editas con un PATCH.
Consultar una plantilla
/contract-templates/{id}Actualizar una plantilla
/contract-templates/{id}Actualización parcial: solo se cambian los campos enviados. Para editar cláusulas, envía la lista houseRules o legalClauses completa — sustituye a la anterior, no la combina cláusula a cláusula.
curl -X PATCH https://api.kinapi.es/api/v1/contract-templates/8a2e1f0c-… \
-H "ocp-apim-subscription-key: TU_API_KEY" \
-H "content-type: application/json" \
-d '{ "houseRules": [{ "es": "No se admiten mascotas." }] }'Eliminar una plantilla
/contract-templates/{id}Las propiedades y las reservas que la tenían asignada pasan a usar la predeterminada del tenant (o las cláusulas estándar de Kinapi si tampoco hay predeterminada). El contractTemplateId huérfano se queda guardado —nadie limpia la referencia—, pero al generar el contrato no se busca otra plantilla asignada: se va directo a la predeterminada, aunque la reserva sea de una propiedad que sí tiene la suya.
Vista previa de la plantilla
/contract-templates/{id}/previewDevuelve un PDF de ejemplo con esta plantilla —cláusulas o documento, según su bodyMode— sobre datos ficticios. Sirve para comprobar cómo queda el contrato al montarlo por API, sin crear un check-in real ni esperar a que alguien firme.
Acepta ?locale= para verlo en el idioma de una reserva concreta. Aplica exactamente el mismo criterio que el contrato real: lo que veas aquí es lo que van a firmar tus huéspedes.
Enseña la plantilla sola: su articulado y, en modo cláusulas, las normas escritas dentro de ella. Los juegos de normas asignados no se combinan aquí, porque esa combinación depende de la propiedad y de la reserva, y la vista previa no tiene ninguna.
curl -OJ -X POST "https://api.kinapi.es/api/v1/contract-templates/8a2e1f0c-…/preview?locale=nl" \ -H "ocp-apim-subscription-key: TU_API_KEY"
Normas de la casa
Un juego de normas es un documento propio del tenant, hermano de la plantilla de contrato y no una sección dentro de ella. Se crean y se editan en la sección Normas de la casa del panel o por API en /house-rules, y se combinan con el articulado al generar cada contrato.
Un juego se escribe de dos maneras, las mismas que el cuerpo del contrato: clauses (una lista bilingüe, norma a norma) o document (el texto ya redactado, uno por idioma, con el mismo marcado que el contrato pegado). Las cláusulas se conservan aunque pases a modo documento: volver atrás no cuesta el trabajo ya hecho.
El objeto juego de normas
{
"id": "c41b90a7-…",
"name": "Chalets con piscina",
"isDefault": false,
"bodyMode": "clauses",
"clauses": [
{ "es": "La piscina cierra a las 22:00.", "en": "The pool closes at 22:00." },
{ "es": "No se admiten mascotas.", "en": "No pets allowed." }
],
"hasText": true,
"createdAt": "2026-08-01T09:00:00.000Z",
"updatedAt": "2026-08-12T10:00:00.000Z"
}En modo documento el objeto lleva además body, con una entrada por idioma, y clauses sigue ahí como respaldo. hasText es un campo calculado: dice si el juego tiene texto de verdad en el modo en que está escrito.
Un juego sin texto no sale en ningún contrato. Al resolver se salta y se sigue con el candidato siguiente, así que asignarlo no da error pero tampoco imprime nada. Comprueba hasText antes de dar por hecho que un juego recién creado ya está imprimiendo.
Qué normas salen en cada contrato
De lo más específico a lo más general, se aplica lo primero que tenga texto:
- El juego de la reserva (
houseRulesIddel check-in). - El juego de la propiedad (se asigna desde el panel, en la ficha del alojamiento).
- El juego predeterminado del tenant (
isDefault). - Las normas del modelo anterior: las escritas dentro de la propiedad si las tiene, y si no las de la plantilla de contrato.
- Las normas estándar de Kinapi, para la cuenta que aún no ha creado ninguna plantilla.
La cadena no se corta por un id que ya no existe: si borras un juego que una reserva antigua seguía referenciando, ese contrato pasa al escalón siguiente en vez de quedarse sin normas. Lo mismo con un juego que existe pero está vacío. Y una plantilla cuya lista de normas hayas vaciado a propósito no imprime ninguna sección de normas: vaciarla es una orden, no un hueco.
Las normas se imprimen en su propia sección, NORMAS DE LA CASA / HOUSE RULES, justo antes de las condiciones legales. En modo cláusulas salen numeradas y bilingües; en modo documento, en el idioma de la reserva con el castellano debajo, igual que el cuerpo del contrato.
Con la plantilla en modo documento, los dos últimos escalones no llegan a imprimirse: el texto pegado ya trae sus propias normas dentro, así que solo añaden sección los tres primeros, es decir, un juego de la reserva, de la propiedad o el predeterminado del tenant.
Asignar un juego a una reserva
Con houseRulesId en POST /checkins eliges, en la misma llamada que crea la reserva, qué articulado y qué normas forman el contrato que se va a firmar — sin que tenga que existir una plantilla por cada cruce de los dos ejes.
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": "ALJ-3F9K2P",
"confirmationCode": "HMXK4L",
"checkInDate": "2026-09-01",
"checkOutDate": "2026-09-08",
"numGuests": 4,
"contractTemplateId": "8a2e1f0c-…",
"houseRulesId": "c41b90a7-…"
}'También se puede cambiar después con PATCH /checkins/{reservationId}. Una cadena vacía desasigna el juego de la reserva y devuelve el mando a la propiedad. El contrato ya firmado no se regenera —conserva las normas con las que se firmó, su sello de tiempo y su copia inmutable—, así que el cambio solo afecta a lo que se firme a partir de ahí.
El juego de cada propiedad se asigna desde el panel, en la ficha del alojamiento: es configuración que se toca una vez y se deja quieta, mientras que la reserva es lo que decide tu PMS llamada a llamada.
Listar juegos de normas
/house-rulesDevuelve { "results": [...] } con todos los juegos del tenant, ordenados por nombre.
curl https://api.kinapi.es/api/v1/house-rules \ -H "ocp-apim-subscription-key: TU_API_KEY"
Crear un juego de normas
/house-rules| Campo | Tipo | Descripción |
|---|---|---|
namerequerido | string | Nombre con el que se elige en el panel y en la API ("Chalets con piscina"). |
id | string | Si se envía, se actualiza ese juego en vez de crear otro. Sirve para reintentar el alta sin acabar con seis juegos iguales cuando la primera llamada se cortó después de guardar. |
isDefault | boolean | Marcarlo como predeterminado del tenant. Solo puede haber uno: marcarlo aquí desmarca al anterior. |
clauses | [{es, en?}] | Las normas, una por elemento. Si se omite al crear, el juego nace con las normas estándar de Kinapi; en una actualización (con id) se conservan las que ya tenía. |
bodyMode | 'clauses' | 'document' | Cómo está escrito el texto. 'clauses' (por defecto) usa la lista; 'document' usa body. Las cláusulas se conservan al cambiar de modo. |
body | {es, en?, de?, fr?, nl?, it?, pt?} | Solo en modo documento: las normas ya redactadas, un texto por idioma, con el mismo marcado que el cuerpo del contrato. `es` es OBLIGATORIO. Se fusiona por idioma y una cadena vacía borra ese idioma. Máx. 60.000 caracteres por idioma. |
curl -X POST https://api.kinapi.es/api/v1/house-rules \
-H "ocp-apim-subscription-key: TU_API_KEY" \
-H "content-type: application/json" \
-d '{
"name": "Chalets con piscina",
"clauses": [
{ "es": "La piscina cierra a las 22:00.", "en": "The pool closes at 22:00." }
]
}'Responde 201 con el juego creado, o 200 si enviaste id y por tanto era una actualización. Guarda el id: es lo que se manda luego en houseRulesId.
Con bodyMode: "document" hay que mandar body.es con texto. Sin él la llamada devuelve 400: el contrato se imprime siempre también en castellano y esa columna no se puede generar sola.
Consultar un juego de normas
/house-rules/{id}Devuelve el juego completo, incluidas las clauses aunque esté en modo documento. 404 si el id no es de este tenant.
Actualizar un juego de normas
/house-rules/{id}Actualización parcial: solo cambian los campos enviados. La lista clauses se envía completa y sustituye a la anterior; body se fusiona por idioma, así que mandar solo el neerlandés no borra el castellano ya escrito — y una cadena vacía borra ese idioma, que es como se elimina una traducción.
curl -X PATCH https://api.kinapi.es/api/v1/house-rules/c41b90a7-… \
-H "ocp-apim-subscription-key: TU_API_KEY" \
-H "content-type: application/json" \
-d '{ "isDefault": true, "clauses": [{ "es": "No se admiten mascotas.", "en": "No pets allowed." }] }'Eliminar un juego de normas
/house-rules/{id}Se puede borrar aunque haya propiedades o reservas que lo referencian: esos ids quedan huérfanos y, al generar el contrato, se pasa al escalón siguiente de la precedencia. Ninguna reserva antigua se queda sin normas ni falla al generar el PDF.
Borrar el juego predeterminado sí cambia contratos de alojamientos que no se nombran en la llamada: nadie hereda ese papel, así que todas las propiedades sin juego propio pasan a imprimir las normas del modelo anterior. Marca otro como isDefault antes de borrarlo.
Descargar el contrato firmado
/checkins/{reservationId}/contractDevuelve el PDF definitivo del contrato de una reserva: a dos columnas —el idioma de la reserva y el castellano—, con la firma de cada huésped incrustada y una página final de registro de evidencias (fecha y hora de cada firma, IP, dispositivo y hash SHA-256 de cada imagen de firma).
curl -OJ https://api.kinapi.es/api/v1/checkins/R-2026-0412/contract \ -H "ocp-apim-subscription-key: TU_API_KEY"
Responde 404 mientras el grupo no haya completado la firma (consulta contract.signed en el check-in para saber cuándo está disponible).
Qué garantiza la evidencia
Cada contrato completado genera automáticamente un rastro de tres capas:
- Copia inmutable (WORM): el PDF exacto y su sello de tiempo se archivan en almacenamiento con política de retención de 3 años: no se pueden sobrescribir ni borrar antes de que venza.
- Sello de tiempo RFC 3161: el hash SHA-256 del PDF se sella en una autoridad de sellado externa. Al recibir el sello se comprueba que sella ese hash y que responde a nuestro nonce; solo entonces cuenta. Cualquier tercero puede verificarlo con
openssl ts(abajo). - Registro append-only: hash del documento, token del sello y, por firmante, IP + dispositivo + hora + hash de su firma.
El huésped también puede descargar su propia copia desde su enlace — una copia fuera de tu control y del nuestro que reforzaría la evidencia ante cualquier disputa.
Obtener y verificar la evidencia
El registro de evidencia de cada contrato firmado —hash del documento, sello de tiempo y firmantes— se consulta por API, y el sello se descarga tal cual lo emitió la autoridad: un TimeStampResp RFC 3161 en DER (extensión .tsr).
/checkins/{reservationId}/evidence| Campo | Tipo | Descripción |
|---|---|---|
id | string | Identificador del registro; con él se descarga el sello. |
createdAt | ISO 8601 | Cuándo completó la firma el último huésped y se generó el PDF definitivo. |
sha256 | hex | Huella SHA-256 del PDF firmado. Es exactamente lo que se selló. |
tsa.ok | boolean | true si hay sello verificado (hash y nonce comprobados al recibirlo). |
tsa.genTime | ISO 8601 | Instante que certifica la autoridad de sellado. |
tsa.authority | string | URL de la autoridad de sellado. |
tsa.archived | boolean | true cuando el sello está también en el almacenamiento inmutable, junto al PDF. |
tsa.error | string | Por qué no hay sello, si no lo hay. Se reintenta cada hora. |
signers | array | Por firmante: guestId, name, signedAt, ip, userAgent, signatureSha256 y uiLocale (idioma en que vio el formulario). |
/checkins/{reservationId}/evidence/{evidenceId}/tokenDevuelve el sello en binario (application/timestamp-reply). 404 con no_timestamp si el registro aún no tiene sello.
curl -OJ https://api.kinapi.es/api/v1/checkins/R-2026-0412/evidence/EVIDENCE_ID/token \ -H "ocp-apim-subscription-key: TU_API_KEY"
# Qué sella y cuándo openssl ts -reply -in sello.tsr -text # Que sella ESTE PDF y que la firma de la autoridad es válida openssl ts -verify -digest $(shasum -a 256 contrato.pdf | cut -d' ' -f1) \ -in sello.tsr -CAfile /etc/ssl/cert.pem
La autoridad es DigiCert (timestamp.digicert.com): externa a Kinapi, y su certificado viaja dentro del token para poder verificarlo dentro de años. No es un sello cualificado eIDAS: es admisible como prueba, sin la presunción legal de exactitud que tiene un prestador cualificado.
Parte de viajeros (el documento exigible)
/checkins/{reservationId}/parteNo confundir con el contrato: el parte de entrada de viajeros es el documento que el RD 933/2021 obliga a conservar y a exhibir si te lo requieren. Sin parámetros devuelve un ZIP con el parte de cada huésped; con ?guestId=, el PDF de esa persona.
curl -OJ "https://api.kinapi.es/api/v1/checkins/R-2026-0412/parte" \ -H "ocp-apim-subscription-key: TU_API_KEY"