Guest portal

Besides the registration, the guest link is the guide to their stay: how to get there, how to get in, how the house works, what is around and who to call. The access codes, only once registration is complete.

Until now the link of each booking did one thing: let the guest register and sign. With the portal on, that same link is also the guide to the stay, with your branding and in the guest’s language. There is no second link and no app: whatever your PMS already hands out, or whatever our emails send, leads to the portal.

It comes switched off, on every account. With the portal off the link works exactly as it did: the content is not even read. You can fill it in and preview it (“View as guest”, on each property) before switching it on, and it is included in the price: what you pay per filed booking does not change.

What the guest sees

The portal is split into four sections, with an icon bar at the bottom on mobile and a side menu on desktop. Only the sections with content show up, and the section travels in the link (#casa), so a message can point straight at one of them.

The four sections
FieldTypeDescription
StayalwaysThe usual: dates and times, the registration, the signature and the guests in the party.
The housealways · access behind a lockIn the order the guest walks through it: how to get there (with a “House address” button that opens their maps app), where to park (with its map), how to get in — instructions, codes and wifi, behind the lock —, how the house works by topic, the rules they signed in the contract and what to do when leaving.
The areaalwaysYour recommendations by category — restaurants, cafés, supermarket, pharmacy, beach, sights, transport, leisure —, each with its description per language and a button to the map.
HelpalwaysYour phone (the property’s or the account’s), WhatsApp, 112 and the useful numbers you add: taxi, health centre, duty pharmacy, local police, repairs.

Every text goes per language: the guest reads theirs, failing that Spanish, failing that whatever exists. Categories, manual topics and phone types are translated on their own; proper names (a restaurant, a taxi company) are not.

When the codes open

The access instructions, the codes and the wifi sit behind a lock that opens when two conditions hold at once:

FieldTypeDescription
Registration completeconditionEvery guest registered and the contract signed. It is exactly the condition that fires the checkin.completed webhook, so the portal and your PMS never disagree.
Inside the windowconditionFrom N hours before the check-in time (48 by default, 1 to 168, set per account) until the end of the check-out day. Less than an hour leaves no room for an early guest; more than a week leaves the codes exposed in a forwarded link for too long.

The codes do not travel with the page: the guest taps “Show the codes” and they are fetched at that moment. Each time is logged — when, from which network (truncated: last octet removed) and why it was allowed — for 90 days, and you can read it over the API. What that log never stores is the codes themselves.

Unlock now. The guest is at the door and has not signed: a button on the booking page — and an API call — opens the access without waiting for the registration or the window. Who opened it and when is recorded, it still closes at the end of the check-out day, and it can be undone.

Access state of a booking
FieldTypeDescription
pendingclosedSomeone still has to register or sign.
tooEarlyclosedRegistration complete, but it is too early: it opens at opensAt.
openopenThe codes are visible, until closesAt. openedBy says whether it was the registration or a manual unlock.
endedclosedThe check-out day is over.

If you turn it on, when the access opens the guest gets an “all set for your arrival” email with the link to the portal. Never with the codes: an email gets forwarded; the portal, with its log of who looked, does not.

Switching it on and tuning it

GET/settings
PATCH/settings

In the panel, Guest portal → Settings. Over the API, the guestPortal object of /settings. Like every other setting, only what you send changes.

guestPortal
FieldTypeDescription
guestPortal.enabledbooleanTurn the portal on or off for the account. Off, the guest link works as it always did.
guestPortal.hoursBeforeinteger 1-168From how many hours before check-in the codes and the wifi can be seen. Default 48.
guestPortal.phonestring | nullContact phone of the account, the one the guest sees under Help. Each property can have its own. null removes it.
guestPortal.readyEmailboolean“All set for your arrival” email with the link (never the codes) when the access becomes visible.
curl
curl -X PATCH https://api.kinapi.es/api/v1/settings \
  -H "ocp-apim-subscription-key: YOUR_API_KEY" \
  -H "content-type: application/json" \
  -d '{"guestPortal":{"enabled":true,"hoursBefore":24,"phone":"+34600000000"}}'

Common content, blocks and own content

Fifty properties are not filled in one by one. Seven sections of the portal can be shared — arrival, parking, the house manual, the house rules, the area, departure and the useful numbers — and they are written in three places:

FieldTypeDescription
Common contentaccountWritten once, it applies to every property that does not say otherwise. House rules, departure or useful numbers are usually the same across the whole portfolio.
A blocknamed variantA version of one section with a name of its own — “Alameda car park”, “City centre” — assigned to the properties that use it. A building with six flats has one car park: write it once, assign it six times.
Own contentpropertyWhat belongs to that flat. And what can only be its own, because it is not shared: the address (the house map), the access, the codes, the wifi and the phone.

For each section of each property you decide where it comes from, and the rule is always the same:

Where each section comes from (origins)
FieldTypeDescription
hidden1stThe property hides the section: nothing is shown, even if the common content has it.
block2ndThe property uses a block (blockId): the block is shown, if it has content.
own3rdThe property has its own content for the section: its own is shown.
common4thNone of the above: the common content of the account is shown.
empty5thNothing anywhere: the section does not appear.

The house manual is the exception: it is composed topic by topic. The property writes heating and rubbish, the common content has kitchen and washing machine, and the guest sees all four; a topic present in both comes out with the property’s text.

In the panel, the Guest portal entry of the menu has three tabs: Properties (a table with the state of every section on every property — ✓ own, ≡ common, ⧉ block — and bulk assignment of a variant to several properties), Common and Settings. On each property, every shared section has its selector — common, a variant, own or hidden —, and the page has “Copy from another property” (everything but codes and wifi) and “Previous / Next” to walk the portfolio without going back to the list.

The common content of the account

GET/portal/common
PUT/portal/common

The PUT replaces the whole common content: a section you leave out is removed. Read, change, write. The read returns only the sections with content.

The shared sections (common content, a block and the property accept the same ones)
FieldTypeDescription
arrivaltext per languageHow to get there: entrance, floor, door, what to look for. An object with one text per language (es, en, de, fr, nl, it, pt); line breaks are kept and https links become links.
parkingtext per languageWhere to leave the car.
parkingMapUrlstringhttps link to any map service (Google Maps, Apple Maps, Waze…). The guest gets a button that opens it in their app.
manualobjectHow the house works, by fixed topic: climatizacion, cocina, aguaCaliente, lavadora, basura, tv, exteriores, otros. Each topic, one text per language. Topic titles are translated on their own; a topic you leave out is not shown.
showHouseRulesbooleanShow in the portal the house rules the guest signs in the contract (the booking’s, else the property’s, else the account default). They are not written here: they already exist.
areaobject{ enabled, places }. enabled: false hides the section without deleting the places. Each place: category (restaurante, cafe, supermercado, farmacia, playa, visita, transporte, ocio, otro), name, mapUrl and description per language.
departuretext per languageWhat to do before leaving and where to leave the keys.
usefulNumbersarrayUseful numbers, below yours and 112: type (taxi, centroSalud, farmacia, policia, mantenimiento, otro), optional name (“Radio Taxi Tarifa”) and number.
curl
curl -X PUT https://api.kinapi.es/api/v1/portal/common \
  -H "ocp-apim-subscription-key: YOUR_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "departure": { "es": "Deja las llaves en la caja y cierra la puerta al salir.", "en": "Leave the keys in the box and shut the door behind you." },
    "showHouseRules": true,
    "usefulNumbers": [
      { "type": "taxi", "name": "Radio Taxi", "number": "+34 956 000 000" },
      { "type": "centroSalud", "number": "+34 956 111 111" }
    ]
  }'

