Properties

Creating and managing accommodations: the link between your PMS listings, the SES establishment code and the contract details.

The property object

Object
{
  "id": "b8f3a2c1-…",
  "nickname": "Sol 3B Apartment",
  "listingId": "38291",
  "channelListingIds": ["airbnb-99812", "booking-4471"],
  "address": { "street": "Calle del Sol 3", "city": "Tarifa", "province": "Cádiz", "postalCode": "11380" },
  "touristLicense": "VFT/CA/12345",
  "ownerId": "a1b2c3d4-…",
  "maxGuests": 6,
  "communicationLocale": "nl",
  "ses": { "codigoEstablecimiento": "0000012345", "tipoPago": "PLATF" },
  "owner": { "legalName": "Gestora Costa SL", "nif": "B12345678", "fiscalAddress": "…" },
  "contractTemplateId": "8a2e1f0c-…",
  "sesCredentialId": "c4d9b7a3-…",
  "active": true,
  "createdAt": "2026-07-01T09:00:00.000Z",
  "updatedAt": "2026-07-19T10:00:00.000Z"
}

Check-ins reference the property by listingId (or any of its channelListingIds — useful when Airbnb or Booking send the reservation with the channel clone id instead of the master one).

contractTemplateId and sesCredentialId assign this property a specific contract template and a specific set of SES credentials, out of those you created in /contract-templates and /ses-credentials. If they are omitted, the property uses whichever you marked as the default in each case.

communicationLocaleis the accommodation's default language, and it governs everything that guest sees: the check-in form, the contract they sign and the emails they receive. It is one setting, not three, and the order is always the same:

the booking's locale→ the accommodation's language → Spanish

The booking wins because it is about one specific person, whose language your PMS may know; the accommodation is the fallback for when nobody knows. An apartment in Amsterdam set to nl serves everyone who arrives without a declared language in Dutch, and still serves a booking that arrives with locale: "de" in German.

Both axes are picked together. A property stores its default contract in contractTemplateId and its default rules in houseRulesId. Set them from its page in the panel (Property details) or through the API, on create and on PATCH; an empty string unassigns and hands control back to the account default.

When you create a booking you can send contractTemplateId and houseRulesId to override those — both, one, or neither. Whatever you leave out is taken from the property, and whatever the property does not have, from the account default. That is what lets the same property sign a different contract on a long stay without touching its record.

The property’s houseRules array is the older format, from when rules were written inside each property. It is still accepted and still wins when no set is assigned at any level, but do not use it in new integrations.

Create a property

POST/properties
Body
FieldTypeDescription
nicknamerequiredstringInternal name of the accommodation.
commercialNamestringTrading name of the accommodation: what the guest sees in their emails, on their registration link and in the contract. Without it nickname is used (and the registration page shows the city), so nickname can stay an internal code. Empty string removes it. Up to 80 characters.
listingIdstringListing id in your PMS or system. Optional: if omitted, Kinapi generates one shaped like ALJ-XXXXXX and returns it in the response.
channelListingIdsstring[]Ids of channel clones that resolve to this property.
addressobject{ street, city, province, postalCode }. It appears on the contract.
touristLicensestringTourist licence number (e.g. VFT/CA/12345).
maxGuestsinteger 1-50The property’s capacity. Bookings for this property INHERIT it when they do not carry their own, so you set it once here instead of repeating it on every call. It is the ceiling up to which the guest can correct the party size from their own link: with no declared capacity they cannot touch it. Send an empty string (or 0) on PATCH to remove it and go back to having no declared capacity.
communicationLocalestringThe accommodation’s default language: es, en, de, fr, nl, it or pt. Bookings that do not send a locale inherit it, and it decides ALL THREE THINGS at once — the guest form, their contract and the emails they receive — not just the emails. It is normalised (nl-NL → nl) and an unknown value falls back to Spanish WITHOUT an error: read communicationLocale in the response to see which one stuck. Send an empty string on PATCH to unassign it.
houseRulesarrayLEGACY: rules written inside the property, as a list of {es, en} objects. Still accepted — they replace the template’s rules when generating the PDF, they are not appended — but they only apply when no house rules set is assigned at any level. In new integrations, use house rules sets instead.
activebooleanfalse retires the property: it drops out of the listing (unless includeInactive), out of the panel selectors and out of the calendar sweep. Its history is kept and the bookings you already have still resolve to it. Setting it back to true reactivates it.
ses.codigoEstablecimientostringEstablishment code at SES Hospedajes. Without it the report cannot be sent.
ses.tipoPagostringCode from the SES TIPO_PAGO table (e.g. PLATF). Optional: without it, the tipoPago of the assigned SES credential set is used. A tipoPago on an individual booking overrides this one.
ownerIdstringId of an existing owner (from /owners). One owner can be on several properties. It takes precedence over owner.
ownerobject{ legalName, nif, fiscalAddress } — creates the owner inline and links it. An alternative to ownerId.
contractTemplateIdstringId of a template created in /contract-templates. Without it, the tenant default is used.
sesCredentialIdstringId of a credential set created in /ses-credentials. Without it, the tenant default is used.
idstringIf sent and it exists, the call updates instead of creating.
curl
curl -X POST https://api.kinapi.es/api/v1/properties \
  -H "ocp-apim-subscription-key: YOUR_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "nickname": "Sol 3B Apartment",
    "ses": { "codigoEstablecimiento": "0000012345" },
    "owner": { "legalName": "Gestora Costa SL", "nif": "B12345678" }
  }'
