{
  "openapi": "3.0.3",
  "info": {
    "title": "Kinapi API",
    "version": "1.0.0",
    "description": "Traveller registration for SES Hospedajes (Spain, Royal Decree 933/2021), as an API.\n\nCreate a check-in per booking, share the guest link, and Kinapi does the rest: the guests register and sign from their phone, the traveller report is filed with the Ministry on the night of arrival, and the signed contract is kept for three years with an external timestamp.\n\nEvery call needs an API key, issued from the panel (Settings → API), in the `ocp-apim-subscription-key` header. Identifiers of the API (nombre, apellido1, tipoPago…) are contract and are not translated; texts returned in `details` are in Spanish.\n\n**Test mode.** Keys starting with `kinapi_test_` belong to your account's test environment, created from the panel (account menu → Switch to test mode). Same API, same webhooks, separate data: filing is answered by a simulator inside Kinapi, so nothing reaches the Ministry, nothing is billed and emails only reach your team. A guest whose first surname is RECHAZO is rejected; an establishment code RECHAZO rejects the whole submission. `GET /settings` returns `livemode: false` and every webhook carries `livemode: false`. Guide: https://www.kinapi.es/en/docs/testing",
    "contact": {
      "name": "Kinapi",
      "url": "https://www.kinapi.es/contacto",
      "email": "gestion@kinapi.es"
    },
    "termsOfService": "https://www.kinapi.es/terminos",
    "license": {
      "name": "Proprietary",
      "url": "https://www.kinapi.es/terminos"
    }
  },
  "externalDocs": {
    "description": "Guides and reference",
    "url": "https://www.kinapi.es/en/docs"
  },
  "servers": [
    {
      "url": "https://api.kinapi.es/api/v1",
      "description": "Production. There is no sandbox: SES Hospedajes has none either."
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Check-ins",
      "description": "One per booking. The public id is your reservationId."
    },
    {
      "name": "Guests",
      "description": "Travellers registered on a check-in."
    },
    {
      "name": "SES Hospedajes",
      "description": "What the Ministry answered, filing on demand, and the official catalogues."
    },
    {
      "name": "Properties",
      "description": "Your accommodations and their SES establishment code."
    },
    {
      "name": "Owners",
      "description": "Landlords printed on the contract. One owner can be on several properties."
    },
    {
      "name": "Contract templates",
      "description": "The contract terms, bilingual clause by clause or as a pasted document."
    },
    {
      "name": "House rules",
      "description": "Rules per accommodation, combinable with any contract template."
    },
    {
      "name": "SES credentials",
      "description": "Web service credentials of each landlord at SES Hospedajes."
    },
    {
      "name": "Settings",
      "description": "Account behaviour and notices to you."
    },
    {
      "name": "Branding",
      "description": "Colour and logo of the guest form."
    },
    {
      "name": "Webhook",
      "description": "Outgoing events to your system."
    },
    {
      "name": "Usage and billing",
      "description": "What you will be charged for, and the billing portal."
    },
    {
      "name": "Exports",
      "description": "A ZIP with the records an accommodation owner must keep: reports, proofs of filing, contracts and signature evidence."
    }
  ],
  "paths": {
    "/checkins": {
      "get": {
        "tags": [
          "Check-ins"
        ],
        "summary": "List check-ins",
        "description": "Paginated, newest arrival first. Filter by arrival date, status or property.",
        "operationId": "listCheckins",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Arrival date from, inclusive (YYYY-MM-DD).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Arrival date to, inclusive (YYYY-MM-DD).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only check-ins in this status.",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "in_progress",
                "completed",
                "signed",
                "submitted",
                "confirmed",
                "error",
                "cancelled"
              ]
            }
          },
          {
            "name": "listingId",
            "in": "query",
            "required": false,
            "description": "Only check-ins of this property.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ownerId",
            "in": "query",
            "required": false,
            "description": "Only check-ins of the properties of this owner (id from /owners), including deactivated properties and their channel listing ids.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1 to 200. Default 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Pagination offset.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of check-ins.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckinListResponse"
                }
              }
            }
          },
          "400": {
            "description": "validation: status is not one of the known values, or ownerId does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Check-ins"
        ],
        "summary": "Create a check-in",
        "description": "One per booking. Returns the guest link to share. Idempotent: if a check-in with this reservationId already exists, it answers 200 with the existing one (created: false) and nothing is modified — to change it, use PATCH.",
        "operationId": "createCheckin",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCheckinRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "It already existed: the existing check-in, untouched (created: false).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckinCreatedResponse"
                }
              }
            }
          },
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckinCreatedResponse"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "payment_method_required: the free trial bookings are used up. Add a card in the panel (Settings → Billing); nothing already filed is affected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "quota_exceeded: daily check-in creation quota reached. It resets at midnight UTC.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal: retry; if it persists, get in touch.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/checkins/{reservationId}": {
      "get": {
        "tags": [
          "Check-ins"
        ],
        "summary": "Read a check-in",
        "description": "The check-in with its registered guests.",
        "operationId": "getCheckin",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The check-in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckinWithGuests"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no check-in with that reservationId in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Check-ins"
        ],
        "summary": "Update a check-in",
        "description": "Only the fields you send change; the rest stays as it is. Changing the dates of a booking whose report was already filed annuls that filing with SES Hospedajes and files it again with the new dates — straight away if the new arrival is today or in the past, otherwise in the nightly submission of the new arrival date. It annuls first and saves only if the Ministry accepts: on failure nothing changes anywhere and the response says why.",
        "operationId": "updateCheckin",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCheckinRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated. refiling is present only when the dates of a filed booking changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckinUpdatedResponse"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no check-in with that reservationId in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "refiling_blocked, code en_vuelo: the report is sent and SES has not confirmed it yet, so it cannot be annulled. Nothing was saved; retry in an hour.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "refiling_blocked, code anulacion_fallida: SES did not accept the annulment or did not answer. Nothing was saved; retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Check-ins"
        ],
        "summary": "Delete or cancel a check-in",
        "description": "Deletes the booking if it left no trace worth keeping (nobody signed, nothing was filed, nothing was billed); otherwise cancels it, keeps everything and annuls any report already filed with SES Hospedajes (Article 6.3 of RD 933/2021). action says which happened. A deleted booking disappears from the listing; a cancelled one stays with status cancelled.",
        "operationId": "deleteCheckin",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted or cancelled: see action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckinDeleteResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no check-in with that reservationId in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/checkins/{reservationId}/guests": {
      "get": {
        "tags": [
          "Guests"
        ],
        "summary": "List the guests of a check-in",
        "operationId": "listGuests",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Registered guests, in registration order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuestListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no check-in with that reservationId in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Guests"
        ],
        "summary": "Register a guest",
        "description": "For systems that already hold the traveller details. Same validation as the public form: minors, Spanish documents and addresses follow the rules described on each field. It does NOT sign: the contract still needs each guest to sign from the link, and if the account requires signature before filing, the report waits for it.",
        "operationId": "registerGuest",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterGuestRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuestRegisteredResponse"
                }
              }
            }
          },
          "400": {
            "description": "validation with issues: one entry per invalid field, message being a translation key of the guest form. Or invalid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no check-in with that reservationId in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "full: the booking already has as many guests as confirmed places. cancelled: the check-in is cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/checkins/{reservationId}/guests/{guestId}": {
      "get": {
        "tags": [
          "Guests"
        ],
        "summary": "Read a guest",
        "operationId": "getGuest",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "guestId",
            "in": "path",
            "required": true,
            "description": "Guest id, from the guests array of the check-in.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The guest.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Guest"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no such check-in, or no such guest in it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Guests"
        ],
        "summary": "Remove a guest",
        "description": "Refused once the report has been filed with SES: what was reported to the police is not undone by deleting here.",
        "operationId": "deleteGuest",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "guestId",
            "in": "path",
            "required": true,
            "description": "Guest id, from the guests array of the check-in.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuestDeletedResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no such check-in, or no such guest in it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "already_submitted: the report was filed with SES; the guest cannot be removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/checkins/{reservationId}/ses": {
      "get": {
        "tags": [
          "SES Hospedajes"
        ],
        "summary": "Status of the traveller report at SES Hospedajes",
        "description": "Every batch sent for this booking, with the Ministry’s answer per guest: communication code, or the rejection verbatim plus Kinapi’s reading of it, and the annulment of the batch when the booking was cancelled or its dates changed. The same in PDF: GET …/ses/justificante.",
        "operationId": "getSesStatus",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Status and batches.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SesStatusResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no check-in with that reservationId in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "SES Hospedajes"
        ],
        "summary": "File the report now",
        "description": "Sends the report to SES Hospedajes immediately instead of waiting for the nightly submission (the panel’s “Send manually”). SES does not confirm on the spot: it returns a batch number and the per-guest result arrives with the hourly poll. In test mode the simulated answer is applied at once.",
        "operationId": "submitToSes",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sent; confirmation pending.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SesSubmitNowResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no check-in with that reservationId in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "not_ready: it cannot be sent yet — no guests, no signature where the account requires it, or the property has no establishment code. details says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "ses_error: SES Hospedajes rejected the submission or did not answer. details has the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/checkins/{reservationId}/ses/justificante": {
      "get": {
        "tags": [
          "SES Hospedajes"
        ],
        "summary": "Download the proof of filing (PDF)",
        "description": "A PDF to show in an inspection: establishment, stay and, for every batch, its number, date and the communication code of each guest, plus any annulment and its result. The Ministry issues no PDF for web-service filings: the communication code is the official record, and this document gathers them with the stay as it stands in Kinapi. It is generated on request, so it always reflects the current state.",
        "operationId": "downloadSesReceipt",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The proof of filing.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no check-in with that reservationId in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/checkins/{reservationId}/ses/simulate": {
      "post": {
        "tags": [
          "Test mode"
        ],
        "summary": "Simulate the Ministry’s answer (test mode)",
        "description": "Test mode only. Files the report now and makes the simulated Ministry answer what you ask: accepted, any rejection of the catalogue, a whole-submission error, or a hold for non-payment. The webhooks go out exactly as they would live. It needs the same as a real submission: registered guests not yet filed, an establishment code and, if the account requires it, the signed contract.",
        "operationId": "simulateSes",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SimulateSesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The simulated answer was applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SimulateSesResponse"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "test_mode_only: this endpoint only works with a test-mode key (kinapi_test_…). Also tenant_suspended: the account is suspended for non-payment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no check-in with that reservationId in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "not_ready: it cannot be filed yet — no guests pending, no signature where the account requires it, or the property has no establishment code. details says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/checkins/{reservationId}/contract": {
      "get": {
        "tags": [
          "Check-ins"
        ],
        "summary": "Download the signed contract",
        "description": "The PDF with every signature and its evidence page. Available once the whole group has signed. The PDF itself carries no timestamp: its SHA-256 hash is timestamped by an external authority (RFC 3161), and that token plus the signature record are available from GET /checkins/{reservationId}/evidence.",
        "operationId": "downloadContract",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The signed contract.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found, or \"not signed yet\" while the contract is not complete.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/checkins/{reservationId}/evidence": {
      "get": {
        "tags": [
          "Check-ins"
        ],
        "summary": "Signature evidence of the contract",
        "description": "What lets you prove, without Kinapi, what was signed and when: the SHA-256 of the signed PDF, its RFC 3161 timestamp (verified on receipt: hash and nonce), and per signer the time, IP, device and signature hash. The PDF and the timestamp token are also archived in immutable storage for three years. Download the token from …/evidence/{evidenceId}/token and verify it with openssl ts.",
        "operationId": "listEvidence",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Evidence records, most recent first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EvidenceListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no check-in with that reservationId in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/checkins/{reservationId}/evidence/{evidenceId}/token": {
      "get": {
        "tags": [
          "Check-ins"
        ],
        "summary": "Download the RFC 3161 timestamp token",
        "description": "The TimeStampResp exactly as the timestamping authority (DigiCert) issued it, DER encoded, with the authority certificate inside. Verify it against the PDF with: openssl ts -verify -digest <sha256 of the PDF> -in token.tsr -CAfile <system CA bundle>.",
        "operationId": "downloadTimestampToken",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "evidenceId",
            "in": "path",
            "required": true,
            "description": "Evidence record id, from GET /checkins/{reservationId}/evidence.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The token (.tsr).",
            "content": {
              "application/timestamp-reply": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no such check-in or evidence record. no_timestamp: the record has no timestamp yet (details says why; it is retried hourly).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/checkins/{reservationId}/parte": {
      "get": {
        "tags": [
          "Check-ins"
        ],
        "summary": "Download the traveller report (parte de entrada)",
        "description": "The document RD 933/2021 requires you to keep and show. Without guestId, a ZIP with one PDF per guest; with guestId, that guest’s PDF.",
        "operationId": "downloadReport",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "description": "Your booking identifier: the reservationId you sent when creating the check-in.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "guestId",
            "in": "query",
            "required": false,
            "description": "Only this guest’s report, as a PDF.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ZIP of PDFs, or a single PDF when guestId is given.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found, or no_guests when nobody has registered yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/properties": {
      "get": {
        "tags": [
          "Properties"
        ],
        "summary": "List properties",
        "operationId": "listProperties",
        "parameters": [
          {
            "name": "includeInactive",
            "in": "query",
            "required": false,
            "description": "true to include retired properties as well.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Your properties (active ones by default).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PropertyListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Properties"
        ],
        "summary": "Create a property",
        "description": "Creates a property, or updates one if you send its id. The owner can be linked by ownerId or created inline with owner.",
        "operationId": "createProperty",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePropertyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated (an id was sent).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Property"
                }
              }
            }
          },
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Property"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON. Also when ownerId does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/properties/{propertyId}": {
      "get": {
        "tags": [
          "Properties"
        ],
        "summary": "Read a property",
        "operationId": "getProperty",
        "parameters": [
          {
            "name": "propertyId",
            "in": "path",
            "required": true,
            "description": "Property id (the id field of the property, not its listingId).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The property, with its owner when it has one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Property"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Properties"
        ],
        "summary": "Update a property",
        "description": "Only the fields you send change; the rest stays as it is.",
        "operationId": "updateProperty",
        "parameters": [
          {
            "name": "propertyId",
            "in": "path",
            "required": true,
            "description": "Property id (the id field of the property, not its listingId).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePropertyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Property"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON. Also when ownerId does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Properties"
        ],
        "summary": "Delete or retire a property",
        "description": "Really deletes it if no booking references it; otherwise retires it (active: false) and keeps the record, because existing bookings resolve their establishment code through it. action says which happened.",
        "operationId": "deleteProperty",
        "parameters": [
          {
            "name": "propertyId",
            "in": "path",
            "required": true,
            "description": "Property id (the id field of the property, not its listingId).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted or deactivated: see action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PropertyDeleteResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/owners": {
      "get": {
        "tags": [
          "Owners"
        ],
        "summary": "List owners",
        "operationId": "listOwners",
        "responses": {
          "200": {
            "description": "Your owners. One owner can be linked to several properties.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OwnerListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Owners"
        ],
        "summary": "Create an owner",
        "operationId": "createOwner",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOwnerRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. Link it to properties with ownerId.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Owner"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/owners/{id}": {
      "get": {
        "tags": [
          "Owners"
        ],
        "summary": "Read an owner",
        "operationId": "getOwner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The owner.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Owner"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Owners"
        ],
        "summary": "Update an owner",
        "description": "Only the fields you send change; the rest stays as it is. The change shows on every property linked to this owner.",
        "operationId": "updateOwner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateOwnerRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Owner"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Owners"
        ],
        "summary": "Delete an owner",
        "description": "Refused while any property still links to it: reassign those properties first.",
        "operationId": "deleteOwner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "owner_in_use: the owner is still linked to one or more properties.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/contract-templates": {
      "get": {
        "tags": [
          "Contract templates"
        ],
        "summary": "List contract templates",
        "operationId": "listContractTemplates",
        "responses": {
          "200": {
            "description": "Your templates.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContractTemplateListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Contract templates"
        ],
        "summary": "Create a contract template",
        "description": "Omitted clauses are filled with Kinapi’s standard ones. Assign the template from a property or a booking with contractTemplateId.",
        "operationId": "createContractTemplate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateContractTemplateRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContractTemplate"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON. Also when bodyMode is document and body.es is empty.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/contract-templates/{id}": {
      "get": {
        "tags": [
          "Contract templates"
        ],
        "summary": "Read a contract template",
        "operationId": "getContractTemplate",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The template.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContractTemplate"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Contract templates"
        ],
        "summary": "Update a contract template",
        "description": "Only the fields you send change; the rest stays as it is. Contracts already signed keep the text they were signed with.",
        "operationId": "updateContractTemplate",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateContractTemplateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContractTemplate"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON. Also when bodyMode is document and body.es is empty.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Contract templates"
        ],
        "summary": "Delete a contract template",
        "description": "Properties and bookings that had it assigned fall back to the account default.",
        "operationId": "deleteContractTemplate",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/contract-templates/{id}/preview": {
      "post": {
        "tags": [
          "Contract templates"
        ],
        "summary": "Preview a contract template as PDF",
        "description": "A sample contract with this template’s clauses and options over fictitious data, to check how it looks without creating a booking or waiting for a signature.",
        "operationId": "previewContractTemplate",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "locale",
            "in": "query",
            "required": false,
            "description": "Language to render the sample in (es, en, de, fr, nl, it, pt). Default es.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The sample contract.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/house-rules": {
      "get": {
        "tags": [
          "House rules"
        ],
        "summary": "List house rules sets",
        "operationId": "listHouseRules",
        "responses": {
          "200": {
            "description": "Your house rules sets.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HouseRulesListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "House rules"
        ],
        "summary": "Create a house rules set",
        "description": "Creates a set, or updates one if you send its id. Assign it from a property or a booking with houseRulesId; it combines with any contract template.",
        "operationId": "createHouseRules",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateHouseRulesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated (an id was sent).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HouseRules"
                }
              }
            }
          },
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HouseRules"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON. Also when bodyMode is document and body.es is empty.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/house-rules/{id}": {
      "get": {
        "tags": [
          "House rules"
        ],
        "summary": "Read a house rules set",
        "operationId": "getHouseRules",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HouseRules"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "House rules"
        ],
        "summary": "Update a house rules set",
        "description": "Only the fields you send change; the rest stays as it is.",
        "operationId": "updateHouseRules",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateHouseRulesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HouseRules"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON. Also when bodyMode is document and body.es is empty.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "House rules"
        ],
        "summary": "Delete a house rules set",
        "description": "Allowed even if properties or bookings reference it: they fall back to the next set in the chain (property, then account default). Deleting the default set changes what properties without a set of their own print.",
        "operationId": "deleteHouseRules",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ses-credentials": {
      "get": {
        "tags": [
          "SES credentials"
        ],
        "summary": "List SES credential sets",
        "operationId": "listSesCredentials",
        "responses": {
          "200": {
            "description": "Your credential sets, without passwords.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SesCredentialListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "SES credentials"
        ],
        "summary": "Create a SES credential set",
        "description": "One set per landlord. Test it right after with POST /ses-credentials/{id}/test: SES has no sandbox, and a wrong password shows up the first night a report goes out.",
        "operationId": "createSesCredential",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSesCredentialRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SesCredential"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ses-credentials/{id}": {
      "get": {
        "tags": [
          "SES credentials"
        ],
        "summary": "Read a SES credential set",
        "operationId": "getSesCredential",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The set, without its password.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SesCredential"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "SES credentials"
        ],
        "summary": "Update a SES credential set",
        "description": "Only the fields you send change; the rest stays as it is.",
        "operationId": "updateSesCredential",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSesCredentialRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SesCredential"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "SES credentials"
        ],
        "summary": "Delete a SES credential set",
        "description": "Properties that had it assigned fall back to the account default.",
        "operationId": "deleteSesCredential",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OkResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ses-credentials/{id}/test": {
      "post": {
        "tags": [
          "SES credentials"
        ],
        "summary": "Test a SES credential set against SES Hospedajes",
        "description": "Makes a real, harmless call (the catalogue operation) with these credentials and reports whether they got in. Covers user, password, endpoint and TLS at once. The result is in the body: ok is false when SES rejected them, with the reason in details.",
        "operationId": "testSesCredential",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Test result, whether it passed or not.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SesCredentialTestResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ses/catalogs": {
      "get": {
        "tags": [
          "SES Hospedajes"
        ],
        "summary": "Official SES catalogues",
        "description": "Document types, sex, relationship and payment methods with the codes SES accepts today, as cached by Kinapi. Build your dropdowns from here instead of hard-coding the codes.",
        "operationId": "getSesCatalogs",
        "responses": {
          "200": {
            "description": "The four catalogues; null for one never refreshed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SesCatalogsResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "SES Hospedajes"
        ],
        "summary": "Refresh the catalogues from SES Hospedajes",
        "description": "Fetches the four catalogues from the Ministry with your default credentials and updates the cache.",
        "operationId": "refreshSesCatalogs",
        "responses": {
          "200": {
            "description": "Which catalogues were refreshed and which failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SesCatalogsRefreshedResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "ses_error: no catalogue could be refreshed; check the account’s SES credentials. failed lists them.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/settings": {
      "get": {
        "tags": [
          "Settings"
        ],
        "summary": "Read the account settings",
        "operationId": "getSettings",
        "responses": {
          "200": {
            "description": "Current settings. Secrets are never returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Settings"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Settings"
        ],
        "summary": "Update the account settings",
        "description": "Only the fields you send change; the rest stays as it is.",
        "operationId": "updateSettings",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSettingsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Settings"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON. Also when telegram is sent without botToken and none is stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/branding": {
      "get": {
        "tags": [
          "Branding"
        ],
        "summary": "Read the guest form branding",
        "operationId": "getBranding",
        "responses": {
          "200": {
            "description": "Colour, background and logo of the guest form.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Branding"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Branding"
        ],
        "summary": "Update the guest form branding",
        "description": "Only the fields you send change; the rest stays as it is.",
        "operationId": "updateBranding",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBrandingRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Branding"
                }
              }
            }
          },
          "400": {
            "description": "validation: colour not in #RRGGBB format, logo not valid base64, empty or over 1 MB, or not PNG/JPEG/WebP.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Branding"
        ],
        "summary": "Reset the branding to the Kinapi default",
        "operationId": "resetBranding",
        "responses": {
          "204": {
            "description": "Reset. No content."
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhook": {
      "get": {
        "tags": [
          "Webhook"
        ],
        "summary": "Read the outgoing webhook",
        "description": "There is exactly one webhook per account. The response also lists the events you can subscribe to.",
        "operationId": "getWebhook",
        "parameters": [
          {
            "name": "locale",
            "in": "query",
            "required": false,
            "description": "Language of the event labels and descriptions: es or en. Default: Accept-Language, then es.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current configuration (null if none) and the event catalogue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Webhook"
        ],
        "summary": "Create or replace the outgoing webhook",
        "description": "Every delivery is a POST with the JSON body signed in the x-kinapi-signature-sha256 header (HMAC-SHA256 with the secret). Failed deliveries are retried three times.",
        "operationId": "putWebhook",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PutWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSavedResponse"
                }
              }
            }
          },
          "400": {
            "description": "validation, or invalid_url: the URL is not https or does not resolve to a public address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Webhook"
        ],
        "summary": "Remove the outgoing webhook",
        "operationId": "deleteWebhook",
        "responses": {
          "204": {
            "description": "Removed; nothing is emitted any more. No content."
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhook/test": {
      "post": {
        "tags": [
          "Webhook"
        ],
        "summary": "Send a test delivery",
        "description": "Emits a test event to your URL and returns the real result of the attempt, so you can debug the receiver without waiting for a real event.",
        "operationId": "testWebhook",
        "responses": {
          "200": {
            "description": "Result of the attempt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookTestResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "not_configured: no webhook yet (use PUT /webhook first). disabled: the webhook is switched off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhook/deliveries": {
      "get": {
        "tags": [
          "Webhook"
        ],
        "summary": "Recent webhook deliveries",
        "description": "The last attempts with their status and the first bytes of your response. Kept for 30 days.",
        "operationId": "listWebhookDeliveries",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1 to 200. Default 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Most recent first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveriesResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/usage": {
      "get": {
        "tags": [
          "Usage and billing"
        ],
        "summary": "Billable usage",
        "description": "Reports confirmed by SES in a month and the estimated amount, VAT excluded. The official invoice is issued by Stripe at month end.",
        "operationId": "getUsage",
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "required": false,
            "description": "Month to read, YYYY-MM (UTC). Default: the current month.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Usage for the month, by day, and the last six months.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/test/emails": {
      "get": {
        "tags": [
          "Test mode"
        ],
        "summary": "Test inbox",
        "description": "Test mode only. Every email the test environment generated, newest first, whether it went out to your team or stayed here. Use it to check what a guest would receive, or to pick up the registration link of an invitation in your automated tests. Kept for 30 days.",
        "operationId": "listTestEmails",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many, 1 to 50. Default 20.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The emails.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestEmailList"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "test_mode_only: this endpoint only works with a test-mode key (kinapi_test_…). Also tenant_suspended: the account is suspended for non-payment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/test/reset": {
      "post": {
        "tags": [
          "Test mode"
        ],
        "summary": "Empty the test environment",
        "description": "Test mode only. Deletes every test booking with its guests, submissions, signatures, test inbox and webhook deliveries, so each test run starts clean. Configuration stays: properties, owners, templates, house rules, API keys, webhook and settings. No webhook is sent for what it deletes.",
        "operationId": "resetTestData",
        "responses": {
          "200": {
            "description": "What was deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestResetResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "test_mode_only: this endpoint only works with a test-mode key (kinapi_test_…). Also tenant_suspended: the account is suspended for non-payment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "internal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/exports": {
      "get": {
        "tags": [
          "Exports"
        ],
        "summary": "List exports",
        "description": "The exports of your account, most recent first (up to 50, kept for 30 days).",
        "operationId": "listExports",
        "responses": {
          "200": {
            "description": "Exports.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExportListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment. The whole API is blocked until it is settled. (This endpoint stays open while an account closure is pending.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Exports"
        ],
        "summary": "Request a records export (ZIP)",
        "description": "Generates a ZIP with everything an accommodation owner has to keep: for each booking, the traveller report of every guest as a PDF with their signature, the proof of filing with SES (PDF), the signed contract, the signature evidence and its RFC 3161 timestamp (.tsr), and the booking as JSON; plus CSV files with bookings, travellers and communications. Filter by owner (ownerId, including deactivated properties), by properties (listingIds) and/or by arrival dates. It answers 202 at once and generates the ZIP in the background: the export.ready (or export.failed) webhook says when it ends, or poll GET /exports/{id}. The ZIP can be downloaded for seven days. Also available while an account closure is pending.",
        "operationId": "createExport",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateExportRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Requested; being generated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Export"
                }
              }
            }
          },
          "400": {
            "description": "validation: invalid body (details carries fieldErrors), or invalid JSON. Also ownerId does not exist, ownerId combined with listingIds, or from after to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment. The whole API is blocked until it is settled. (This endpoint stays open while an account closure is pending.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/exports/{id}": {
      "get": {
        "tags": [
          "Exports"
        ],
        "summary": "Status of an export",
        "operationId": "getExport",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The export.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Export"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment. The whole API is blocked until it is settled. (This endpoint stays open while an account closure is pending.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no export with that id in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/exports/{id}/download": {
      "get": {
        "tags": [
          "Exports"
        ],
        "summary": "Download the export ZIP",
        "description": "Streams the ZIP. Only when status is ready.",
        "operationId": "downloadExport",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Resource id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The ZIP.",
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment. The whole API is blocked until it is settled. (This endpoint stays open while an account closure is pending.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not found: no export with that id in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "not_ready: still being generated, or it failed (details says why).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "expired: the ZIP was deleted seven days after it was ready. Request a new export.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/billing/portal": {
      "post": {
        "tags": [
          "Usage and billing"
        ],
        "summary": "Get a link to the billing portal",
        "description": "A single-use link to the Stripe billing portal (invoices and payment method). It expires: request a new one each time.",
        "operationId": "createBillingPortalLink",
        "responses": {
          "200": {
            "description": "The link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingPortalResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "tenant_suspended: the account is suspended for non-payment; the whole API is blocked until it is settled. account_closing: the account closure was requested; until it takes effect (effectiveAt) the API only allows exports, and reactivating the account in the panel opens it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "no_billing: the account has no billing customer yet (payment was never set up).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "ocp-apim-subscription-key",
        "description": "API key issued from the panel (Settings → API). The header x-api-key is accepted as an alias."
      }
    },
    "schemas": {
      "BillingPortalResponse": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "Single-use Stripe billing portal link (invoices and payment method). It expires: request a new one each time."
          }
        },
        "required": [
          "url"
        ]
      },
      "Branding": {
        "type": "object",
        "properties": {
          "primaryColor": {
            "nullable": true,
            "description": "#RRGGBB or null (Kinapi default).",
            "type": "string"
          },
          "primaryForeground": {
            "nullable": true,
            "description": "Text colour Kinapi will use on the brand colour, worked out from contrast.",
            "type": "string"
          },
          "backgroundColor": {
            "nullable": true,
            "type": "string"
          },
          "commercialName": {
            "nullable": true,
            "description": "Trading name shown to guests, or null.",
            "type": "string"
          },
          "logo": {
            "nullable": true,
            "description": "null when no logo is set.",
            "type": "object",
            "properties": {
              "contentType": {
                "nullable": true,
                "description": "image/png, image/jpeg or image/webp.",
                "type": "string"
              },
              "updatedAt": {
                "nullable": true,
                "type": "string"
              }
            },
            "required": [
              "contentType",
              "updatedAt"
            ]
          }
        },
        "required": [
          "primaryColor",
          "primaryForeground",
          "backgroundColor",
          "commercialName",
          "logo"
        ]
      },
      "Checkin": {
        "type": "object",
        "properties": {
          "reservationId": {
            "type": "string",
            "description": "Your booking identifier: the public id of the check-in everywhere in the API."
          },
          "listingId": {
            "type": "string",
            "description": "Listing id of the property."
          },
          "confirmationCode": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "description": "Origin of the booking as you sent it."
          },
          "checkInDate": {
            "type": "string",
            "description": "YYYY-MM-DD."
          },
          "checkOutDate": {
            "type": "string",
            "description": "YYYY-MM-DD."
          },
          "arrivalTime": {
            "type": "string",
            "description": "hh:mm."
          },
          "departureTime": {
            "type": "string",
            "description": "hh:mm."
          },
          "numGuestsExpected": {
            "type": "integer",
            "description": "Guests you announced."
          },
          "numGuestsConfirmed": {
            "nullable": true,
            "description": "Party size as corrected by the guest from their link; null until they do.",
            "type": "integer"
          },
          "leadGuest": {
            "type": "object",
            "properties": {
              "name": {
                "nullable": true,
                "type": "string"
              },
              "email": {
                "nullable": true,
                "type": "string"
              },
              "phone": {
                "nullable": true,
                "type": "string"
              }
            },
            "required": [
              "name",
              "email",
              "phone"
            ],
            "description": "Lead guest contact details. Always present; null in the fields the booking does not have."
          },
          "maxGuests": {
            "description": "Only when the booking carries its own capacity ceiling.",
            "type": "integer"
          },
          "returnUrl": {
            "description": "Only when set.",
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "in_progress",
              "completed",
              "signed",
              "submitted",
              "confirmed",
              "error",
              "cancelled"
            ],
            "description": "Where the check-in is: pending (no guest yet), in_progress (some guests registered), completed (all guests registered), signed (contract signed), submitted (report sent to SES, batch assigned), confirmed (SES returned a communication code for every guest), error (needs attention: see errorReason), cancelled."
          },
          "locale": {
            "description": "Language ACTUALLY applied to the guest form, contract and emails (an unsupported value falls back to Spanish). Absent when the booking inherits the property default.",
            "type": "string"
          },
          "contractTemplateId": {
            "description": "Only when the booking has its own template assigned.",
            "type": "string"
          },
          "houseRulesId": {
            "description": "Only when the booking has its own house rules set assigned.",
            "type": "string"
          },
          "tipoPago": {
            "description": "Only when the booking has its own payment method.",
            "type": "string"
          },
          "skipContract": {
            "description": "Only when true.",
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "manualSubmit": {
            "description": "Only when true.",
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "errorReason": {
            "description": "Why the check-in is in error, when status is error.",
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "Guest link. A bearer credential: whoever has it can register guests on this booking."
          },
          "contract": {
            "type": "object",
            "properties": {
              "signed": {
                "type": "boolean"
              },
              "signedAt": {
                "nullable": true,
                "description": "When the contract was completed by every signer; null until then.",
                "type": "string"
              }
            },
            "required": [
              "signed",
              "signedAt"
            ]
          },
          "anonymizedAt": {
            "description": "Only when the personal data of this booking was suppressed (its owner was offboarded): no guests, no lead guest, no files. What the law requires to keep is held blocked outside the account and deleted when its three years run out.",
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "description": "When the check-in was created. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "updatedAt": {
            "type": "string",
            "description": "Last change. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          }
        },
        "required": [
          "reservationId",
          "listingId",
          "confirmationCode",
          "source",
          "checkInDate",
          "checkOutDate",
          "arrivalTime",
          "departureTime",
          "numGuestsExpected",
          "numGuestsConfirmed",
          "leadGuest",
          "status",
          "url",
          "contract",
          "createdAt",
          "updatedAt"
        ]
      },
      "CheckinCancelledResponse": {
        "type": "object",
        "properties": {
          "reservationId": {
            "type": "string",
            "description": "Your booking identifier: the public id of the check-in everywhere in the API."
          },
          "listingId": {
            "type": "string",
            "description": "Listing id of the property."
          },
          "confirmationCode": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "description": "Origin of the booking as you sent it."
          },
          "checkInDate": {
            "type": "string",
            "description": "YYYY-MM-DD."
          },
          "checkOutDate": {
            "type": "string",
            "description": "YYYY-MM-DD."
          },
          "arrivalTime": {
            "type": "string",
            "description": "hh:mm."
          },
          "departureTime": {
            "type": "string",
            "description": "hh:mm."
          },
          "numGuestsExpected": {
            "type": "integer",
            "description": "Guests you announced."
          },
          "numGuestsConfirmed": {
            "nullable": true,
            "description": "Party size as corrected by the guest from their link; null until they do.",
            "type": "integer"
          },
          "leadGuest": {
            "type": "object",
            "properties": {
              "name": {
                "nullable": true,
                "type": "string"
              },
              "email": {
                "nullable": true,
                "type": "string"
              },
              "phone": {
                "nullable": true,
                "type": "string"
              }
            },
            "required": [
              "name",
              "email",
              "phone"
            ],
            "description": "Lead guest contact details. Always present; null in the fields the booking does not have."
          },
          "maxGuests": {
            "description": "Only when the booking carries its own capacity ceiling.",
            "type": "integer"
          },
          "returnUrl": {
            "description": "Only when set.",
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "in_progress",
              "completed",
              "signed",
              "submitted",
              "confirmed",
              "error",
              "cancelled"
            ],
            "description": "Where the check-in is: pending (no guest yet), in_progress (some guests registered), completed (all guests registered), signed (contract signed), submitted (report sent to SES, batch assigned), confirmed (SES returned a communication code for every guest), error (needs attention: see errorReason), cancelled."
          },
          "locale": {
            "description": "Language ACTUALLY applied to the guest form, contract and emails (an unsupported value falls back to Spanish). Absent when the booking inherits the property default.",
            "type": "string"
          },
          "contractTemplateId": {
            "description": "Only when the booking has its own template assigned.",
            "type": "string"
          },
          "houseRulesId": {
            "description": "Only when the booking has its own house rules set assigned.",
            "type": "string"
          },
          "tipoPago": {
            "description": "Only when the booking has its own payment method.",
            "type": "string"
          },
          "skipContract": {
            "description": "Only when true.",
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "manualSubmit": {
            "description": "Only when true.",
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "errorReason": {
            "description": "Why the check-in is in error, when status is error.",
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "Guest link. A bearer credential: whoever has it can register guests on this booking."
          },
          "contract": {
            "type": "object",
            "properties": {
              "signed": {
                "type": "boolean"
              },
              "signedAt": {
                "nullable": true,
                "description": "When the contract was completed by every signer; null until then.",
                "type": "string"
              }
            },
            "required": [
              "signed",
              "signedAt"
            ]
          },
          "anonymizedAt": {
            "description": "Only when the personal data of this booking was suppressed (its owner was offboarded): no guests, no lead guest, no files. What the law requires to keep is held blocked outside the account and deleted when its three years run out.",
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "description": "When the check-in was created. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "updatedAt": {
            "type": "string",
            "description": "Last change. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "action": {
            "type": "string",
            "enum": [
              "cancelled"
            ],
            "description": "There was something to keep: the booking stays as cancelled, nothing is erased."
          },
          "reason": {
            "type": "string",
            "enum": [
              "firmado",
              "comunicado",
              "facturado"
            ],
            "description": "Why it could not be deleted: firmado (a signed contract or signature evidence exists), comunicado (its report was filed with SES), facturado (it generated billable usage)."
          },
          "ses": {
            "type": "object",
            "properties": {
              "anulados": {
                "type": "integer",
                "description": "Communications annulled with SES in this call (0 when nothing had been filed)."
              },
              "anulacionError": {
                "description": "The Ministry did not accept the annulment. The booking is cancelled anyway; repeat the DELETE to retry the annulment.",
                "type": "string"
              }
            },
            "required": [
              "anulados"
            ],
            "description": "Result of annulling the filed reports."
          }
        },
        "required": [
          "reservationId",
          "listingId",
          "confirmationCode",
          "source",
          "checkInDate",
          "checkOutDate",
          "arrivalTime",
          "departureTime",
          "numGuestsExpected",
          "numGuestsConfirmed",
          "leadGuest",
          "status",
          "url",
          "contract",
          "createdAt",
          "updatedAt",
          "action",
          "reason",
          "ses"
        ]
      },
      "CheckinCreatedResponse": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "Guest link to share with the guests."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "in_progress",
              "completed",
              "signed",
              "submitted",
              "confirmed",
              "error",
              "cancelled"
            ],
            "description": "Where the check-in is: pending (no guest yet), in_progress (some guests registered), completed (all guests registered), signed (contract signed), submitted (report sent to SES, batch assigned), confirmed (SES returned a communication code for every guest), error (needs attention: see errorReason), cancelled."
          },
          "created": {
            "type": "boolean",
            "description": "false when a check-in with this reservationId already existed: the call is idempotent and nothing was modified."
          },
          "locale": {
            "description": "Language actually applied; absent when the property default applies.",
            "type": "string"
          },
          "contractTemplateId": {
            "description": "Only when the booking has its own template assigned.",
            "type": "string"
          },
          "houseRulesId": {
            "description": "Only when the booking has its own house rules set assigned.",
            "type": "string"
          },
          "tipoPago": {
            "description": "Only when the booking has its own payment method.",
            "type": "string"
          },
          "skipContract": {
            "description": "Only when true.",
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "manualSubmit": {
            "description": "Only when true.",
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "warning": {
            "description": "Non-blocking notice, e.g. the listing is not mapped to any property yet.",
            "type": "string"
          }
        },
        "required": [
          "url",
          "status",
          "created"
        ]
      },
      "CheckinDeletedResponse": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "deleted"
            ],
            "description": "Nobody had signed, nothing was filed and nothing was billed: the booking and its guests are gone."
          },
          "reservationId": {
            "type": "string"
          },
          "deletedGuests": {
            "type": "integer",
            "description": "Guests deleted with it."
          }
        },
        "required": [
          "action",
          "reservationId",
          "deletedGuests"
        ]
      },
      "CheckinDeleteResponse": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/CheckinDeletedResponse"
          },
          {
            "$ref": "#/components/schemas/CheckinCancelledResponse"
          }
        ],
        "description": "Which of the two happened is in action."
      },
      "CheckinListResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Checkin"
            }
          },
          "hasMore": {
            "type": "boolean",
            "description": "true when there are more results beyond offset + limit."
          }
        },
        "required": [
          "results",
          "hasMore"
        ]
      },
      "CheckinUpdatedResponse": {
        "type": "object",
        "properties": {
          "reservationId": {
            "type": "string",
            "description": "Your booking identifier: the public id of the check-in everywhere in the API."
          },
          "listingId": {
            "type": "string",
            "description": "Listing id of the property."
          },
          "confirmationCode": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "description": "Origin of the booking as you sent it."
          },
          "checkInDate": {
            "type": "string",
            "description": "YYYY-MM-DD."
          },
          "checkOutDate": {
            "type": "string",
            "description": "YYYY-MM-DD."
          },
          "arrivalTime": {
            "type": "string",
            "description": "hh:mm."
          },
          "departureTime": {
            "type": "string",
            "description": "hh:mm."
          },
          "numGuestsExpected": {
            "type": "integer",
            "description": "Guests you announced."
          },
          "numGuestsConfirmed": {
            "nullable": true,
            "description": "Party size as corrected by the guest from their link; null until they do.",
            "type": "integer"
          },
          "leadGuest": {
            "type": "object",
            "properties": {
              "name": {
                "nullable": true,
                "type": "string"
              },
              "email": {
                "nullable": true,
                "type": "string"
              },
              "phone": {
                "nullable": true,
                "type": "string"
              }
            },
            "required": [
              "name",
              "email",
              "phone"
            ],
            "description": "Lead guest contact details. Always present; null in the fields the booking does not have."
          },
          "maxGuests": {
            "description": "Only when the booking carries its own capacity ceiling.",
            "type": "integer"
          },
          "returnUrl": {
            "description": "Only when set.",
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "in_progress",
              "completed",
              "signed",
              "submitted",
              "confirmed",
              "error",
              "cancelled"
            ],
            "description": "Where the check-in is: pending (no guest yet), in_progress (some guests registered), completed (all guests registered), signed (contract signed), submitted (report sent to SES, batch assigned), confirmed (SES returned a communication code for every guest), error (needs attention: see errorReason), cancelled."
          },
          "locale": {
            "description": "Language ACTUALLY applied to the guest form, contract and emails (an unsupported value falls back to Spanish). Absent when the booking inherits the property default.",
            "type": "string"
          },
          "contractTemplateId": {
            "description": "Only when the booking has its own template assigned.",
            "type": "string"
          },
          "houseRulesId": {
            "description": "Only when the booking has its own house rules set assigned.",
            "type": "string"
          },
          "tipoPago": {
            "description": "Only when the booking has its own payment method.",
            "type": "string"
          },
          "skipContract": {
            "description": "Only when true.",
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "manualSubmit": {
            "description": "Only when true.",
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "errorReason": {
            "description": "Why the check-in is in error, when status is error.",
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "Guest link. A bearer credential: whoever has it can register guests on this booking."
          },
          "contract": {
            "type": "object",
            "properties": {
              "signed": {
                "type": "boolean"
              },
              "signedAt": {
                "nullable": true,
                "description": "When the contract was completed by every signer; null until then.",
                "type": "string"
              }
            },
            "required": [
              "signed",
              "signedAt"
            ]
          },
          "anonymizedAt": {
            "description": "Only when the personal data of this booking was suppressed (its owner was offboarded): no guests, no lead guest, no files. What the law requires to keep is held blocked outside the account and deleted when its three years run out.",
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "description": "When the check-in was created. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "updatedAt": {
            "type": "string",
            "description": "Last change. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "warning": {
            "description": "Present when contractTemplateId was changed on an already signed contract: the signed one keeps its template.",
            "type": "string"
          },
          "refiling": {
            "description": "Only when the dates of an already filed booking changed: what was annulled and whether it was filed again.",
            "allOf": [
              {
                "$ref": "#/components/schemas/Refiling"
              }
            ]
          }
        },
        "required": [
          "reservationId",
          "listingId",
          "confirmationCode",
          "source",
          "checkInDate",
          "checkOutDate",
          "arrivalTime",
          "departureTime",
          "numGuestsExpected",
          "numGuestsConfirmed",
          "leadGuest",
          "status",
          "url",
          "contract",
          "createdAt",
          "updatedAt"
        ]
      },
      "CheckinWithGuests": {
        "type": "object",
        "properties": {
          "reservationId": {
            "type": "string",
            "description": "Your booking identifier: the public id of the check-in everywhere in the API."
          },
          "listingId": {
            "type": "string",
            "description": "Listing id of the property."
          },
          "confirmationCode": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "description": "Origin of the booking as you sent it."
          },
          "checkInDate": {
            "type": "string",
            "description": "YYYY-MM-DD."
          },
          "checkOutDate": {
            "type": "string",
            "description": "YYYY-MM-DD."
          },
          "arrivalTime": {
            "type": "string",
            "description": "hh:mm."
          },
          "departureTime": {
            "type": "string",
            "description": "hh:mm."
          },
          "numGuestsExpected": {
            "type": "integer",
            "description": "Guests you announced."
          },
          "numGuestsConfirmed": {
            "nullable": true,
            "description": "Party size as corrected by the guest from their link; null until they do.",
            "type": "integer"
          },
          "leadGuest": {
            "type": "object",
            "properties": {
              "name": {
                "nullable": true,
                "type": "string"
              },
              "email": {
                "nullable": true,
                "type": "string"
              },
              "phone": {
                "nullable": true,
                "type": "string"
              }
            },
            "required": [
              "name",
              "email",
              "phone"
            ],
            "description": "Lead guest contact details. Always present; null in the fields the booking does not have."
          },
          "maxGuests": {
            "description": "Only when the booking carries its own capacity ceiling.",
            "type": "integer"
          },
          "returnUrl": {
            "description": "Only when set.",
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "in_progress",
              "completed",
              "signed",
              "submitted",
              "confirmed",
              "error",
              "cancelled"
            ],
            "description": "Where the check-in is: pending (no guest yet), in_progress (some guests registered), completed (all guests registered), signed (contract signed), submitted (report sent to SES, batch assigned), confirmed (SES returned a communication code for every guest), error (needs attention: see errorReason), cancelled."
          },
          "locale": {
            "description": "Language ACTUALLY applied to the guest form, contract and emails (an unsupported value falls back to Spanish). Absent when the booking inherits the property default.",
            "type": "string"
          },
          "contractTemplateId": {
            "description": "Only when the booking has its own template assigned.",
            "type": "string"
          },
          "houseRulesId": {
            "description": "Only when the booking has its own house rules set assigned.",
            "type": "string"
          },
          "tipoPago": {
            "description": "Only when the booking has its own payment method.",
            "type": "string"
          },
          "skipContract": {
            "description": "Only when true.",
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "manualSubmit": {
            "description": "Only when true.",
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "errorReason": {
            "description": "Why the check-in is in error, when status is error.",
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "Guest link. A bearer credential: whoever has it can register guests on this booking."
          },
          "contract": {
            "type": "object",
            "properties": {
              "signed": {
                "type": "boolean"
              },
              "signedAt": {
                "nullable": true,
                "description": "When the contract was completed by every signer; null until then.",
                "type": "string"
              }
            },
            "required": [
              "signed",
              "signedAt"
            ]
          },
          "anonymizedAt": {
            "description": "Only when the personal data of this booking was suppressed (its owner was offboarded): no guests, no lead guest, no files. What the law requires to keep is held blocked outside the account and deleted when its three years run out.",
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "description": "When the check-in was created. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "updatedAt": {
            "type": "string",
            "description": "Last change. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "guests": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Guest"
            },
            "description": "Registered guests, in registration order."
          }
        },
        "required": [
          "reservationId",
          "listingId",
          "confirmationCode",
          "source",
          "checkInDate",
          "checkOutDate",
          "arrivalTime",
          "departureTime",
          "numGuestsExpected",
          "numGuestsConfirmed",
          "leadGuest",
          "status",
          "url",
          "contract",
          "createdAt",
          "updatedAt",
          "guests"
        ]
      },
      "Clause": {
        "type": "object",
        "properties": {
          "es": {
            "type": "string",
            "description": "Spanish text."
          },
          "en": {
            "type": "string",
            "description": "English text (may be empty)."
          }
        },
        "required": [
          "es",
          "en"
        ]
      },
      "ClauseInput": {
        "type": "object",
        "properties": {
          "es": {
            "type": "string",
            "description": "Spanish text."
          },
          "en": {
            "default": "",
            "description": "English text. Empty or omitted: the Spanish text is used.",
            "type": "string"
          }
        },
        "required": [
          "es"
        ]
      },
      "ContractTemplate": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Template id, to assign from a property or a booking with contractTemplateId."
          },
          "name": {
            "type": "string"
          },
          "bodyMode": {
            "type": "string",
            "enum": [
              "clauses",
              "document"
            ],
            "description": "clauses: the text is a list of bilingual clauses. document: you paste the whole text, one per language, in body. Clauses are kept when switching modes."
          },
          "body": {
            "description": "Only in document mode, when a body was written.",
            "allOf": [
              {
                "$ref": "#/components/schemas/LocalizedBody"
              }
            ]
          },
          "isDefault": {
            "type": "boolean"
          },
          "showOwnerInfo": {
            "type": "boolean"
          },
          "showOwnerDocument": {
            "type": "boolean"
          },
          "showPropertyInfo": {
            "type": "boolean"
          },
          "houseRules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Clause"
            }
          },
          "legalClauses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Clause"
            }
          },
          "createdAt": {
            "type": "string",
            "description": "When it was created. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "updatedAt": {
            "type": "string",
            "description": "Last change. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          }
        },
        "required": [
          "id",
          "name",
          "bodyMode",
          "isDefault",
          "showOwnerInfo",
          "showOwnerDocument",
          "showPropertyInfo",
          "houseRules",
          "legalClauses",
          "createdAt",
          "updatedAt"
        ]
      },
      "ContractTemplateListResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContractTemplate"
            }
          }
        },
        "required": [
          "results"
        ]
      },
      "CreateCheckinRequest": {
        "type": "object",
        "properties": {
          "reservationId": {
            "type": "string",
            "minLength": 1,
            "description": "Your booking identifier. It becomes the public id of the check-in and must be unique within your account."
          },
          "listingId": {
            "type": "string",
            "minLength": 1,
            "description": "Listing id of the property (its listingId, or one of its channelListingIds)."
          },
          "confirmationCode": {
            "type": "string",
            "minLength": 1,
            "description": "Booking confirmation code, printed on the contract and the reports."
          },
          "source": {
            "description": "Origin of the booking, free text (airbnb, booking, direct…).",
            "type": "string"
          },
          "checkInDate": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Arrival date, YYYY-MM-DD."
          },
          "checkOutDate": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Departure date, YYYY-MM-DD."
          },
          "arrivalTime": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}$",
            "description": "Check-in time, hh:mm. Defaults to the account setting."
          },
          "departureTime": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}$",
            "description": "Check-out time, hh:mm. Defaults to the account setting."
          },
          "numGuests": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "description": "Number of guests expected."
          },
          "maxGuests": {
            "description": "Real capacity of the accommodation. Guests can adjust the party size from their link, and this is the ceiling. Omitted: the property capacity applies, and failing that only the global limit.",
            "type": "integer",
            "minimum": 1,
            "maximum": 50
          },
          "returnUrl": {
            "description": "https 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. https only and no embedded credentials; an invalid URL returns 400. Only the SHAPE is checked, not that it works: send it complete, including any trailing token or id.",
            "type": "string"
          },
          "leadGuest": {
            "type": "object",
            "properties": {
              "name": {
                "nullable": true,
                "type": "string"
              },
              "email": {
                "nullable": true,
                "type": "string"
              },
              "phone": {
                "nullable": true,
                "type": "string"
              }
            },
            "description": "Contact details of the lead guest. The email matters: it is where the check-in invitation, the reminder and the signed contract are sent."
          },
          "locale": {
            "description": "Language of the guest form, the contract and the emails to the guest: es, en, de, fr, nl, it or pt. Regional variants are accepted (nl-NL → nl). Omitted: the property default applies. An unsupported value falls back to Spanish WITHOUT an error; check locale in the response.",
            "type": "string"
          },
          "contractTemplateId": {
            "description": "Contract template for this booking (id from /contract-templates). Omitted: the property template, then the account default.",
            "type": "string"
          },
          "houseRulesId": {
            "description": "House rules set for this booking (id from /house-rules). Omitted: the property set, then the account default.",
            "type": "string"
          },
          "tipoPago": {
            "description": "How THIS stay was paid for, as a code from the SES catalogue TIPO_PAGO (PLATF, TRANS, EFECT, TARJT, MOVIL, DESTI, TREG, OTRO). Overrides the property and the credential defaults.",
            "type": "string"
          },
          "skipContract": {
            "description": "true: this booking carries no contract terms of your own; the document has only the house rules and the legal conditions (which cannot be removed).",
            "type": "boolean"
          },
          "manualSubmit": {
            "description": "true: the report does NOT go out in the nightly submission, nor is it filed again on its own after a date change. File it when you decide with POST /checkins/{reservationId}/ses. The legal deadline still applies.",
            "type": "boolean"
          }
        },
        "required": [
          "reservationId",
          "listingId",
          "confirmationCode",
          "checkInDate",
          "checkOutDate",
          "numGuests"
        ]
      },
      "CreateContractTemplateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "description": "Name you use to pick the template."
          },
          "isDefault": {
            "description": "Mark it as the account default. Only one template can be the default.",
            "type": "boolean"
          },
          "showOwnerInfo": {
            "description": "Print the owner details section on the PDF. Default true.",
            "type": "boolean"
          },
          "showOwnerDocument": {
            "description": "Print the owner tax number. Only applies when showOwnerInfo is true. Default true.",
            "type": "boolean"
          },
          "showPropertyInfo": {
            "description": "Print the property details section on the PDF. Default true.",
            "type": "boolean"
          },
          "houseRules": {
            "description": "House rules printed by this template when no house rules set applies. Omitted on creation: Kinapi standard rules.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClauseInput"
            }
          },
          "legalClauses": {
            "description": "Contract terms, clause by clause. Omitted on creation: Kinapi standard clauses.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClauseInput"
            }
          },
          "bodyMode": {
            "type": "string",
            "enum": [
              "clauses",
              "document"
            ],
            "description": "clauses: the text is a list of bilingual clauses. document: you paste the whole text, one per language, in body. Clauses are kept when switching modes."
          },
          "body": {
            "allOf": [
              {
                "$ref": "#/components/schemas/LocalizedBody"
              }
            ]
          }
        },
        "required": [
          "name"
        ]
      },
      "CreateExportRequest": {
        "type": "object",
        "properties": {
          "ownerId": {
            "description": "Export the bookings of every property of this owner (id from /owners), including deactivated properties. Cannot be combined with listingIds.",
            "type": "string",
            "minLength": 1
          },
          "listingIds": {
            "description": "Or the bookings of these properties (listingId).",
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "from": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Arrival date from, inclusive (YYYY-MM-DD)."
          },
          "to": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Arrival date to, inclusive (YYYY-MM-DD)."
          }
        },
        "description": "What to export. Everything is optional: an empty body exports every booking in the account. Up to 3000 bookings per export; above that, split it by dates."
      },
      "CreateHouseRulesRequest": {
        "type": "object",
        "properties": {
          "id": {
            "description": "If sent and it exists, the call UPDATES that set instead of creating one.",
            "type": "string",
            "minLength": 1
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "description": "Name you use to pick the set (\"Chalets\", \"City centre flat\")."
          },
          "isDefault": {
            "description": "Mark it as the account default: it applies to properties with no set of their own. Only one can be the default.",
            "type": "boolean"
          },
          "clauses": {
            "description": "The rules, one per entry. Omitted on creation: Kinapi standard rules. On an update with id, omitted keeps the existing ones.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClauseInput"
            }
          },
          "bodyMode": {
            "type": "string",
            "enum": [
              "clauses",
              "document"
            ],
            "description": "clauses: a list of bilingual rules. document: the whole text, one per language, in body. Clauses are kept when switching modes."
          },
          "body": {
            "allOf": [
              {
                "$ref": "#/components/schemas/LocalizedBody"
              }
            ]
          }
        },
        "required": [
          "name"
        ]
      },
      "CreateOwnerRequest": {
        "type": "object",
        "properties": {
          "legalName": {
            "type": "string",
            "minLength": 1,
            "description": "Legal name of the owner (person or company). Printed on the contract."
          },
          "nif": {
            "description": "Spanish tax number (NIF/CIF).",
            "type": "string"
          },
          "fiscalAddress": {
            "description": "Registered address.",
            "type": "string"
          }
        },
        "required": [
          "legalName"
        ]
      },
      "CreatePropertyRequest": {
        "type": "object",
        "properties": {
          "id": {
            "description": "If sent and it exists, the call UPDATES that property instead of creating one.",
            "type": "string",
            "minLength": 1
          },
          "nickname": {
            "type": "string",
            "minLength": 1,
            "description": "Name you use for the property."
          },
          "listingId": {
            "description": "Listing id in your PMS. Optional: if omitted, Kinapi generates one shaped like ALJ-XXXXXX and returns it.",
            "type": "string",
            "minLength": 1
          },
          "commercialName": {
            "description": "Trading name of the property: 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). Empty string removes it.",
            "type": "string",
            "maxLength": 80
          },
          "channelListingIds": {
            "description": "Ids of channel clones (Airbnb, Booking, Vrbo…) that resolve to this property when creating check-ins.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "address": {
            "type": "object",
            "properties": {
              "street": {
                "type": "string"
              },
              "city": {
                "type": "string"
              },
              "province": {
                "type": "string"
              },
              "postalCode": {
                "type": "string"
              }
            },
            "description": "Postal address of the accommodation. It is printed on the contract."
          },
          "touristLicense": {
            "description": "Tourist licence number (e.g. VFT/CA/12345).",
            "type": "string"
          },
          "maxGuests": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  ""
                ]
              },
              {
                "type": "integer",
                "minimum": 0,
                "maximum": 50
              }
            ],
            "description": "Capacity of the accommodation, 1 to 50. Bookings inherit it when they do not carry their own: it is the ceiling up to which the guest can correct the party size from their link. Empty string or 0 removes it."
          },
          "communicationLocale": {
            "type": "string",
            "description": "Default language of the accommodation: es, en, de, fr, nl, it or pt. Bookings that do not send a locale inherit it, and it decides the guest form, the contract and the emails at once. Normalised (nl-NL becomes nl); an unsupported value falls back to Spanish without an error. Empty string unassigns it."
          },
          "houseRulesId": {
            "description": "House rules set assigned to this property (id from /house-rules). A booking can override it with its own. Empty string unassigns it.",
            "type": "string"
          },
          "notifications": {
            "type": "object",
            "properties": {
              "invitacion": {
                "description": "Email the guest an invitation to register.",
                "type": "boolean"
              },
              "recordatorio": {
                "description": "Second email if the guest has not registered yet.",
                "type": "boolean"
              },
              "completo": {
                "description": "Email you when every guest of a booking has registered.",
                "type": "boolean"
              },
              "ultimoAviso": {
                "description": "Last notice to the guest on the morning of arrival, if they have not registered yet.",
                "type": "boolean"
              }
            },
            "description": "Email notices for THIS property, over the account settings. Each field is three-state: absent inherits the account setting, true and false decide on their own. The reminder and the last notice do not depend on the invitation: keep them on with the invitation off if you send the link yourself. Send an empty object to clear the property settings and inherit again."
          },
          "houseRules": {
            "maxItems": 50,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "es": {
                  "type": "string",
                  "maxLength": 500
                },
                "en": {
                  "default": "",
                  "type": "string",
                  "maxLength": 500
                }
              },
              "required": [
                "es"
              ]
            },
            "description": "LEGACY. House rules written inside the property. Still accepted, but they only apply when no house rules set is assigned at any level. New integrations should use houseRulesId instead. An empty array clears them."
          },
          "ses": {
            "type": "object",
            "properties": {
              "codigoEstablecimiento": {
                "description": "Establishment code at SES Hospedajes. Without it no report can be filed for this property.",
                "type": "string"
              },
              "tipoPago": {
                "description": "Default payment method for this property, SES TIPO_PAGO code. Without it the credential set default applies. A tipoPago on a booking overrides it.",
                "type": "string"
              }
            },
            "description": "SES Hospedajes settings of the property."
          },
          "ownerId": {
            "description": "Id of an existing owner (from /owners). One owner can be on several properties. Takes precedence over owner. On PATCH, empty string unlinks the owner.",
            "type": "string"
          },
          "owner": {
            "type": "object",
            "properties": {
              "legalName": {
                "type": "string",
                "minLength": 1,
                "description": "Legal name of the owner (person or company)."
              },
              "nif": {
                "description": "Spanish tax number (NIF/CIF).",
                "type": "string"
              },
              "fiscalAddress": {
                "description": "Registered address.",
                "type": "string"
              }
            },
            "required": [
              "legalName"
            ],
            "description": "Creates (or, on PATCH, updates) the owner inline and links it to the property. Alternative to ownerId."
          },
          "contractTemplateId": {
            "description": "Contract template assigned to this property (id from /contract-templates). Without it the account default applies. Empty string unassigns it.",
            "type": "string"
          },
          "sesCredentialId": {
            "description": "SES credential set used for this property (id from /ses-credentials). Without it the account default applies. Empty string unassigns it.",
            "type": "string"
          },
          "active": {
            "description": "false retires the property: out of the listing (unless includeInactive), the panel selectors and the calendar sweep. Existing bookings still resolve to it. true reactivates it.",
            "type": "boolean"
          }
        },
        "required": [
          "nickname"
        ]
      },
      "CreateSesCredentialRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "description": "Name you use to pick the set (usually the landlord)."
          },
          "isDefault": {
            "description": "Mark it as the account default, used by properties with no set of their own.",
            "type": "boolean"
          },
          "codigoArrendador": {
            "type": "string",
            "minLength": 1,
            "description": "Landlord code assigned by SES Hospedajes when the landlord registered (10 digits)."
          },
          "usuario": {
            "type": "string",
            "minLength": 1,
            "description": "Web service user issued by SES (it ends in WS). Not the portal login."
          },
          "aplicacion": {
            "type": "string",
            "minLength": 1,
            "description": "Name your software identifies itself with to SES on every call. Any short label."
          },
          "tipoPago": {
            "type": "string",
            "minLength": 1,
            "description": "Default payment method for this landlord, SES TIPO_PAGO code. The last fallback when neither the booking nor the property sets one."
          },
          "contrasena": {
            "type": "string",
            "minLength": 1,
            "description": "Web service password. Stored encrypted, never returned."
          }
        },
        "required": [
          "name",
          "codigoArrendador",
          "usuario",
          "aplicacion",
          "tipoPago",
          "contrasena"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Stable error code to branch on: validation, not found, full, already_submitted, not_ready, refiling_blocked, ses_error, payment_method_required, tenant_suspended, quota_exceeded, invalid_url, owner_in_use, no_guests, no_timestamp, no_billing, not_configured, disabled, cancelled, internal."
          },
          "code": {
            "description": "Sub-code when one error has several causes (refiling_blocked: en_vuelo or anulacion_fallida).",
            "type": "string"
          },
          "details": {
            "description": "Human-readable explanation, in Spanish. For validation errors it is instead the Zod \"flatten\" object: formErrors (string array) and fieldErrors (field name to string array)."
          },
          "issues": {
            "description": "Guest registration only: one entry per invalid field. message is a translation key of the guest form (required, invalidDocument, soporteRequired, parentescoRequired, invalidPostalCodeEs…), the same the form shows the guest.",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "path",
                "message"
              ]
            }
          },
          "failed": {
            "description": "SES catalogues refresh only: catalogues that could not be refreshed.",
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "error"
        ]
      },
      "Evidence": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Evidence record id. Use it to download the timestamp token."
          },
          "createdAt": {
            "type": "string",
            "description": "When the whole party had signed and the final PDF was generated. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "sha256": {
            "type": "string",
            "description": "SHA-256 of the signed PDF. This is exactly what was timestamped."
          },
          "docLocale": {
            "nullable": true,
            "description": "Language the contract was issued in.",
            "type": "string"
          },
          "contractTemplateId": {
            "nullable": true,
            "description": "Template it was generated with, to reconstruct it.",
            "type": "string"
          },
          "tsa": {
            "type": "object",
            "properties": {
              "ok": {
                "type": "boolean",
                "description": "true: a verified RFC 3161 timestamp exists (hash and nonce checked when received)."
              },
              "authority": {
                "type": "string",
                "description": "URL of the timestamping authority."
              },
              "genTime": {
                "nullable": true,
                "description": "Instant certified by the authority, ISO 8601. null when there is no timestamp.",
                "type": "string"
              },
              "archived": {
                "type": "boolean",
                "description": "true: the token is also archived in immutable storage next to the PDF."
              },
              "error": {
                "nullable": true,
                "description": "Why there is no timestamp, when there is none. It is retried every hour.",
                "type": "string"
              },
              "hasToken": {
                "type": "boolean",
                "description": "true: GET …/evidence/{evidenceId}/token returns the token."
              }
            },
            "required": [
              "ok",
              "authority",
              "genTime",
              "archived",
              "error",
              "hasToken"
            ],
            "description": "The RFC 3161 timestamp of sha256."
          },
          "signers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "guestId": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "signedAt": {
                  "nullable": true,
                  "description": "When this guest signed.",
                  "type": "string"
                },
                "ip": {
                  "nullable": true,
                  "description": "IP address the signature came from.",
                  "type": "string"
                },
                "userAgent": {
                  "nullable": true,
                  "description": "Browser/device the signature came from.",
                  "type": "string"
                },
                "signatureSha256": {
                  "nullable": true,
                  "description": "SHA-256 of the signature image, as printed on the evidence page of the PDF.",
                  "type": "string"
                },
                "uiLocale": {
                  "nullable": true,
                  "description": "Language this signer saw the form in.",
                  "type": "string"
                },
                "importedFromChekin": {
                  "type": "boolean",
                  "description": "true: signature imported from a previous provider, not captured by Kinapi."
                }
              },
              "required": [
                "guestId",
                "name",
                "signedAt",
                "ip",
                "userAgent",
                "signatureSha256",
                "uiLocale",
                "importedFromChekin"
              ]
            },
            "description": "One entry per guest who signed."
          }
        },
        "required": [
          "id",
          "createdAt",
          "sha256",
          "docLocale",
          "contractTemplateId",
          "tsa",
          "signers"
        ]
      },
      "EvidenceListResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Evidence"
            },
            "description": "Most recent first. A booking can have more than one when the contract was completed again."
          }
        },
        "required": [
          "results"
        ]
      },
      "Export": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Export id."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "ready",
              "failed",
              "expired"
            ],
            "description": "pending/processing: being generated (the export.ready or export.failed webhook says when it ends). ready: download it from …/download. failed: error says why. expired: the ZIP was deleted 7 days after it was ready; request a new one."
          },
          "description": {
            "type": "string",
            "description": "What is exported, in Spanish."
          },
          "scope": {
            "type": "object",
            "properties": {
              "ownerId": {
                "nullable": true,
                "type": "string"
              },
              "listingIds": {
                "nullable": true,
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "from": {
                "nullable": true,
                "type": "string"
              },
              "to": {
                "nullable": true,
                "type": "string"
              }
            },
            "required": [
              "ownerId",
              "listingIds",
              "from",
              "to"
            ]
          },
          "requestedBy": {
            "type": "string",
            "description": "Panel user email, or api."
          },
          "createdAt": {
            "type": "string",
            "description": "When it was requested. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "finishedAt": {
            "nullable": true,
            "description": "When it finished, ready or failed.",
            "type": "string"
          },
          "expiresAt": {
            "nullable": true,
            "description": "Until when the ZIP can be downloaded.",
            "type": "string"
          },
          "bookings": {
            "nullable": true,
            "description": "Bookings included, once ready.",
            "type": "integer"
          },
          "files": {
            "nullable": true,
            "description": "Files inside the ZIP, once ready.",
            "type": "integer"
          },
          "bytes": {
            "nullable": true,
            "description": "Size of the ZIP, once ready.",
            "type": "integer"
          },
          "error": {
            "nullable": true,
            "description": "Why it failed, in Spanish.",
            "type": "string"
          }
        },
        "required": [
          "id",
          "status",
          "description",
          "scope",
          "requestedBy",
          "createdAt",
          "finishedAt",
          "expiresAt",
          "bookings",
          "files",
          "bytes",
          "error"
        ]
      },
      "ExportListResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Export"
            },
            "description": "Most recent first, up to 50. Exports are kept for 30 days."
          }
        },
        "required": [
          "results"
        ]
      },
      "Guest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Guest id, unique within the check-in."
          },
          "nombre": {
            "type": "string"
          },
          "apellido1": {
            "type": "string"
          },
          "apellido2": {
            "nullable": true,
            "type": "string"
          },
          "tipoDocumento": {
            "nullable": true,
            "description": "NIF, NIE, PAS or OTRO; null for minors without a document.",
            "type": "string"
          },
          "numeroDocumento": {
            "nullable": true,
            "type": "string"
          },
          "soporteDocumento": {
            "nullable": true,
            "description": "Only for NIF and NIE.",
            "type": "string"
          },
          "sexo": {
            "nullable": true,
            "description": "H, M or O.",
            "type": "string"
          },
          "parentesco": {
            "nullable": true,
            "description": "TIPO_PARENTESCO code; only for minors under 14.",
            "type": "string"
          },
          "direccion": {
            "type": "object",
            "properties": {
              "direccion": {
                "nullable": true,
                "type": "string"
              },
              "codigoMunicipio": {
                "nullable": true,
                "description": "Spanish INE municipality code; null outside Spain.",
                "type": "string"
              },
              "nombreMunicipio": {
                "nullable": true,
                "type": "string"
              },
              "codigoPostal": {
                "nullable": true,
                "type": "string"
              },
              "pais": {
                "nullable": true,
                "description": "ISO 3166-1 alpha-3 country code.",
                "type": "string"
              }
            },
            "required": [
              "direccion",
              "codigoMunicipio",
              "nombreMunicipio",
              "codigoPostal",
              "pais"
            ]
          },
          "nacionalidad": {
            "type": "string",
            "description": "ISO 3166-1 alpha-3 country code."
          },
          "fechaNacimiento": {
            "type": "string",
            "description": "YYYY-MM-DD."
          },
          "esMenor": {
            "type": "boolean",
            "description": "Under 18 at check-in. Informational; the document rules use 14."
          },
          "isLead": {
            "type": "boolean"
          },
          "telefono": {
            "nullable": true,
            "type": "string"
          },
          "correo": {
            "nullable": true,
            "type": "string"
          },
          "registeredAt": {
            "type": "string",
            "description": "When the guest completed the registration. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "signedAt": {
            "nullable": true,
            "description": "When this guest signed the contract; null until they do.",
            "type": "string"
          }
        },
        "required": [
          "id",
          "nombre",
          "apellido1",
          "apellido2",
          "tipoDocumento",
          "numeroDocumento",
          "soporteDocumento",
          "sexo",
          "parentesco",
          "direccion",
          "nacionalidad",
          "fechaNacimiento",
          "esMenor",
          "isLead",
          "telefono",
          "correo",
          "registeredAt",
          "signedAt"
        ],
        "description": "A registered guest. It carries everything needed to register the same person again on another booking (POST /checkins/{reservationId}/guests accepts this object as its body)."
      },
      "GuestDeletedResponse": {
        "type": "object",
        "properties": {
          "deleted": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "checkinStatus": {
            "nullable": true,
            "description": "Status of the check-in after removing the guest.",
            "type": "string",
            "enum": [
              "pending",
              "in_progress",
              "completed",
              "signed",
              "submitted",
              "confirmed",
              "error",
              "cancelled"
            ]
          }
        },
        "required": [
          "deleted",
          "checkinStatus"
        ]
      },
      "GuestListResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Guest"
            }
          }
        },
        "required": [
          "results"
        ]
      },
      "GuestRegisteredResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id of the new guest."
          },
          "checkinStatus": {
            "nullable": true,
            "description": "Status of the check-in after adding the guest.",
            "type": "string",
            "enum": [
              "pending",
              "in_progress",
              "completed",
              "signed",
              "submitted",
              "confirmed",
              "error",
              "cancelled"
            ]
          },
          "signed": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "Always false: registering through the API never signs."
          }
        },
        "required": [
          "id",
          "checkinStatus",
          "signed"
        ]
      },
      "HouseRules": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Set id, to assign from a property or a booking with houseRulesId."
          },
          "name": {
            "type": "string"
          },
          "bodyMode": {
            "type": "string",
            "enum": [
              "clauses",
              "document"
            ],
            "description": "clauses: a list of bilingual rules. document: the whole text, one per language, in body. Clauses are kept when switching modes."
          },
          "body": {
            "description": "Only in document mode, when a body was written.",
            "allOf": [
              {
                "$ref": "#/components/schemas/LocalizedBody"
              }
            ]
          },
          "isDefault": {
            "type": "boolean"
          },
          "clauses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Clause"
            },
            "description": "Always returned, even in document mode: they are the fallback when switching back."
          },
          "hasText": {
            "type": "boolean",
            "description": "false: this set prints NOTHING and is skipped when resolving which rules apply. Sets with no text are indistinguishable from filled ones otherwise."
          },
          "createdAt": {
            "type": "string",
            "description": "When it was created. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "updatedAt": {
            "type": "string",
            "description": "Last change. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          }
        },
        "required": [
          "id",
          "name",
          "bodyMode",
          "isDefault",
          "clauses",
          "hasText",
          "createdAt",
          "updatedAt"
        ]
      },
      "HouseRulesListResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HouseRules"
            }
          }
        },
        "required": [
          "results"
        ]
      },
      "LocalizedBody": {
        "type": "object",
        "properties": {
          "es": {
            "type": "string",
            "maxLength": 60000
          },
          "en": {
            "type": "string",
            "maxLength": 60000
          },
          "de": {
            "type": "string",
            "maxLength": 60000
          },
          "fr": {
            "type": "string",
            "maxLength": 60000
          },
          "nl": {
            "type": "string",
            "maxLength": 60000
          },
          "it": {
            "type": "string",
            "maxLength": 60000
          },
          "pt": {
            "type": "string",
            "maxLength": 60000
          }
        },
        "description": "One text per language (es, en, de, fr, nl, it, pt), up to 60000 characters each, in Kinapi's restricted markup: # ## ### for headings, - and 1. for lists, **bold**, *italic*. A language that is missing falls back to Spanish in full."
      },
      "OkResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          }
        },
        "required": [
          "ok"
        ]
      },
      "Owner": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Owner id, to link from a property with ownerId."
          },
          "legalName": {
            "type": "string"
          },
          "nif": {
            "nullable": true,
            "type": "string"
          },
          "fiscalAddress": {
            "nullable": true,
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "description": "When it was created. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "updatedAt": {
            "type": "string",
            "description": "Last change. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          }
        },
        "required": [
          "id",
          "legalName",
          "nif",
          "fiscalAddress",
          "createdAt",
          "updatedAt"
        ]
      },
      "OwnerListResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Owner"
            }
          }
        },
        "required": [
          "results"
        ]
      },
      "Property": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Property id (used in the URL of this resource)."
          },
          "nickname": {
            "type": "string"
          },
          "commercialName": {
            "description": "Only when the property has a trading name.",
            "type": "string"
          },
          "listingId": {
            "type": "string",
            "description": "Listing id used to create check-ins for this property."
          },
          "maxGuests": {
            "description": "Only when a capacity is declared.",
            "type": "integer"
          },
          "channelListingIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "address": {
            "nullable": true,
            "type": "object",
            "properties": {
              "street": {
                "type": "string"
              },
              "city": {
                "type": "string"
              },
              "province": {
                "type": "string"
              },
              "postalCode": {
                "type": "string"
              }
            }
          },
          "touristLicense": {
            "nullable": true,
            "type": "string"
          },
          "ownerId": {
            "nullable": true,
            "description": "Owner (titular) this property belongs to, from /owners; null when none is linked. Also in the list, so you can group properties by owner without one call per property.",
            "type": "string"
          },
          "houseRulesId": {
            "description": "Only when a house rules set is assigned.",
            "type": "string"
          },
          "notifications": {
            "description": "Only when the property has its own email notice settings.",
            "type": "object",
            "properties": {
              "invitacion": {
                "type": "boolean"
              },
              "recordatorio": {
                "type": "boolean"
              },
              "ultimoAviso": {
                "type": "boolean"
              },
              "completo": {
                "type": "boolean"
              }
            }
          },
          "houseRules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Clause"
            },
            "description": "LEGACY rules written inside the property; empty for new properties."
          },
          "ses": {
            "type": "object",
            "properties": {
              "codigoEstablecimiento": {
                "nullable": true,
                "type": "string"
              },
              "tipoPago": {
                "nullable": true,
                "type": "string"
              }
            },
            "required": [
              "codigoEstablecimiento",
              "tipoPago"
            ]
          },
          "owner": {
            "description": "Only on GET and PATCH of a single property, when it has an owner linked.",
            "type": "object",
            "properties": {
              "legalName": {
                "type": "string"
              },
              "nif": {
                "nullable": true,
                "type": "string"
              },
              "fiscalAddress": {
                "nullable": true,
                "type": "string"
              }
            },
            "required": [
              "legalName",
              "nif",
              "fiscalAddress"
            ]
          },
          "contractTemplateId": {
            "nullable": true,
            "type": "string"
          },
          "sesCredentialId": {
            "nullable": true,
            "type": "string"
          },
          "communicationLocale": {
            "nullable": true,
            "description": "Default language of the accommodation as stored, or null when none was chosen (Spanish applies).",
            "type": "string"
          },
          "active": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "description": "When it was created. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "updatedAt": {
            "type": "string",
            "description": "Last change. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          }
        },
        "required": [
          "id",
          "nickname",
          "listingId",
          "channelListingIds",
          "address",
          "touristLicense",
          "ownerId",
          "houseRules",
          "ses",
          "contractTemplateId",
          "sesCredentialId",
          "communicationLocale",
          "active",
          "createdAt",
          "updatedAt"
        ]
      },
      "PropertyDeleteResponse": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "ok": {
                "type": "boolean",
                "enum": [
                  true
                ]
              },
              "action": {
                "type": "string",
                "enum": [
                  "deleted"
                ],
                "description": "No booking referenced it: the record is really gone."
              },
              "propertyId": {
                "type": "string"
              }
            },
            "required": [
              "ok",
              "action",
              "propertyId"
            ]
          },
          {
            "type": "object",
            "properties": {
              "ok": {
                "type": "boolean",
                "enum": [
                  true
                ]
              },
              "action": {
                "type": "string",
                "enum": [
                  "deactivated"
                ],
                "description": "Some booking references it: kept and marked active: false."
              },
              "active": {
                "type": "boolean",
                "enum": [
                  false
                ]
              },
              "reason": {
                "type": "string",
                "enum": [
                  "has_bookings"
                ]
              },
              "propertyId": {
                "type": "string"
              }
            },
            "required": [
              "ok",
              "action",
              "active",
              "reason",
              "propertyId"
            ]
          }
        ],
        "description": "Which of the two happened is in action."
      },
      "PropertyListResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Property"
            }
          }
        },
        "required": [
          "results"
        ]
      },
      "PutWebhookRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "minLength": 1,
            "description": "Public https URL Kinapi will POST to. Internal and private addresses are rejected (400 invalid_url)."
          },
          "enabled": {
            "default": true,
            "type": "boolean"
          },
          "events": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "guest.registered",
                "checkin.completed",
                "ses.submitted",
                "ses.confirmed",
                "ses.error",
                "ses.blocked",
                "ses.cancelled",
                "ses.cancel_failed",
                "export.ready",
                "export.failed"
              ]
            },
            "description": "Events to subscribe to: guest.registered, checkin.completed, ses.submitted, ses.confirmed, ses.error, ses.blocked, ses.cancelled."
          },
          "secret": {
            "description": "Secret used to sign every delivery (header x-kinapi-signature-sha256, HMAC-SHA256 of the raw body). Omitted keeps the current one, or generates one if there was none.",
            "type": "string",
            "minLength": 16
          }
        },
        "required": [
          "url",
          "events"
        ],
        "description": "Creates or replaces the webhook. There is exactly one per account."
      },
      "Refiling": {
        "type": "object",
        "properties": {
          "anulados": {
            "type": "integer",
            "description": "How many communications were annulled with SES."
          },
          "reenviado": {
            "type": "boolean",
            "description": "true: filed again straight away (new arrival is today or in the past), see lote. false: it goes out in the nightly submission for the new arrival date, or the immediate refiling failed (see error) and the nightly submission retries it."
          },
          "lote": {
            "description": "Batch number SES assigned to the new filing.",
            "type": "string"
          },
          "error": {
            "description": "Why the immediate refiling failed.",
            "type": "string"
          },
          "retenida": {
            "description": "Present when 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.",
            "type": "boolean",
            "enum": [
              true
            ]
          }
        },
        "required": [
          "anulados",
          "reenviado"
        ]
      },
      "RegisterGuestRequest": {
        "type": "object",
        "properties": {
          "nacionalidad": {
            "type": "string",
            "description": "Nationality as an ISO 3166-1 alpha-3 country code (ESP, NLD, GBR…)."
          },
          "nombre": {
            "description": "First name, 2 to 50 characters.",
            "type": "string"
          },
          "apellido1": {
            "description": "First surname, 2 to 50 characters.",
            "type": "string"
          },
          "apellido2": {
            "description": "Second surname. Required when the document is a Spanish NIF; otherwise optional (null or omitted).",
            "nullable": true,
            "type": "string"
          },
          "fechaNacimiento": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Date of birth, YYYY-MM-DD. It decides the rules that follow: from 14 years of age at check-in the document is mandatory; below 14 it is optional and parentesco is mandatory."
          },
          "sexo": {
            "description": "Sex, SES catalogue SEXO: H (male), M (female), O (other).",
            "nullable": true,
            "type": "string",
            "enum": [
              "H",
              "M",
              "O"
            ]
          },
          "tipoDocumento": {
            "description": "Document type: NIF (Spanish national ID), NIE (Spanish foreigner ID), PAS (passport), OTRO (other). Mandatory from 14 years of age.",
            "nullable": true,
            "type": "string",
            "enum": [
              "NIF",
              "NIE",
              "PAS",
              "OTRO"
            ]
          },
          "numeroDocumento": {
            "description": "Document number as printed, without spaces or separators (they are stripped and it is upper-cased). Validated against the check digit for NIF and NIE.",
            "nullable": true,
            "type": "string"
          },
          "soporteDocumento": {
            "description": "Support number printed on Spanish NIF and NIE cards. Mandatory for those two types, ignored for the rest.",
            "nullable": true,
            "type": "string"
          },
          "parentesco": {
            "description": "Relationship of a minor (under 14) to the accompanying adult, as a code from the SES catalogue TIPO_PARENTESCO (see GET /ses/catalogs). Mandatory under 14, ignored otherwise.",
            "nullable": true,
            "type": "string",
            "maxLength": 5
          },
          "direccion": {
            "type": "object",
            "properties": {
              "direccion": {
                "description": "Street address, at least 3 characters and not just the postcode.",
                "nullable": true,
                "type": "string"
              },
              "codigoMunicipio": {
                "description": "Spanish INE municipality code. Mandatory when pais is ESP; ignored otherwise.",
                "nullable": true,
                "type": "string"
              },
              "nombreMunicipio": {
                "description": "Town or city name. Mandatory when pais is not ESP.",
                "nullable": true,
                "type": "string"
              },
              "codigoPostal": {
                "description": "Postcode. For ESP it must be an existing five-digit Spanish postcode; elsewhere at least 3 characters.",
                "nullable": true,
                "type": "string"
              },
              "pais": {
                "description": "Country of residence as an ISO 3166-1 alpha-3 code. Required.",
                "nullable": true,
                "type": "string"
              }
            },
            "description": "Home address of the traveller. Required by the Ministry for every guest."
          },
          "correo": {
            "description": "Email address. At least one of correo or telefono is required.",
            "nullable": true,
            "type": "string"
          },
          "telefono": {
            "description": "Phone number, international format accepted. At least one of correo or telefono is required.",
            "nullable": true,
            "type": "string"
          },
          "isLead": {
            "description": "Force this guest to be the lead guest. By default the first guest registered is the lead.",
            "type": "boolean"
          },
          "locale": {
            "description": "Language the guest used, for the record (es, en, de, fr, nl, it, pt). Informational.",
            "type": "string"
          }
        },
        "required": [
          "nacionalidad",
          "nombre",
          "apellido1",
          "fechaNacimiento",
          "direccion"
        ],
        "description": "Registers a guest with the same validation as the public form. Registering through the API does NOT produce a signature: the contract still has to be signed from the guest link."
      },
      "SesCatalogsRefreshedResponse": {
        "type": "object",
        "properties": {
          "refreshed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Catalogues refreshed from the Ministry."
          },
          "failed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Catalogues that could not be refreshed."
          }
        },
        "required": [
          "refreshed",
          "failed"
        ]
      },
      "SesCatalogsResponse": {
        "type": "object",
        "properties": {
          "catalogs": {
            "type": "object",
            "properties": {
              "TIPO_DOCUMENTO": {
                "nullable": true,
                "description": "null when the catalogue has never been refreshed.",
                "type": "object",
                "properties": {
                  "values": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "codigo": {
                          "type": "string"
                        },
                        "descripcion": {
                          "type": "string",
                          "description": "Official description, in Spanish."
                        }
                      },
                      "required": [
                        "codigo",
                        "descripcion"
                      ]
                    }
                  },
                  "updatedAt": {
                    "type": "string",
                    "description": "When it was last refreshed from the Ministry."
                  }
                },
                "required": [
                  "values",
                  "updatedAt"
                ]
              },
              "SEXO": {
                "nullable": true,
                "description": "null when the catalogue has never been refreshed.",
                "type": "object",
                "properties": {
                  "values": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "codigo": {
                          "type": "string"
                        },
                        "descripcion": {
                          "type": "string",
                          "description": "Official description, in Spanish."
                        }
                      },
                      "required": [
                        "codigo",
                        "descripcion"
                      ]
                    }
                  },
                  "updatedAt": {
                    "type": "string",
                    "description": "When it was last refreshed from the Ministry."
                  }
                },
                "required": [
                  "values",
                  "updatedAt"
                ]
              },
              "TIPO_PARENTESCO": {
                "nullable": true,
                "description": "null when the catalogue has never been refreshed.",
                "type": "object",
                "properties": {
                  "values": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "codigo": {
                          "type": "string"
                        },
                        "descripcion": {
                          "type": "string",
                          "description": "Official description, in Spanish."
                        }
                      },
                      "required": [
                        "codigo",
                        "descripcion"
                      ]
                    }
                  },
                  "updatedAt": {
                    "type": "string",
                    "description": "When it was last refreshed from the Ministry."
                  }
                },
                "required": [
                  "values",
                  "updatedAt"
                ]
              },
              "TIPO_PAGO": {
                "nullable": true,
                "description": "null when the catalogue has never been refreshed.",
                "type": "object",
                "properties": {
                  "values": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "codigo": {
                          "type": "string"
                        },
                        "descripcion": {
                          "type": "string",
                          "description": "Official description, in Spanish."
                        }
                      },
                      "required": [
                        "codigo",
                        "descripcion"
                      ]
                    }
                  },
                  "updatedAt": {
                    "type": "string",
                    "description": "When it was last refreshed from the Ministry."
                  }
                },
                "required": [
                  "values",
                  "updatedAt"
                ]
              }
            },
            "required": [
              "TIPO_DOCUMENTO",
              "SEXO",
              "TIPO_PARENTESCO",
              "TIPO_PAGO"
            ]
          }
        },
        "required": [
          "catalogs"
        ]
      },
      "SesCredential": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Credential set id, to assign from a property with sesCredentialId."
          },
          "name": {
            "type": "string"
          },
          "isDefault": {
            "type": "boolean"
          },
          "codigoArrendador": {
            "type": "string"
          },
          "usuario": {
            "type": "string"
          },
          "aplicacion": {
            "type": "string"
          },
          "tipoPago": {
            "type": "string"
          },
          "hasPassword": {
            "type": "boolean",
            "description": "Whether a password is stored. The password itself is never returned."
          },
          "createdAt": {
            "type": "string",
            "description": "When it was created. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "updatedAt": {
            "type": "string",
            "description": "Last change. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          }
        },
        "required": [
          "id",
          "name",
          "isDefault",
          "codigoArrendador",
          "usuario",
          "aplicacion",
          "tipoPago",
          "hasPassword",
          "createdAt",
          "updatedAt"
        ]
      },
      "SesCredentialListResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SesCredential"
            }
          }
        },
        "required": [
          "results"
        ]
      },
      "SesCredentialTestResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "true: SES accepted the credentials (a real catalogue call went through)."
          },
          "env": {
            "type": "string",
            "enum": [
              "pre",
              "prod"
            ],
            "description": "Environment the test ran against."
          },
          "details": {
            "type": "string",
            "description": "What happened, in Spanish: accepted, rejected (HTTP 401), unexpected answer, or no connection."
          }
        },
        "required": [
          "ok",
          "env",
          "details"
        ]
      },
      "SesStatusResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "in_progress",
              "completed",
              "signed",
              "submitted",
              "confirmed",
              "error",
              "cancelled"
            ],
            "description": "Where the check-in is: pending (no guest yet), in_progress (some guests registered), completed (all guests registered), signed (contract signed), submitted (report sent to SES, batch assigned), confirmed (SES returned a communication code for every guest), error (needs attention: see errorReason), cancelled."
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SesSubmission"
            },
            "description": "Every batch sent for this booking, newest first. A booking can have several: guests registered late go out in a later batch, and a date change annuls and files again."
          }
        },
        "required": [
          "status",
          "results"
        ]
      },
      "SesSubmission": {
        "type": "object",
        "properties": {
          "lote": {
            "nullable": true,
            "description": "Batch number assigned by SES; null when the HTTP call itself failed.",
            "type": "string"
          },
          "env": {
            "type": "string",
            "enum": [
              "pre",
              "prod",
              "test"
            ],
            "description": "Where the batch went: prod is the Ministry. test means it was answered by the test-mode simulator and never left Kinapi (see Test mode in the docs). pre is the Ministry test environment, used only by Kinapi internally."
          },
          "status": {
            "type": "string",
            "enum": [
              "sent",
              "polled_ok",
              "polled_error",
              "http_error"
            ],
            "description": "sent: accepted by SES, result pending (polled hourly). polled_ok: every communication accepted. polled_error: at least one rejected. http_error: the call to SES failed."
          },
          "codigoEstablecimiento": {
            "type": "string",
            "description": "Establishment code the batch was filed under."
          },
          "sentAt": {
            "type": "string",
            "description": "When the batch was sent. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          },
          "polledAt": {
            "nullable": true,
            "description": "When the result was read from SES; null while pending.",
            "type": "string"
          },
          "error": {
            "description": "Transport or batch-level error, when there was one.",
            "type": "string"
          },
          "annulment": {
            "nullable": true,
            "description": "Annulment of this batch with the Ministry, when the booking was cancelled or its dates changed. null when it was never annulled. A booking cancelled while its report was in flight is annulled as soon as the Ministry confirms it.",
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "retrying",
                  "abandoned",
                  "requested",
                  "confirmed",
                  "rejected"
                ],
                "description": "retrying: the Ministry did not accept the annulment request; Kinapi retries it every hour. abandoned: it was retried for three days without success, it needs a manual check in SES.HOSPEDAJES. requested: the Ministry accepted the request and has not processed it yet (it is checked every hour). confirmed: processed, the communications no longer stand. rejected: processed and rejected, the communications STILL stand (error says why)."
              },
              "reason": {
                "type": "string",
                "enum": [
                  "cancelled",
                  "dates_changed"
                ],
                "description": "Why it was annulled: the booking was cancelled, or its dates changed and it was filed again."
              },
              "lote": {
                "nullable": true,
                "description": "Batch number of the annulment request; null while it has not been accepted.",
                "type": "string"
              },
              "requestedAt": {
                "nullable": true,
                "description": "When the Ministry accepted the annulment request.",
                "type": "string"
              },
              "checkedAt": {
                "nullable": true,
                "description": "When its processing result was read.",
                "type": "string"
              },
              "attempts": {
                "type": "integer",
                "description": "Failed attempts so far."
              },
              "error": {
                "nullable": true,
                "description": "Why it was rejected, verbatim from the Ministry.",
                "type": "string"
              }
            },
            "required": [
              "status",
              "reason",
              "lote",
              "requestedAt",
              "checkedAt",
              "attempts",
              "error"
            ]
          },
          "comunicaciones": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "guestId": {
                  "type": "string"
                },
                "guestName": {
                  "type": "string"
                },
                "codigoComunicacion": {
                  "nullable": true,
                  "description": "Communication code the Ministry returned for this guest; null until confirmed or when rejected.",
                  "type": "string"
                },
                "errores": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Rejection messages verbatim from the Ministry, in Spanish."
                },
                "rechazos": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "codigo": {
                        "type": "string",
                        "enum": [
                          "documentoDuplicado",
                          "numPersonasNoCoincide",
                          "codigoPostalInvalido",
                          "campoInvalido"
                        ],
                        "description": "Stable code of the rejection, as Kinapi reads it."
                      },
                      "campo": {
                        "description": "Guest field to correct, when it can be determined (numeroDocumento, direccion.codigoPostal…).",
                        "type": "string"
                      },
                      "valor": {
                        "description": "The value the Ministry pointed at.",
                        "type": "string"
                      },
                      "comunicacion": {
                        "description": "1-based position of the communication inside the batch.",
                        "type": "integer"
                      }
                    },
                    "required": [
                      "codigo"
                    ],
                    "description": "A rejection translated into something a program can act on. Rejections Kinapi cannot classify are not here; they stay verbatim in errores."
                  }
                }
              },
              "required": [
                "guestId",
                "guestName",
                "codigoComunicacion",
                "errores",
                "rechazos"
              ]
            },
            "description": "One entry per guest in the batch."
          }
        },
        "required": [
          "lote",
          "env",
          "status",
          "codigoEstablecimiento",
          "sentAt",
          "polledAt",
          "annulment",
          "comunicaciones"
        ],
        "description": "One submission (batch) of the traveller report to SES Hospedajes."
      },
      "SesSubmitNowResponse": {
        "type": "object",
        "properties": {
          "submitted": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "lote": {
            "description": "Batch number assigned by SES.",
            "type": "string"
          },
          "details": {
            "type": "string",
            "description": "SES does not confirm on the spot: the per-guest result arrives with the hourly poll. Read it with GET on this same endpoint. In test mode the result is already there."
          }
        },
        "required": [
          "submitted",
          "details"
        ]
      },
      "Settings": {
        "type": "object",
        "properties": {
          "livemode": {
            "type": "boolean",
            "description": "false when the API key belongs to the test environment: nothing it does is filed with the Ministry or billed. Check it once at start-up to know which environment your integration is talking to."
          },
          "ses": {
            "type": "object",
            "properties": {
              "submitRequiresSignature": {
                "type": "boolean"
              }
            },
            "required": [
              "submitRequiresSignature"
            ]
          },
          "notifications": {
            "type": "object",
            "properties": {
              "locale": {
                "type": "string",
                "enum": [
                  "es",
                  "en"
                ],
                "description": "Language of the emails Kinapi sends you."
              },
              "telegram": {
                "type": "object",
                "properties": {
                  "configured": {
                    "type": "boolean",
                    "description": "Whether a bot token is stored. The token is never returned."
                  },
                  "chatId": {
                    "nullable": true,
                    "type": "string"
                  },
                  "topicId": {
                    "nullable": true,
                    "type": "string"
                  }
                },
                "required": [
                  "configured",
                  "chatId",
                  "topicId"
                ]
              },
              "guestInvitations": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "daysBefore": {
                    "type": "integer"
                  },
                  "reminder": {
                    "type": "boolean"
                  },
                  "lastCall": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "enabled",
                  "daysBefore",
                  "reminder",
                  "lastCall"
                ],
                "description": "What actually goes out at account level. Properties can override it."
              },
              "guestReplyTo": {
                "type": "string",
                "description": "Where guest replies go: the configured address, or the account contact email."
              },
              "guestReplyToConfigured": {
                "type": "boolean",
                "description": "false when guestReplyTo is the account contact email because none is set."
              }
            },
            "required": [
              "locale",
              "telegram",
              "guestInvitations",
              "guestReplyTo",
              "guestReplyToConfigured"
            ]
          },
          "guestEmails": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": {
                "type": "object",
                "properties": {
                  "subject": {
                    "description": "Subject, one line, up to 150 characters.",
                    "type": "string"
                  },
                  "headline": {
                    "description": "Headline inside the email, one line, up to 120 characters.",
                    "type": "string"
                  },
                  "body": {
                    "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                    "type": "string"
                  }
                },
                "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
              }
            },
            "description": "Your own texts, by email (invitation, reminder, lastCall) and language. Only what you customised appears."
          },
          "updatedAt": {
            "nullable": true,
            "description": "Last change; null if the account never changed a setting.",
            "type": "string"
          }
        },
        "required": [
          "livemode",
          "ses",
          "notifications",
          "guestEmails",
          "updatedAt"
        ]
      },
      "SimulateSesRequest": {
        "type": "object",
        "properties": {
          "result": {
            "type": "string",
            "enum": [
              "accepted",
              "rejected",
              "submit_error",
              "blocked"
            ],
            "description": "What the simulated Ministry answers. accepted: every guest gets a communication code (ses.submitted, then ses.confirmed). rejected: the guests are rejected with the chosen rejection (ses.submitted, then ses.error with phase poll). submit_error: the whole submission is refused, like a header error (ses.error with phase submit). blocked: the booking is held as if the account were suspended for non-payment (ses.blocked); the next nightly run lifts it."
          },
          "rejection": {
            "description": "Only with rejected. Which rejection, written exactly as the Ministry writes it: apellido1, nombre, numeroDocumento and fechaNacimiento are «Valor incorrecto para el campo …» on that field; documentoDuplicado, numPersonas and codigoPostal are the Ministry’s own messages for those cases; sinClasificar is a rejection Kinapi cannot translate (empty rechazos, message only in errores). Default apellido1.",
            "type": "string",
            "enum": [
              "apellido1",
              "nombre",
              "numeroDocumento",
              "fechaNacimiento",
              "documentoDuplicado",
              "numPersonas",
              "codigoPostal",
              "sinClasificar"
            ]
          },
          "guestIds": {
            "description": "Only with rejected: the guests to reject (ids from /guests). The rest of the submission is accepted. Default: every guest in the submission.",
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          }
        },
        "required": [
          "result"
        ],
        "description": "Test mode only. Files the report now, like POST /checkins/{reservationId}/ses, and makes the simulated Ministry answer what you ask. It needs the same as a real submission: registered guests not yet filed, an establishment code on the property and, if the account requires it, the signed contract."
      },
      "SimulateSesResponse": {
        "type": "object",
        "properties": {
          "simulated": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "result": {
            "type": "string",
            "enum": [
              "accepted",
              "rejected",
              "submit_error",
              "blocked"
            ]
          },
          "lote": {
            "nullable": true,
            "description": "Test batch number (PRUEBA-…); null for submit_error and blocked.",
            "type": "string"
          },
          "status": {
            "nullable": true,
            "description": "Booking status after the simulated answer.",
            "type": "string",
            "enum": [
              "pending",
              "in_progress",
              "completed",
              "signed",
              "submitted",
              "confirmed",
              "error",
              "cancelled"
            ]
          },
          "error": {
            "description": "With submit_error: the reason, as it comes in the ses.error webhook.",
            "type": "string"
          }
        },
        "required": [
          "simulated",
          "result",
          "lote",
          "status"
        ]
      },
      "TestEmail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "to": {
            "type": "string",
            "description": "Who it was addressed to."
          },
          "subject": {
            "type": "string",
            "description": "Subject as the recipient would see it live (without the [TEST] prefix)."
          },
          "kind": {
            "type": "string",
            "description": "Which email it is: guest_invitacion, guest_recordatorio, guest_ultima-llamada, contrato_firmado, registro_completo…"
          },
          "delivered": {
            "type": "boolean",
            "description": "true: it was sent, because the address belongs to your team. false: it stayed here; in test mode nothing goes to anyone else."
          },
          "attachments": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Names of the attachments (the signed contract PDF, for instance). Their content is not kept."
          },
          "text": {
            "type": "string",
            "description": "Plain-text version. The registration link of an invitation is in it."
          },
          "html": {
            "nullable": true,
            "description": "HTML version, with the logo embedded.",
            "type": "string"
          },
          "createdAt": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "to",
          "subject",
          "kind",
          "delivered",
          "attachments",
          "text",
          "html",
          "createdAt"
        ]
      },
      "TestEmailList": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TestEmail"
            },
            "description": "Newest first. Kept for 30 days."
          }
        },
        "required": [
          "results"
        ]
      },
      "TestResetResponse": {
        "type": "object",
        "properties": {
          "reset": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "checkins": {
            "type": "integer",
            "description": "Bookings deleted, with their guests, submissions and signatures."
          },
          "emails": {
            "type": "integer",
            "description": "Emails removed from the test inbox."
          }
        },
        "required": [
          "reset",
          "checkins",
          "emails"
        ]
      },
      "UpdateBrandingRequest": {
        "type": "object",
        "properties": {
          "primaryColor": {
            "description": "Brand colour as #RRGGBB. null returns to the Kinapi default.",
            "nullable": true,
            "type": "string"
          },
          "backgroundColor": {
            "description": "Background of the guest page as #RRGGBB; cards, borders and muted text are derived from it. null returns to the theme background.",
            "nullable": true,
            "type": "string"
          },
          "commercialName": {
            "description": "Trading name of your company, as guests know it. Shown in the header of guest emails, under the property name. null or empty string removes it.",
            "nullable": true,
            "type": "string",
            "maxLength": 80
          },
          "logo": {
            "description": "Logo as a base64 data URL (data:image/png;base64,…), PNG, JPEG or WebP, up to 1 MB. The type is detected from the bytes; SVG is refused. null removes it.",
            "nullable": true,
            "type": "string"
          }
        },
        "description": "Only what you send changes."
      },
      "UpdateCheckinRequest": {
        "type": "object",
        "properties": {
          "checkInDate": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "New arrival date. If the report has already been filed, Kinapi annuls it with SES and files it again with the new dates."
          },
          "checkOutDate": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "New departure date. If the report has already been filed, Kinapi annuls it with SES and files it again with the new dates."
          },
          "arrivalTime": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}$",
            "description": "New check-in time."
          },
          "departureTime": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}$",
            "description": "New check-out time."
          },
          "numGuests": {
            "description": "New expected number of guests.",
            "type": "integer",
            "minimum": 1,
            "maximum": 50
          },
          "houseRulesId": {
            "description": "New house rules set. Empty string unassigns it and hands control back to the property.",
            "type": "string"
          },
          "tipoPago": {
            "description": "New payment method (SES TIPO_PAGO code). Empty string falls back to the property. It changes what gets filed as long as the report has not gone out.",
            "type": "string"
          },
          "skipContract": {
            "description": "true: no contract terms of your own, only house rules and legal conditions.",
            "type": "boolean"
          },
          "manualSubmit": {
            "description": "true: keep the report out of the nightly submission, and out of the automatic refiling after a date change. false: release it.",
            "type": "boolean"
          },
          "source": {
            "description": "Origin of the booking, free text (airbnb, booking, direct…). Informational only. Empty string sets it back to unknown.",
            "type": "string"
          },
          "maxGuests": {
            "description": "Capacity ceiling for this booking, 1 to 50. Empty string or 0 removes it, so the property capacity applies again.",
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  ""
                ]
              },
              {
                "type": "integer",
                "minimum": 0,
                "maximum": 50
              }
            ]
          },
          "leadGuest": {
            "description": "Corrects the lead guest. MERGED field by field with what the booking has: send only what changed; an empty string clears that field.",
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "phone": {
                "type": "string"
              }
            }
          },
          "returnUrl": {
            "description": "https 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. https only and no embedded credentials; an invalid URL returns 400. Only the SHAPE is checked, not that it works: send it complete, including any trailing token or id. Empty string removes it.",
            "type": "string"
          },
          "locale": {
            "description": "New language for the form, the contract and the emails. Empty string returns the booking to the property default.",
            "type": "string"
          },
          "contractTemplateId": {
            "description": "New contract template. Empty string falls back to the property template. If the contract is already signed nothing is rewritten: the response carries a warning and the change only affects later signatures.",
            "type": "string"
          }
        },
        "description": "All fields optional: only what you send changes. Changing checkInDate or checkOutDate on a booking whose report was already filed annuls that filing with SES and files it again."
      },
      "UpdateContractTemplateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "isDefault": {
            "description": "Mark it as the account default. Only one template can be the default.",
            "type": "boolean"
          },
          "showOwnerInfo": {
            "description": "Print the owner details section on the PDF. Default true.",
            "type": "boolean"
          },
          "showOwnerDocument": {
            "description": "Print the owner tax number. Only applies when showOwnerInfo is true. Default true.",
            "type": "boolean"
          },
          "showPropertyInfo": {
            "description": "Print the property details section on the PDF. Default true.",
            "type": "boolean"
          },
          "houseRules": {
            "description": "House rules printed by this template when no house rules set applies. Omitted on creation: Kinapi standard rules.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClauseInput"
            }
          },
          "legalClauses": {
            "description": "Contract terms, clause by clause. Omitted on creation: Kinapi standard clauses.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClauseInput"
            }
          },
          "bodyMode": {
            "type": "string",
            "enum": [
              "clauses",
              "document"
            ],
            "description": "clauses: the text is a list of bilingual clauses. document: you paste the whole text, one per language, in body. Clauses are kept when switching modes."
          },
          "body": {
            "description": "Merged per language: sending only nl does not erase the es already written.",
            "allOf": [
              {
                "$ref": "#/components/schemas/LocalizedBody"
              }
            ]
          }
        },
        "description": "All fields optional: only what you send changes."
      },
      "UpdateHouseRulesRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "isDefault": {
            "type": "boolean"
          },
          "clauses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClauseInput"
            }
          },
          "bodyMode": {
            "type": "string",
            "enum": [
              "clauses",
              "document"
            ],
            "description": "clauses: a list of bilingual rules. document: the whole text, one per language, in body. Clauses are kept when switching modes."
          },
          "body": {
            "description": "Merged per language: sending only nl does not erase the es already written.",
            "allOf": [
              {
                "$ref": "#/components/schemas/LocalizedBody"
              }
            ]
          }
        },
        "description": "All fields optional: only what you send changes."
      },
      "UpdateOwnerRequest": {
        "type": "object",
        "properties": {
          "legalName": {
            "type": "string",
            "minLength": 1
          },
          "nif": {
            "type": "string"
          },
          "fiscalAddress": {
            "type": "string"
          }
        },
        "description": "All fields optional. Changes show on every property linked to this owner."
      },
      "UpdatePropertyRequest": {
        "type": "object",
        "properties": {
          "nickname": {
            "type": "string",
            "minLength": 1
          },
          "listingId": {
            "description": "New listing id.",
            "type": "string",
            "minLength": 1
          },
          "commercialName": {
            "description": "Trading name of the property: 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). Empty string removes it.",
            "type": "string",
            "maxLength": 80
          },
          "channelListingIds": {
            "description": "Ids of channel clones (Airbnb, Booking, Vrbo…) that resolve to this property when creating check-ins.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "address": {
            "type": "object",
            "properties": {
              "street": {
                "type": "string"
              },
              "city": {
                "type": "string"
              },
              "province": {
                "type": "string"
              },
              "postalCode": {
                "type": "string"
              }
            },
            "description": "Postal address of the accommodation. It is printed on the contract."
          },
          "touristLicense": {
            "description": "Tourist licence number (e.g. VFT/CA/12345).",
            "type": "string"
          },
          "maxGuests": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  ""
                ]
              },
              {
                "type": "integer",
                "minimum": 0,
                "maximum": 50
              }
            ],
            "description": "Capacity of the accommodation, 1 to 50. Bookings inherit it when they do not carry their own: it is the ceiling up to which the guest can correct the party size from their link. Empty string or 0 removes it."
          },
          "communicationLocale": {
            "type": "string",
            "description": "Default language of the accommodation: es, en, de, fr, nl, it or pt. Bookings that do not send a locale inherit it, and it decides the guest form, the contract and the emails at once. Normalised (nl-NL becomes nl); an unsupported value falls back to Spanish without an error. Empty string unassigns it."
          },
          "houseRulesId": {
            "description": "House rules set assigned to this property (id from /house-rules). A booking can override it with its own. Empty string unassigns it.",
            "type": "string"
          },
          "notifications": {
            "type": "object",
            "properties": {
              "invitacion": {
                "description": "Email the guest an invitation to register.",
                "type": "boolean"
              },
              "recordatorio": {
                "description": "Second email if the guest has not registered yet.",
                "type": "boolean"
              },
              "completo": {
                "description": "Email you when every guest of a booking has registered.",
                "type": "boolean"
              },
              "ultimoAviso": {
                "description": "Last notice to the guest on the morning of arrival, if they have not registered yet.",
                "type": "boolean"
              }
            },
            "description": "Email notices for THIS property, over the account settings. Each field is three-state: absent inherits the account setting, true and false decide on their own. The reminder and the last notice do not depend on the invitation: keep them on with the invitation off if you send the link yourself. Send an empty object to clear the property settings and inherit again."
          },
          "houseRules": {
            "maxItems": 50,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "es": {
                  "type": "string",
                  "maxLength": 500
                },
                "en": {
                  "default": "",
                  "type": "string",
                  "maxLength": 500
                }
              },
              "required": [
                "es"
              ]
            },
            "description": "LEGACY. House rules written inside the property. Still accepted, but they only apply when no house rules set is assigned at any level. New integrations should use houseRulesId instead. An empty array clears them."
          },
          "ses": {
            "type": "object",
            "properties": {
              "codigoEstablecimiento": {
                "description": "Establishment code at SES Hospedajes. Without it no report can be filed for this property.",
                "type": "string"
              },
              "tipoPago": {
                "description": "Default payment method for this property, SES TIPO_PAGO code. Without it the credential set default applies. A tipoPago on a booking overrides it.",
                "type": "string"
              }
            },
            "description": "SES Hospedajes settings of the property."
          },
          "ownerId": {
            "description": "Id of an existing owner (from /owners). One owner can be on several properties. Takes precedence over owner. On PATCH, empty string unlinks the owner.",
            "type": "string"
          },
          "owner": {
            "type": "object",
            "properties": {
              "legalName": {
                "type": "string",
                "minLength": 1,
                "description": "Legal name of the owner (person or company)."
              },
              "nif": {
                "description": "Spanish tax number (NIF/CIF).",
                "type": "string"
              },
              "fiscalAddress": {
                "description": "Registered address.",
                "type": "string"
              }
            },
            "required": [
              "legalName"
            ],
            "description": "Creates (or, on PATCH, updates) the owner inline and links it to the property. Alternative to ownerId."
          },
          "contractTemplateId": {
            "description": "Contract template assigned to this property (id from /contract-templates). Without it the account default applies. Empty string unassigns it.",
            "type": "string"
          },
          "sesCredentialId": {
            "description": "SES credential set used for this property (id from /ses-credentials). Without it the account default applies. Empty string unassigns it.",
            "type": "string"
          },
          "active": {
            "description": "false retires the property: out of the listing (unless includeInactive), the panel selectors and the calendar sweep. Existing bookings still resolve to it. true reactivates it.",
            "type": "boolean"
          }
        },
        "description": "All fields optional: only what you send changes."
      },
      "UpdateSesCredentialRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "isDefault": {
            "type": "boolean"
          },
          "codigoArrendador": {
            "type": "string",
            "minLength": 1
          },
          "usuario": {
            "type": "string",
            "minLength": 1
          },
          "aplicacion": {
            "type": "string",
            "minLength": 1
          },
          "tipoPago": {
            "type": "string",
            "minLength": 1
          },
          "contrasena": {
            "description": "New password. Empty or omitted keeps the stored one.",
            "type": "string"
          }
        },
        "description": "All fields optional: only what you send changes."
      },
      "UpdateSettingsRequest": {
        "type": "object",
        "properties": {
          "ses": {
            "type": "object",
            "properties": {
              "submitRequiresSignature": {
                "type": "boolean",
                "description": "true: only check-ins whose contract is signed are filed with SES. false: the report goes out with whoever has registered."
              }
            },
            "required": [
              "submitRequiresSignature"
            ]
          },
          "notifications": {
            "type": "object",
            "properties": {
              "locale": {
                "description": "Language of the emails Kinapi sends YOU (es or en). Nothing the guest sees.",
                "type": "string",
                "enum": [
                  "es",
                  "en"
                ]
              },
              "guestInvitations": {
                "description": "Guest emails. Each one goes only to guests who have not registered yet, and only if the booking has the lead guest email. Fields you leave out keep their value. Properties can override these with their own notifications.",
                "type": "object",
                "properties": {
                  "enabled": {
                    "description": "Invitation email with the registration link, daysBefore days before arrival (straight away for bookings created later than that).",
                    "type": "boolean"
                  },
                  "daysBefore": {
                    "description": "Days before arrival for the invitation, 1 to 30.",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 30
                  },
                  "reminder": {
                    "description": "Reminder two days before arrival. Independent of the invitation: keep it on with the invitation off if you send the link yourself.",
                    "type": "boolean"
                  },
                  "lastCall": {
                    "description": "Last notice on the morning of arrival. Also independent of the invitation.",
                    "type": "boolean"
                  }
                }
              },
              "guestReplyTo": {
                "description": "Address guest replies go to (invitation, reminders, signed contract). null or empty string returns to the account contact email.",
                "nullable": true,
                "type": "string"
              },
              "telegram": {
                "description": "Notices to your own Telegram bot. null switches them off.",
                "nullable": true,
                "type": "object",
                "properties": {
                  "botToken": {
                    "description": "Bot token. Omitted keeps the stored one; required the first time.",
                    "type": "string",
                    "minLength": 1
                  },
                  "chatId": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Chat the notices go to."
                  },
                  "topicId": {
                    "description": "Topic inside the chat, or null.",
                    "nullable": true,
                    "type": "string"
                  }
                },
                "required": [
                  "chatId"
                ]
              }
            }
          },
          "guestEmails": {
            "type": "object",
            "properties": {
              "invitation": {
                "type": "object",
                "properties": {
                  "es": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "en": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "de": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "fr": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "nl": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "it": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "pt": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  }
                },
                "description": "One entry per language (es, en, de, fr, nl, it, pt). null removes that language and the Kinapi text goes out again; an absent language is not touched."
              },
              "reminder": {
                "type": "object",
                "properties": {
                  "es": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "en": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "de": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "fr": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "nl": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "it": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "pt": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  }
                },
                "description": "One entry per language (es, en, de, fr, nl, it, pt). null removes that language and the Kinapi text goes out again; an absent language is not touched."
              },
              "lastCall": {
                "type": "object",
                "properties": {
                  "es": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "en": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "de": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "fr": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "nl": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "it": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  },
                  "pt": {
                    "nullable": true,
                    "type": "object",
                    "properties": {
                      "subject": {
                        "description": "Subject, one line, up to 150 characters.",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Headline inside the email, one line, up to 120 characters.",
                        "type": "string"
                      },
                      "body": {
                        "description": "Opening text, up to 2000 characters and 8 paragraphs separated by a blank line.",
                        "type": "string"
                      }
                    },
                    "description": "Your own text for one email in one language. Empty or absent fields go out with the Kinapi text. Variables: {nombre} (lead guest name; removed when unknown), {alojamiento} (trading name of the property) and {fecha_entrada} (arrival date). No links or web addresses: the only link is the button. The button, the documents needed, the arrival date and the legal notice always stay."
                  }
                },
                "description": "One entry per language (es, en, de, fr, nl, it, pt). null removes that language and the Kinapi text goes out again; an absent language is not touched."
              }
            },
            "description": "Your own texts for the three guest emails: invitation, reminder (two days before arrival) and last notice (arrival day)."
          }
        },
        "description": "All fields optional: only what you send changes. Other notification settings are untouched."
      },
      "UsageResponse": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string",
            "description": "YYYY-MM, UTC."
          },
          "partes": {
            "type": "integer",
            "description": "Guests confirmed by SES this month, free-quota ones included."
          },
          "unitAmountCents": {
            "type": "integer",
            "description": "Price per booking, in cents, VAT excluded."
          },
          "currency": {
            "type": "string",
            "enum": [
              "eur"
            ]
          },
          "freePartes": {
            "type": "integer",
            "description": "Reports covered by the free quota."
          },
          "estimatedCents": {
            "type": "integer",
            "description": "Estimated amount for the month, VAT excluded."
          },
          "note": {
            "type": "string"
          },
          "byDay": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Reports per day (YYYY-MM-DD)."
          },
          "history": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "month": {
                  "type": "string"
                },
                "partes": {
                  "type": "integer"
                },
                "estimatedCents": {
                  "type": "integer"
                }
              },
              "required": [
                "month",
                "partes",
                "estimatedCents"
              ]
            },
            "description": "Billable usage of the last six months."
          }
        },
        "required": [
          "month",
          "partes",
          "unitAmountCents",
          "currency",
          "freePartes",
          "estimatedCents",
          "note",
          "byDay",
          "history"
        ]
      },
      "WebhookConfig": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "secret": {
            "type": "string",
            "description": "Signing secret. Returned because you need it to verify deliveries."
          }
        },
        "required": [
          "url",
          "enabled",
          "events",
          "secret"
        ]
      },
      "WebhookDeliveriesResponse": {
        "type": "object",
        "properties": {
          "deliveries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookDelivery"
            },
            "description": "Most recent first. Kept for 30 days."
          }
        },
        "required": [
          "deliveries"
        ]
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "attempt": {
            "type": "integer",
            "description": "1-based; each retry is its own entry."
          },
          "status": {
            "nullable": true,
            "description": "HTTP status your endpoint returned; null on network failure or timeout.",
            "type": "integer"
          },
          "ok": {
            "type": "boolean"
          },
          "durationMs": {
            "type": "integer"
          },
          "error": {
            "nullable": true,
            "type": "string"
          },
          "responseBody": {
            "nullable": true,
            "description": "First bytes of your response, to debug broken receivers.",
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "description": "When the attempt was made. ISO 8601 timestamp in UTC (e.g. 2026-07-19T10:12:00.000Z)."
          }
        },
        "required": [
          "event",
          "url",
          "attempt",
          "status",
          "ok",
          "durationMs",
          "error",
          "responseBody",
          "createdAt"
        ]
      },
      "WebhookResponse": {
        "type": "object",
        "properties": {
          "webhook": {
            "nullable": true,
            "description": "null when no webhook is configured.",
            "allOf": [
              {
                "$ref": "#/components/schemas/WebhookConfig"
              }
            ],
            "type": "object"
          },
          "availableEvents": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "label",
                "description"
              ]
            },
            "description": "Catalogue of events with their label and description, in the language asked for with ?locale (es or en; Accept-Language otherwise)."
          }
        },
        "required": [
          "webhook",
          "availableEvents"
        ]
      },
      "WebhookSavedResponse": {
        "type": "object",
        "properties": {
          "webhook": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/WebhookConfig"
              }
            ],
            "type": "object"
          }
        },
        "required": [
          "webhook"
        ]
      },
      "WebhookTestResponse": {
        "type": "object",
        "properties": {
          "sent": {
            "type": "boolean",
            "description": "Whether a test delivery was attempted."
          },
          "delivery": {
            "nullable": true,
            "description": "Result of the attempt, or null if none was made.",
            "allOf": [
              {
                "$ref": "#/components/schemas/WebhookDelivery"
              }
            ],
            "type": "object"
          }
        },
        "required": [
          "sent",
          "delivery"
        ]
      }
    }
  }
}