Blocks

GET/portal/blocks
POST/portal/blocks
GET/portal/blocks/{id}
PATCH/portal/blocks/{id}
DELETE/portal/blocks/{id}
Body
FieldTypeDescription
sectionrequiredstringWhich section it is a variant of: arrival, parking, manual, houseRules, area, departure or usefulNumbers. It cannot change afterwards. POST only.
namerequiredstringYour name for the block (“Alameda car park”). The guest never sees it. Up to 60 characters.
contentobjectThe same fields as the common content; only those of the block’s section are kept. On PATCH it replaces the content.
propertyIdsstring[]The properties that use this block instead of the common or their own content. On PATCH it is the full list: a property left out stops using it and goes back to its own or the common content.
curl
curl -X POST https://api.kinapi.es/api/v1/portal/blocks \
  -H "ocp-apim-subscription-key: YOUR_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "section": "parking",
    "name": "Alameda car park",
    "content": {
      "parking": { "es": "Plaza 14 del parking de la Alameda, a 200 m. El mando abre la barrera.", "en": "Bay 14 at the Alameda car park, 200 m away. The remote opens the barrier." },
      "parkingMapUrl": "https://maps.app.goo.gl/…"
    },
    "propertyIds": ["b8f3a2c1-…", "5c7d9e01-…"]
  }'
201 Created
{
  "id": "3f9c…",
  "section": "parking",
  "name": "Alameda car park",
  "content": { "parking": { "es": "…", "en": "…" }, "parkingMapUrl": "https://maps.app.goo.gl/…" },
  "propertyIds": ["b8f3a2c1-…", "5c7d9e01-…"],
  "updatedAt": "2026-10-23T10:00:00.000Z"
}

The id is what goes into a property’s origins as block:3f9c…. Deleting a block sends its properties back to their own or the common content; nothing of theirs is deleted.

The portal of a property

GET/properties/{propertyId}/portal
PUT/properties/{propertyId}/portal

What belongs to each accommodation: the shared sections above, plus what can only be its own and where each section comes from. The PUT replaces the whole portal, codes and wifi included: what you leave out is removed.