201 Created
{
  "id": "b8f3a2c1-…",
  "nickname": "Sol 3B Apartment",
  "listingId": "ALJ-7KM4TQ",
  …
}

Keep the listingId from the response: it is what you send on every check-in. The id is a different thing — the internal identifier of the record, the one that goes in the /properties/{propertyId} URL.

List properties

GET/properties

Returns the active properties in the account — with includeInactive, the retired ones too — each with its id and its listingId. This is how you recover the identifier of a property you created by hand in the panel: add it there, list here, and you have what you need to create its check-ins from your own software. The panel shows it too, under the property name and on its page.

Each property in the list also carries its ownerId (null when no owner is linked), so you can group them by owner without one call per property. The owner’s details (owner) only come when you read a single property.

Query
FieldTypeDescription
includeInactivebooleantrue to include retired properties as well.
curl
curl https://api.kinapi.es/api/v1/properties \
  -H "ocp-apim-subscription-key: YOUR_API_KEY"
200 OK
{
  "results": [
    { "id": "b8f3a2c1-…", "nickname": "Sol 3B Apartment", "listingId": "ALJ-7KM4TQ", … },
    { "id": "5c7d9e01-…", "nickname": "Levante Penthouse", "listingId": "38291", … }
  ]
}

Read a property

GET/properties/{propertyId}

Returns the full record, including the linked owner’s details.

Update a property

PATCH/properties/{propertyId}

Partial update: send only the fields that change. This is how you fix a record that went in wrong — the address, the tourist licence, the internal name, the channel ids, the owner, the template or the credentials — without resending the whole thing. The ses object is merged with the existing one, and an inline owner updates the linked owner.

curl
curl -X PATCH https://api.kinapi.es/api/v1/properties/b8f3a2c1-… \
  -H "ocp-apim-subscription-key: YOUR_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "address": { "street": "Calle del Sol 3", "city": "Tarifa", "province": "Cádiz", "postalCode": "11380" },
    "touristLicense": "VFT/CA/12345",
    "maxGuests": 6,
    "communicationLocale": "nl"
  }'

address is replaced as a whole: unlike ses it is not merged field by field, so send all four fields even when only one changes. The listingId is accepted here too, but changing it orphans the bookings you already have — see the warning above.

An empty string is the instruction to clear, and it works on every optional field: touristLicense, communicationLocale, maxGuests, houseRulesId, contractTemplateId and sesCredentialId. It is what separates «remove it» from «leave it alone», which without it would be the same call.

Changing the language also moves bookings that already exist, if they never declared one of their own. The language is not copied into each booking: it is resolved when the booking is opened, so a pending booking with no locale switches to the new language — form, contract and emails — even though it was created before the change.

Two things do not move, and they are the ones that matter: a booking that does carry its own locale keeps it, and a signed contract is never rewritten — the archived PDF stays in the language it was signed in, which is what makes the signature worth anything.

The same details can be fixed from the panel, on the property page under Property details: address, licence, name, channel ids and the house rules set.

