Check-ins

The core resource of the API: one check-in per booking, always identified by your reservationId.

The check-in object

Object
{
  "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"
}

The optional fields — maxGuests, returnUrl, contractTemplateId, houseRulesId, tipoPago, skipContract, manualSubmit and errorReason — only appear when the booking carries them. Their absence means the booking does not have them, not that they are unknown.

anonymizedAt only appears on a booking whose personal data was suppressed when its owner was offboarded: it is still listed, but it no longer has guests, a lead guest or files. See Exports and offboarding.

Statuses (status)
FieldTypeDescription
pendingstatusCreated; no guest registered yet.
in_progressstatusAt least one guest registered.
completedstatusEvery guest registered.
signedstatusContract signed by the whole party.
submittedstatusTraveller report sent to SES (batch assigned).
confirmedstatusSES confirmed every communication.
errorstatusNeeds attention — see errorReason.
cancelledstatusBooking cancelled.

Create a check-in

POST/checkins
Body
FieldTypeDescription
reservationIdrequiredstringThe booking id in your system. It is the public identifier of the check-in.
listingIdrequiredstringListing id — a property with that listingId must exist. If none does, the booking is still created, in the error status, and the response carries a warning with the reason: nothing is lost silently.
confirmationCoderequiredstringThe confirmation code the guest sees.
checkInDaterequiredYYYY-MM-DDArrival date.
checkOutDaterequiredYYYY-MM-DDDeparture date.
numGuestsrequiredinteger 1-50Expected number of guests (the party can adjust it).
arrivalTimehh:mmCheck-in time (16:00 by default).
departureTimehh:mmCheck-out time (11:00 by default).
sourcestringBooking channel: airbnb, booking, direct… Free text and informational: it shows in the panel and comes back in responses, but is not sent to SES. Without it, the booking is left as unknown.
leadGuestobject{ name, email, phone } of the lead guest, to prefill their registration.
localestringLanguage of the form, the contract and the emails to the guest: es, en, de, fr, nl, it or pt. Regional variants are accepted (nl-NL → nl). If you do NOT send it, the property's own default language is used, and Spanish if it has none. What you send here overrides the property, because it is about one specific guest. A language we do not support falls back to Spanish WITHOUT an error — check the locale in the response to see which one was applied.
returnUrlstringhttps URL the guest goes back to when they finish the check-in, or when they choose to leave part-way. They go back by TAPPING a button, never on their own: that way they can stay to review what they sent, and opening their link again does not bounce them out of it. Lets you embed the registration inside your own flow. https only and no embedded credentials; an invalid URL returns 400. We check the SHAPE, not that it works: send it complete, including any trailing token or id, because a well-formed but incomplete URL is accepted and fails at the very end, once the guest has already finished.
maxGuestsinteger 1-50The property’s real capacity. Guests can adjust the group size from their link, and this is the ceiling they cannot exceed. If the number sent was too high they can always LOWER it, even above the cap. Without it only the global limit applies.
contractTemplateIdstringContract template for this booking. It takes precedence over the one assigned to the property; with neither, the tenant default is used.
houseRulesIdstringHouse rules set for this booking. It is the other axis of the contract: it combines with contractTemplateId, so you never need one template per pairing of legal clauses and rules. It takes precedence over the set assigned to the property.
tipoPagostringHow THIS stay was paid for: a code from the SES TIPO_PAGO catalogue (PLATF, TRANS, EFECT…). It overrides the one on the property and the one on the SES credentials. Send it only when that booking was collected differently from the property’s usual method.
skipContractbooleantrue = this booking carries no terms of your own: the document comes out with the house rules and the legal conditions, nothing else. For stays that do not need a contract. The legal conditions cannot be removed: they carry the notice that data is filed with the Ministry and the data-protection information.
manualSubmitbooleantrue = this booking’s report does NOT go out in the nightly submission to SES.Hospedajes, nor is it filed again on its own if its dates change. It is reported when you ask for it: from the panel with “Send manually”, or by calling POST /checkins/{reservationId}/ses. For stays you want to review first. The legal deadline for the report still applies.
curl
curl -X POST https://api.kinapi.es/api/v1/checkins \
  -H "ocp-apim-subscription-key: YOUR_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 and tipoPago only come back when the booking has them stored: that is how you confirm they were saved. All three — along with skipContract and manualSubmit — also come back in the check-in object (GET and PATCH), so whatever you change later can be read back without holding on to the response from the creation call.