Body (besides the shared sections)
FieldTypeDescription
houseMapUrlstringMap link to the house. The guest sees a “House address” button.
accesstext per languageHow to get in: where the key box is, what each code opens. Shown only once registration is complete and inside the window.
codesarrayThe codes, encrypted at rest: type (portal, puerta, cajaLlaves, garaje, otro) and value as typed on the lock. Behind the “Show the codes” button, and every reveal is logged.
wifiobject{ network, password }. Without password, open network. Shown with the codes.
phonestringContact phone of this property. Without it, the account’s (guestPortal.phone).
originsobjectPer shared section, where it comes from on this property: common, own, hidden or block:<id>. Left out: own if the property brings content for that section, common otherwise. With common or a block, the own content you send for that section is discarded.
curl
curl -X PUT https://api.kinapi.es/api/v1/properties/b8f3a2c1-…/portal \
  -H "ocp-apim-subscription-key: YOUR_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "houseMapUrl": "https://maps.app.goo.gl/…",
    "arrival": { "es": "Portal verde junto a la farmacia. 2.º izquierda.", "en": "Green street door next to the pharmacy. 2nd floor, left." },
    "access": { "en": "The key box is to the right of the street door; the door code opens the garage too." },
    "codes": [ { "type": "portal", "value": "4821" }, { "type": "cajaLlaves", "value": "0719" } ],
    "wifi": { "network": "Sol3B", "password": "tarifa-2026" },
    "manual": { "climatizacion": { "en": "The thermostat is in the hallway; 21 ºC is plenty." } },
    "origins": { "parking": "block:3f9c…", "area": "hidden" }
  }'
200 OK
{
  "propertyId": "b8f3a2c1-…",
  "houseMapUrl": "https://maps.app.goo.gl/…",
  "arrival": { "es": "…", "en": "…" },
  "access": { "en": "…" },
  "codes": [ { "type": "portal", "value": "4821" }, { "type": "cajaLlaves", "value": "0719" } ],
  "wifi": { "network": "Sol3B", "password": "tarifa-2026" },
  "manual": { "climatizacion": { "en": "…" } },
  "origins": {
    "arrival": { "source": "own" },
    "parking": { "source": "block", "blockId": "3f9c…" },
    "manual": { "source": "own" },
    "houseRules": { "source": "common" },
    "area": { "source": "hidden" },
    "departure": { "source": "common" },
    "usefulNumbers": { "source": "common" }
  },
  "updatedAt": "2026-10-23T10:05:00.000Z"
}

The codes come in clear here, and only here: they open the house. They never appear in lists, the response carries cache-control: no-store, and your integration should treat them like a password. The fields above are the own content; origins says what the guest sees after resolving blocks and the common content, and manual is composed topic by topic with the common one.

The access of a booking

GET/checkins/{reservationId}/portal
POST/checkins/{reservationId}/portal/unlock
DELETE/checkins/{reservationId}/portal/unlock

Whether the guest of a booking can already see the codes, since or until when, whether someone unlocked it by hand and when they looked. The POST is “Unlock now” — signed api: plus the label of your key — and the DELETE undoes it. All three return the same object; never the codes, which belong to the property.

200 OK
{
  "enabled": true,
  "state": "open",
  "closesAt": "2026-08-18T21:59:59.999Z",
  "openedBy": "unlock",
  "unlocked": { "at": "2026-08-14T13:02:11.000Z", "by": "api:PMS" },
  "reveals": [
    { "at": "2026-08-14T15:40:02.000Z", "network": "83.45.12.0", "reason": "unlock" }
  ]
}

A cancelled booking cannot be unlocked: 409 cancelled. There are no new webhook events: checkin.completed is still the moment the access opens, if the booking is already inside the window.

Check-in and check-out times

The code window and what the guest reads under Stay depend on the check-in and check-out times of the booking, and those resolve in a cascade: the booking → the property → the account → 16:00 / 11:00. A booking that brings its time keeps it; one that does not inherits the property’s (checkInTime and checkOutTime on /properties), and if the property has none, the account’s (stay on /settings).

Changing a property’s or the account’s time moves the future bookings that inherited it and have not been filed yet, the same as from the panel. Those that brought their own are not touched.

From an agent

The MCP server has one tool per operation above, to configure the portal by talking:

FieldTypeDescription
kinapi_get_property_portal · kinapi_set_property_portalpropertyThe portal of a property, with where each section comes from; and replacing it.
kinapi_get_portal_common · kinapi_set_portal_commonaccountThe common content, and replacing it.
kinapi_list_portal_blocks · kinapi_create_portal_block · kinapi_update_portal_block · kinapi_delete_portal_blockblocksThe named variants and which properties they apply to.
kinapi_get_checkin_access · kinapi_unlock_checkin_access · kinapi_lock_checkin_accessbookingWhether the guest can already see the codes, “Unlock now” and undoing it.
kinapi_update_settingsaccountSwitch the portal on, the window, the phone, the “all set” email and the default times.
Things you can ask for
"Put in the common content that the keys go in the box, and show the house rules"
"Create a parking block called 'Alameda car park' with this text and assign it to the six flats on Alameda street"
"Has the guest of booking 4821 seen the codes yet?"
"The guest of 4821 is at the door and has not signed: open the access for them"