Delete or retire a property

DELETE/properties/{propertyId}

What happens depends on whether the property has bookings, and the response says which in action:

Outcome
FieldTypeDescription
deletedno bookingsNo booking references it, neither through its listingId nor through its channel ids: the record is really deleted and drops out of the listing. This is the property created by mistake, or the throwaway one you made while wiring up the integration.
deactivatedhas bookingsSome booking references it: the record is kept and merely marked active: false, and the response adds reason: "has_bookings". Bookings resolve their accommodation by listingId on every read, so deleting the record would leave them with no establishment code and their reports would fail with nothing pointing at the cause.
200 OK
{
  "ok": true,
  "action": "deactivated",
  "active": false,
  "reason": "has_bookings",
  "propertyId": "b8f3a2c1-…"
}

A deactivated property stops being listed, stops being offered in the panel and stops importing bookings from its calendars, and its history — bookings, contracts, SES reports — is kept intact for auditing. What it does not do is reject check-ins: a booking arriving with its listingId is still created, so you cut the flow by no longer sending them from your PMS.

Import bookings from a calendar (iCal)

Airbnb, Vrbo and most PMSs publish an .ics calendar per property. Paste its address on the property page and Kinapi creates the bookings on its own, once an hour. It is set up from the panel and not through the API: the address is a credential — anyone holding it can see the property’s booked dates — and it should not travel through integrations.

What it does NOT do. The importer only creates bookings: it never modifies or cancels an existing one, and never withdraws a report filed with the Ministry. An event disappearing from a calendar does not mean the booking was cancelled — channels drop past events by design, and a network error looks exactly the same. Date changes and overlapping bookings are flagged on the property page for a person to look at.

And one warning worth reading before you count on it: no calendar carries the traveller’s details. Airbnb gives the booking and the last four digits of the phone; Vrbo, the name; Booking, nothing. What the importer removes is typing every booking by hand — no small thing — not getting the guest to fill in their report.

What can be imported from each channel
FieldTypeDescription
AirbnbyesTells bookings from blocks with no margin for error. Imports the booking and its code.
Vrbo / HomeAwayyesImports the booking and the guest name, which it publishes in the event summary.
Booking.comnoIts calendar marks a booking exactly like an owner block, so there is no way to tell them apart. Nothing is imported: guessing would end in traveller reports for people who do not exist.
Other PMSsdependsImported when the event explicitly says it is a booking (a summary starting with “Reserved”, for instance). Otherwise it is discarded.

Stays that have already ended are not imported, and bookings arriving from a calendar are deduplicated against the ones you already have — from your PMS or from another channel — by comparing property and dates, not identifiers. A booking starting the day the previous one ends is a different booking, and it gets created.

Email notifications, property by property

Kinapi can write to the guest asking them to register, and tell you when a booking is complete. It is decided per property rather than for the whole account: where the channel already sends the link in its welcome message, turning it on would give the guest two identical emails; where the booking is direct, nobody sends it.

The property’s notifications object takes four fields —invitacion, recordatorio, ultimoAviso (the one on the morning of arrival) and completo— and each has three states: absent inherits whatever you set under Settings → Notifications, true turns it on for this property, and false turns it off even when the account has it on. Sending {} clears the property’s own settings and goes back to inheriting.

The three guest emails are independent: if you send the link from your PMS, switch the invitation off and keep the reminder and the last notice, which only reach guests who have not registered yet. Properties configured before this option existed stay as they were —there the reminder went with the invitation— until they are saved again.

Territories Kinapi does not file for

Catalonia and the Basque Country have their own police forces competent for the traveller registry: reports go to the Mossos d’Esquadra and the Ertzaintza, not to SES.Hospedajes. Kinapi does not connect to those systems — in Catalonia’s case there is no web service to connect to: reports are uploaded to their portal by hand.

If you add a property in either of those regions, its page in the panel says so. You can still use Kinapi to collect the guest’s details and for the contract, but filing with the police is on you, and the RD 933/2021 24-hour deadline runs all the same.

Which authority applies is decided by the address’s province, and only by postcode when there is no province. They are not the same thing: the Treviño enclave is a municipality of Burgos — and therefore SES — but carries Álava postcodes. The administrative territory wins.