# Errors and limits

## Status codes

| Code | Meaning |
|---|---|
| 200, 201 | Success. Creating something answers 201 where noted. |
| 401 | The token is missing, malformed, expired or revoked. |
| 403 | The token is valid but lacks the scope, or the account has the module switched off (`error` explains which). |
| 404 | No such record **in your account**. Another account's id looks the same, on purpose. |
| 422 | Validation failed. |
| 429 | Rate limit reached. Wait for the `Retry-After` header. |
| 500 | Something broke on our side. Safe to retry reads; check before retrying a write. |

## Error body

Most errors are small:

```json
{ "error": "Please provide valid contact data" }
```

Scope errors name the scope:

```json
{ "error": "insufficient_scope", "error_description": "This token does not have the \"contacts:write\" scope." }
```

Endpoints that use standard Laravel validation answer 422 with every failing field:

```json
{
  "message": "The subject field is required.",
  "errors": { "subject": ["The subject field is required."] }
}
```

Handle all three shapes: read `error`, then `message`, and use `errors` for field-level display.

## Rate limits

| Surface | Limit |
|---|---|
| Account-scoped API | 60 requests per minute per token |
| `POST /platform/contacts/sync` | 30 per minute per token, on top of the above |
| MCP tool calls | 120 per minute per connection |
| OAuth token endpoint | per IP, see `Retry-After` |

Every response carries `X-RateLimit-Limit` and `X-RateLimit-Remaining`. On `429`, wait for `Retry-After` seconds, then retry with a growing delay. Prefer the batch endpoints (`/platform/contacts/sync` takes 200 contacts a call) over many single calls.

## Idempotency and retries

- `GET` is always safe to retry.
- Writes are not deduplicated by KLCT. If a `POST` times out, read the resource back before sending it again so you do not create a duplicate ticket or contact.
- Webhook deliveries can arrive more than once. De-duplicate on the delivery `id`. See [Webhooks](webhooks).

## Data you can and cannot change

The API creates and updates. It does not hard-delete customer data (contacts, tickets, conversations). Removing records stays a decision a person makes inside KLCT.