Idempotent: if the check-in already exists, it answers 200 with the existing one (created: false) and the same URL. Retry freely. The second call does not modify the booking that was already there: to change its dates, template, rules or payment method, use PATCH.

For returnUrl we check the shape, not that it works. That it is https and carries no credentials inside; that is where it ends. We never visit it or verify that it exists, because at the moment the booking is created it may not exist yet, and because opening connections to addresses dictated from outside is a risk that is not worth what it buys.

The practical consequence: a well-formed but incomplete URL is accepted without complaint and fails at the end, when the guest has already finished, taps to go back and lands on a 404. This happens with addresses that carry a token or an id in the last segment and are assembled piece by piece — …/booking/R-2026-0412 instead of …/booking/R-2026-0412/f9K2xQ. Send it whole, token included, and test it by pasting it into a browser before you wire it up.

Contract and house rules: two axes that combine

The legal clauses are the same across almost the whole portfolio; what changes from one home to the next are the rules — pool and barbecue here, lift and quiet hours there. That is why house rules are an entity of their own rather than a section inside the contract template: every booking picks its own pairing with contractTemplateId (the clauses) and houseRulesId (the rules).

M templates and N rule sets therefore give you M × N contracts without duplicating a single paragraph. With the rules living inside the template, changing one rule for one apartment meant duplicating the whole template — legal clauses included — and then keeping the same legal text in copies that drift apart on their own. Rule sets are created and listed at /v1/house-rules, and a property is given its own set from the panel; see Contracts and Properties.

Which house rules end up in the contract (most specific first)
FieldTypeDescription
houseRulesId on the booking1stWhat you send in POST or PATCH /checkins. The only one that can be decided call by call, with the booking already in front of you.
houseRulesId on the property2ndThe set assigned to the property, either from its panel record (Property details) or over the API: /v1/properties accepts it on creation and on PATCH, and returns it in the property object. It covers every booking of that property that does not carry its own.
the tenant default set3rdThe one flagged isDefault in /v1/house-rules. It covers every property with no set of its own.
inherited rules4thWith no set to apply, the property’s own rules (the deprecated houseRules field) or, when those are empty, the ones written inside the contract template. Failing all of the above, Kinapi’s factory rules.

A houseRulesId pointing at a deleted set, or at one left with no text in any language, does not break the chain: resolution carries on with the next candidate. A deletion months later cannot strip the rules from bookings that already referenced it.

Payment method for the booking

tipoPago is the code from the Ministry’s TIPO_PAGO catalogue that travels in the traveller report. It belongs to the booking, not to the property: the same house is paid through the platform one week and by bank transfer the next, and what SES asks is how that stay was paid for.

tipoPago cascade (most specific first)
FieldTypeDescription
tipoPago on the booking1stWhat you send in POST or PATCH /checkins. Normally you leave it out and let the property decide.
ses.tipoPago on the property2ndHow that property is usually paid for.
tipoPago on the SES credentials3rdThe landlord default. It is required when the credentials are created, so the cascade always ends in a value and no report goes out without a payment method.

Valid codes: EFECT, TARJT, PLATF, TRANS, MOVIL, DESTI, TREG and OTRO — the table with what each one means is in SES credentials. The value is read when the report is built, not when the booking is created: a PATCH before submission changes what gets filed. After that, changing only the payment method does not redo what was filed; changing the dates does, and the new filing carries the current value.

List check-ins

GET/checkins
Query
FieldTypeDescription
fromYYYY-MM-DDArrival from (inclusive).
toYYYY-MM-DDArrival to (inclusive).
statusstringFilter by status (see the status table).
listingIdstringOnly check-ins for that listing.
ownerIdstringOnly the bookings of this owner’s properties (id from /owners), including their deactivated properties and the bookings that came in under a channel id. An owner that does not exist returns 400 validation.
limitinteger 1-200Page size (50 by default).
offsetintegerPagination offset.
curl
curl "https://api.kinapi.es/api/v1/checkins?from=2026-08-01&to=2026-08-31&status=confirmed" \
  -H "ocp-apim-subscription-key: YOUR_API_KEY"
200 OK
{
  "results": [ { …check-in… }, … ],
  "hasMore": false
}

Read a check-in

GET/checkins/{reservationId}

Returns the check-in with the guests array included (same shape as the Guests resource).

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

Update a check-in

