Errors

Standard HTTP codes and consistent error bodies across the whole API.

Status codes

FieldTypeDescription
200OKThe request succeeded.
201CreatedResource created (for example a new check-in).
400Bad RequestInvalid JSON or failed validation — the detail names the exact field.
401UnauthorizedThe API key is missing or invalid (the gateway rejects it).
402Payment RequiredYou have used up the free trial bookings. Add a card to keep creating bookings; anything already filed is unaffected.
403ForbiddenAccount 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.
404Not FoundThe resource does not exist in your account (or is not available yet — an unsigned contract, for instance).
409ConflictThe 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.
410GoneThe 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).
429Too Many RequestsDaily check-in creation quota reached (it resets at midnight UTC). If your real operation needs more, get in touch and we will raise it.
500InternalOur fault. Retry; if it persists, get in touch.
502Bad GatewayAn external system failed (SES Hospedajes rejected the call or did not answer). It is not a problem with your request: retry later.
503Service UnavailableSES 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:

FieldTypeDescription
payment_method_required402You 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_suspended403Account suspended for non-payment. The whole v1 API is blocked until it is settled.
account_closing403The 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.
validation400Invalid data. Comes with issues[] or details naming the field.
invalid_url400Webhook URL rejected: it must be https and point at a public server (internal networks are blocked).
full409The booking already has as many registered guests as confirmed places.
already_submitted409The guest cannot be deleted: their report has already been filed with SES.
refiling_blocked409 / 503You 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_ready409The 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 / disabled409A webhook test was requested but none is configured, or it is switched off.
no_billing409The billing portal was requested but the account has no payment set up yet.
no_guests404The traveller report was requested for a check-in that has no guests yet.
no_timestamp404The timestamp token was requested for an evidence record that has none yet. details says why; it is retried every hour.
ses_error502SES Hospedajes rejected the submission or did not answer. The detail is in details.
link_expired410Expired guest link (public /c/… routes).
expired410You 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.