Skip to main content

Error Handling

This guide documents the standard error response format, what each HTTP status code means in the KISS API, and how to troubleshoot common issues.


Error Response Format

All error responses follow a consistent JSON structure:

{
"message": "A human-readable description of what went wrong."
}

For validation errors, the response includes field-level details:

{
"message": "Validation failed.",
"errors": {
"units.0.crm_unit_id": ["The crm_unit_id field is required."],
"units.0.occupied": ["The occupied field is required."]
}
}

The errors object maps field paths to arrays of error messages. For nested fields, the path uses dot notation (e.g., units.0.tenant.phone).


HTTP Status Codes

StatusNameWhen you'll see it
200OKRequest succeeded
201CreatedResource created (e.g., log entry)
400Bad RequestMalformed JSON or invalid request structure
401UnauthorizedMissing, invalid, or expired token
403ForbiddenToken is valid but lacks permission for this resource
404Not FoundThe resource doesn't exist (e.g., wrong lock ID or entry point ID)
422Unprocessable EntityRequest is well-formed but fails validation (missing required fields, invalid values)
409ConflictSource-type collision (a push write against a pull-owned unit, or vice versa) or Idempotency-Key reused with a different payload.
429Too Many RequestsRate limit exceeded. Wait and retry.
500Server ErrorSomething went wrong on our end. If this persists, contact support.
501Not ImplementedEndpoint exists but isn't available yet (e.g., webhook registration)

Common Mistakes and Fixes

Syncing a unit without required fields

Error:

{
"message": "Validation failed.",
"errors": {
"units.0.crm_unit_id": ["The crm_unit_id field is required."],
"units.0.occupied": ["The occupied field is required."]
}
}

Fix: Every unit in the sync payload must include crm_unit_id and occupied. If occupied is true, you also need a tenant object with pms_tenant_id, first_name, and last_name.


Syncing an occupied unit without tenant info

Error:

{
"message": "Validation failed.",
"errors": {
"units.0.tenant": ["The tenant field is required when occupied is true."]
}
}

Fix: When occupied is true, include the tenant object:

{
"crm_unit_id": "PMS-U-1001",
"occupied": true,
"tenant": {
"pms_tenant_id": "PMS-T-5001",
"first_name": "Jane",
"last_name": "Smith"
}
}

Syncing a move-in without move_in_date

Error:

{
"message": "Validation failed.",
"errors": {
"units.0.move_in_date": ["The move_in_date field is required when occupied is true."]
}
}

Fix: When occupied is true, include move_in_date alongside the tenant object.


Using an expired or invalid OTP

Error:

{
"message": "The OTP code is invalid or has expired."
}

Fix: The OTP has expired or was entered incorrectly. Request a new one via POST /auth/phone and try again.


Using an expired Bearer token

Error:

{
"message": "Unauthorized."
}

Fix: The tenant's Bearer token has expired. Re-authenticate through the OTP flow (POST /auth/phone then POST /auth/verify-otp) to get a new token. There is no refresh token mechanism.


Sending a request without a token

Error:

{
"message": "Unauthorized."
}

Fix: Include the token in the Authorization header:

Authorization: Bearer YOUR_TOKEN

Phone number not found

Error:

{
"message": "A tenant with the submitted phone number does not exist."
}

Fix: The phone number is not associated with any tenant in KISS. Verify the tenant was synced from the PMS with a valid phone number, or check for typos in the country code / phone number.


Lock not found

Error:

{
"message": "Lock not found."
}

Fix: The lock_id in the URL doesn't match any resource. Double-check the ID from the GET /tenant/access response.


Entry point not found

Error:

{
"message": "Entry point not found."
}

Fix: The entry_point_id in the URL doesn't match any resource. Double-check the ID from the GET /tenant/access response.


Rate limited

Error:

{
"message": "Too many attempts. Please try again in 60 seconds."
}

Fix: You've exceeded the rate limit. Wait for the window to reset and retry. See the rate limits table for limits per endpoint. The response includes a Retry-After header indicating how long to wait.


Idempotency-Key reused with a different payload

Error (HTTP 409):

{
"message": "Idempotency-Key reused with a different request."
}

Fix: You sent two requests with the same Idempotency-Key header but different bodies. The key is tied to a specific logical operation — reusing it with different data is a client-side bug. Generate a new unique value for each new logical operation; reuse the same value only when retrying the exact same request.


Source-type conflict

Error (HTTP 409):

{
"message": "This unit is managed by a pull-mode PMS integration. Contact KISS support to change."
}

Fix: The unit you're trying to write to was created by a different integration mode (pull, standalone). A push write cannot silently take over a pull-owned unit — the source-of-truth rule protects partner data. If you need to migrate a unit between source types, reach out to KISS support.


Sending duplicate sync data

This is not an error. The sync endpoint is idempotent. Posting the same data multiple times produces the same result with no side effects. The response will show unchanged for units that didn't change:

{
"message": "Sync completed.",
"data": {
"synced_at": "2026-04-07T14:30:00Z",
"total": 3,
"created": 0,
"updated": 0,
"unchanged": 3,
"errors": []
}
}

No need to check if a unit or tenant exists before syncing. Just send your current state and KISS reconciles.


Debugging Tips

1. Check the health endpoint first

If something isn't working, start here:

curl https://api.keepitsimplestorage.com/api/v2/health

If this returns a 200, the API is up and the issue is in your request. If it doesn't respond, the API may be down.

2. Check your token

A 401 on every request usually means your token is wrong or expired. For PMS integrators, verify the API token in the KISS dashboard under Company > API. For tenant tokens, re-run the OTP flow.

3. Read the error message

KISS error messages are specific. "The crm_unit_id field is required" tells you exactly what's missing. Check your payload against the OpenAPI reference (link in the intro page — moving to a code-derived viewer).

4. Check field paths in validation errors

Validation errors include the exact path to the problem field. units.2.tenant.phone means the third unit in your array has an issue with the tenant's phone field.

5. Test with a minimal payload

If a large sync is failing, reduce to a single unit with only required fields to isolate the problem:

{
"units": [
{
"crm_unit_id": "TEST-001",
"occupied": false
}
]
}

If this works, add fields back one at a time until you find what's causing the error.