PATCH/checkins/{reservationId}
Body (all optional)
FieldTypeDescription
checkInDateYYYY-MM-DDNew arrival date. If the report has already been filed, Kinapi annuls it and files it again with the new date: see below.
checkOutDateYYYY-MM-DDNew departure date. If the report has already been filed, Kinapi annuls it and files it again with the new date: see below.
arrivalTimehh:mmNew check-in time.
departureTimehh:mmNew check-out time.
numGuestsinteger 1-50New expected number of guests.
leadGuestobjectCorrect the lead guest details: { name, email, phone }. It is MERGED with what the booking already has, so send only the field that changed and the rest stays as it was; an empty string clears that field. The email matters more than it looks: it is the address the check-in invitation, the reminder and the signed contract go to, so a stale one leaves the guest without their link and nothing errors.
localestringNew language for the form, the contract and the emails. Empty returns the booking to the property's own default.
returnUrlstringhttps URL the guest goes back to when they finish the check-in, or when they choose to leave part-way. They go back by TAPPING a button, never on their own: that way they can stay to review what they sent, and opening their link again does not bounce them out of it. Lets you embed the registration inside your own flow. https only and no embedded credentials; an invalid URL returns 400. We check the SHAPE, not that it works: send it complete, including any trailing token or id, because a well-formed but incomplete URL is accepted and fails at the very end, once the guest has already finished. Empty string to remove it.
maxGuestsinteger 1-50The property’s real capacity. Guests can adjust the group size from their link, and this is the ceiling they cannot exceed. If the number sent was too high they can always LOWER it, even above the cap. Without it only the global limit applies. Send an empty string (or 0) to remove it.
contractTemplateIdstringChanges the contract template for this booking. Empty string to fall back to the property’s. If the contract was already signed, the template it was signed with is kept and the response includes a warning: the change only affects later signatures.
houseRulesIdstringChanges the house rules set for this booking. Empty string to unassign it and hand control back to the property. On an already signed contract nothing changes: the archived PDF keeps the rules it was signed with.
tipoPagostringChanges the payment method for this booking. Empty string to unassign it and fall back to the property’s. Useful when the way a stay was collected changes after the booking was created: as long as the report has not been filed, this is the value that will be reported.
skipContractbooleantrue = this booking carries no terms of your own: the document comes out with the house rules and the legal conditions, nothing else. For stays that do not need a contract. The legal conditions cannot be removed: they carry the notice that data is filed with the Ministry and the data-protection information.
manualSubmitbooleantrue = this booking’s report does NOT go out in the nightly submission to SES.Hospedajes, nor is it filed again on its own if its dates change. It is reported when you ask for it: from the panel with “Send manually”, or by calling POST /checkins/{reservationId}/ses. For stays you want to review first. The legal deadline for the report still applies.
sourcestringCorrects the booking channel (airbnb, booking, direct…). Informational: it changes nothing with SES. An empty string sets it back to unknown.

Choosing the contract by length of stay. If your regional rules distinguish regimes by duration — in the Valencian Community, more than 10 days stops being a tourist dwelling and becomes a seasonal tenancy under the LAU — your system makes that call, because it is the one that knows your case and its legal reading:

contractTemplateId = nights > 10 ? SEASONAL_TEMPLATE : TOURIST_TEMPLATE

And if the booking is extended and crosses the threshold, the same PATCH that changes the dates can change the contract. In the panel, each booking shows its nights, its language and which template it has, so you can check that your logic did what you expected.

curl
curl -X PATCH https://api.kinapi.es/api/v1/checkins/R-2026-0412 \
  -H "ocp-apim-subscription-key: YOUR_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "checkOutDate": "2026-08-19" }'

Changing the dates of an already filed booking files it again. SES Hospedajes has no modify operation — only create, query and annul — and the report carries the dates. If you move the arrival or departure of a booking whose report has already gone out, Kinapi annuls that communication with the Ministry and files it again with the guests it already had: straight away if the new arrival is today or in the past, and if it is in the future, in that night’s submission, like any other booking.

It annuls first and only then saves. If the annulment does not go through, the booking stays as it was, here and at the Ministry, and the response says so: you never end up with new dates in Kinapi and old ones at SES. Only the dates trigger this; times, guest count or payment method are saved without touching what was filed.

