Errors
Standard HTTP codes and consistent error bodies across the whole API.
Status codes
| Field | Type | Description |
|---|---|---|
200 | OK | The request succeeded. |
201 | Created | Resource created (for example a new check-in). |
400 | Bad Request | Invalid JSON or failed validation — the detail names the exact field. |
401 | Unauthorized | The API key is missing or invalid (the gateway rejects it). |
402 | Payment Required | You have used up the free trial bookings. Add a card to keep creating bookings; anything already filed is unaffected. |
403 | Forbidden | Account suspended for non-payment (tenant_suspended): settle the payment in the panel (Billing) to reactivate the service. Or an account closure is pending (account_closing): only /exports answers until the account is reactivated or the closure takes effect. |
404 | Not Found | The resource does not exist in your account (or is not available yet — an unsigned contract, for instance). |
409 | Conflict | The operation does not fit the resource’s current state (adding a guest to a full booking, deleting one whose report has already been filed, changing the dates of a booking whose report SES has not confirmed yet, or downloading an export that is still being generated). The error field says which. |
410 | Gone | The guest link has expired (14 days after check-out), on the public /c/… routes; or, on the v1 API, an export’s ZIP has already been deleted (seven days after it was ready). |
429 | Too Many Requests | Daily check-in creation quota reached (it resets at midnight UTC). If your real operation needs more, get in touch and we will raise it. |
500 | Internal | Our fault. Retry; if it persists, get in touch. |
502 | Bad Gateway | An external system failed (SES Hospedajes rejected the call or did not answer). It is not a problem with your request: retry later. |
503 | Service Unavailable | SES Hospedajes did not answer an operation that has to succeed BEFORE your change is saved: annulling a report to change the dates of an already filed booking. Nothing was saved; retry later. |
Specific error codes
Beyond the HTTP status, the error field in the body identifies the exact cause so you can branch without parsing prose:
| Field | Type | Description |
|---|---|---|
payment_method_required | 402 | You have used up the free trial bookings. Add a card in the panel (Settings → Billing) to keep creating bookings. Anything already filed is unaffected: reports already sent stay sent and the panel stays accessible. Only POST /checkins returns it. |
tenant_suspended | 403 | Account suspended for non-payment. The whole v1 API is blocked until it is settled. |
account_closing | 403 | The account closure has been requested. During the 30-day grace period the whole v1 API answers this except /exports, so you can take your records with you. effectiveAt says when the closure becomes final; reactivating the account from the panel brings everything back. See Exports and offboarding. |
validation | 400 | Invalid data. Comes with issues[] or details naming the field. |
invalid_url | 400 | Webhook URL rejected: it must be https and point at a public server (internal networks are blocked). |
full | 409 | The booking already has as many registered guests as confirmed places. |
already_submitted | 409 | The guest cannot be deleted: their report has already been filed with SES. |
refiling_blocked | 409 / 503 | You asked to change the dates of a booking whose report was already filed, and Kinapi could not annul it to file it again. Nothing was saved. The code field says why: en_vuelo (409), the report has been sent and SES has not confirmed it yet, retry in an hour; anulacion_fallida (503), SES did not accept the annulment or did not answer, retry later. |
not_ready | 409 | The report cannot be sent yet (no guests, no signature where one is required, or a property with no establishment code). Or, on GET /exports/{id}/download, the export is still being generated or failed. The reason is in details. |
not_configured / disabled | 409 | A webhook test was requested but none is configured, or it is switched off. |
no_billing | 409 | The billing portal was requested but the account has no payment set up yet. |
no_guests | 404 | The traveller report was requested for a check-in that has no guests yet. |
no_timestamp | 404 | The timestamp token was requested for an evidence record that has none yet. details says why; it is retried every hour. |
ses_error | 502 | SES Hospedajes rejected the submission or did not answer. The detail is in details. |
link_expired | 410 | Expired guest link (public /c/… routes). |
expired | 410 | You asked to download an export whose ZIP has already been deleted (seven days after it was ready). Request a new one with POST /exports. |
Error format
Validation errors spell out each problematic field:
400 Bad Request
{
"error": "validation",
"details": {
"fieldErrors": {
"checkInDate": ["Invalid string: must match pattern"]
}
}
}404 Not Found
{ "error": "not found" }When an account closure is pending, the body also carries the date it becomes final:
403 Forbidden
{
"error": "account_closing",
"details": "La baja de la cuenta está pedida y se hará efectiva el 2026-11-13. Hasta entonces la API solo permite exportar los registros (/exports); para volver a usarla, reactiva la cuenta desde el panel.",
"effectiveAt": "2026-11-13T09:00:00.000Z"
}Retries
Creating a check-in is idempotent: retrying a POST /checkins with the same reservationId returns the existing check-in (200) instead of duplicating it. On a 500 or a timeout, retry with exponential backoff without worrying.