What the PATCH returns when it changes the dates of a filed booking
FieldTypeDescription
refiling200Annulled and saved. anulados: how many communications were annulled. reenviado: true if it was filed again straight away, with the new batch in lote; false if it goes out in the nightly submission for the new arrival. If the immediate refiling failed, error explains why and the nightly submission retries it. retenida: true if the booking is on manual submission (manualSubmit): it was annulled, but the new report does not go out until you call POST /checkins/{reservationId}/ses.
refiling_blocked · en_vuelo409The report has been sent and SES has not confirmed it yet, so it cannot be annulled yet. Nothing was saved. Retry in an hour: the SES response is checked hourly.
refiling_blocked · anulacion_fallida503SES did not accept the annulment or did not answer. Nothing was saved: the booking keeps its previous dates. Retry later.
200 OK — new arrival already past: annulled and filed again
{
  …check-in with the new dates…,
  "refiling": { "anulados": 2, "reenviado": true, "lote": "…" }
}
409 Conflict — report not yet confirmed by SES: nothing was saved
{
  "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 — this stay was paid by bank transfer and goes back to the property rules
curl -X PATCH https://api.kinapi.es/api/v1/checkins/R-2026-0412 \
  -H "ocp-apim-subscription-key: YOUR_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "tipoPago": "TRANS", "houseRulesId": "" }'

Guest link validity

The guest link — the url field of the response — is a bearer credential: whoever holds it can register guests on that booking. It expires 14 days after the checkout date; from then on it returns 410 and stops serving data and the contract PDF. If you need to reopen it, create a new check-in.

Within the link, the fields that identify a person — document number, support number and date of birth — are only shown to whoever registered them, and only for a few hours. After that they can be corrected but not read: the rest of the group sees the document masked. This is what stops a forwarded link from being used to look up someone else’s details.

Cancel or delete a check-in

DELETE/checkins/{reservationId}

Either way the booking leaves the flow: the guest link stops accepting registrations and no report will be filed. What differs is whether a trace remains, and that is not your call: a booking that has not left anything worth keeping is deleted, one that has is cancelled. The response says which in action, so you never have to guess: what was deleted no longer shows up in GET /checkins, what was cancelled still does.

The two outcomes (action field)
FieldTypeDescription
deletedactionNobody had signed, nothing had been filed with the Ministry and the booking had not been billed: it is deleted along with its guests. The response carries reservationId and deletedGuests (how many guests went with it).
cancelledactionThere was something to keep: the booking moves to the cancelled status and no data is deleted. The response carries the full check-in object, reason with the motive, and ses with the outcome of the annulment.
Why it could not be deleted (reason field)
FieldTypeDescription
firmadoreasonThere is a signed contract, a guest who already signed, or archived signature evidence. It is kept sealed for three years: a document that can vanish at the request of whoever issued it proves nothing.
comunicadoreasonIts reports had already gone to the Ministry. Deleting here would leave a filing there with nothing to explain it and no way to annul it.
facturadoreasonThe booking generated billable usage, and that is accounting history backing an invoice.

If that booking’s report had already been filed — the comunicado case, which always ends in a cancellation — Kinapi annuls it with SES Hospedajes in the same call: Article 6.3 of RD 933/2021 requires the cancellation to be reported too, and without it the Ministry would still hold a stay that is not going to happen. The response carries ses.anulados with how many communications were annulled, and ses.anulacionError if the Ministry did not accept the annulment. The booking is cancelled either way and Kinapi retries the annulment every hour, up to 72 attempts (three days); if it does not get through, it emits the ses.cancel_failed webhook. You can also repeat the DELETE to retry it on the spot. When the Ministry accepts the annulment, the ses.cancelled webhook is emitted, with reason: "cancelled". In the deleted case there is, by definition, nothing to annul.

Cancelled while the report was in flight. If the report had been sent and SES had not confirmed it yet, it cannot be annulled yet: for the Ministry it does not exist so far. The booking is cancelled anyway, and as soon as the confirmation arrives Kinapi annuls those communications; the booking stays cancelled.

Accepting an annulment is not processing it. SES processes annulments asynchronously, just like filings: Kinapi checks the annulment batch every hour and, if the Ministry rejected it, emits ses.cancel_failed. The communication still stands and needs checking in SES.HOSPEDAJES. The state of each annulment is in the annulment field of GET /checkins/{reservationId}/ses.

curl
curl -X DELETE https://api.kinapi.es/api/v1/checkins/R-2026-0412 \
  -H "ocp-apim-subscription-key: YOUR_API_KEY"
200 OK — nothing to keep: deleted
{
  "action": "deleted",
  "reservationId": "R-2026-0412",
  "deletedGuests": 2
}
200 OK — report already filed: cancelled and annulled
{
  …check-in with "status": "cancelled"…,
  "action": "cancelled",
  "reason": "comunicado",
  "ses": { "anulados": 3 }
}