# Errors

Every REST API error uses the same envelope. `name` is stable and meant for code; `message` is an English sentence for humans and may change.

```json
{
  "statusCode": 422,
  "name": "validation_error",
  "message": "to: Invalid email address"
}
```

As a rule, `4xx` means fix the request before sending it again; `429` and `5xx` are temporary, so retry with backoff (see [Limits](https://hatang.dev/docs/reference/limits.md)). See also the [error handling guide](https://hatang.dev/docs/guides/getting-started/errors.md).

## REST API

| Code (`name`) | HTTP | Meaning and what to do |
|---|---|---|
| `validation_error` | 422 | The request body or a parameter is invalid; `message` names the field. |
| `missing_required_field` | 422 | A required field is missing. |
| `missing_api_key` | 401 | No `Authorization: Bearer` header. |
| `invalid_api_key` | 403 | The API key is unknown or revoked. |
| `restricted_api_key` | 401 | The key lacks the scope this endpoint needs, or is locked to other origins. |
| `invalid_from_address` | 403 | The `from` domain is not verified for this business (or not allowed for this key). |
| `not_found` | 404 | The resource does not exist or belongs to another business. |
| `method_not_allowed` | 405 | The HTTP method is not supported on this path. |
| `invalid_idempotent_request` | 400 | The `Idempotency-Key` was used before with a different body. |
| `concurrent_idempotent_requests` | 409 | Another request with the same `Idempotency-Key` is still running; retry shortly. |
| `rate_limit_exceeded` | 429 | Too many requests; wait for `retry-after` seconds. |
| `daily_quota_exceeded` | 429 | The plan's daily sending quota is used up. |
| `invalid_attachment` | 422 | An attachment is malformed, unreachable (`path`) or too large. |
| `application_error` | 500 | Something failed on our side; retry with backoff. |
| `internal_server_error` | 500 | Unexpected error; retry with backoff and contact support if it persists. |
| `origin_not_allowed` | 403 | The browser origin is not one of the site's domain or allowed origins. |
| `invalid_visitor_token` | 401 | The visitor token is missing, expired or for another site; bootstrap again. |
| `invalid_agent_token` | 401 | The agent token is missing or expired; sign in again. |
| `site_disabled` | 403 | The chat site is turned off. |
| `invalid_app_key` | 401 | The mobile app key (`hak_…`) is unknown or for another site. |
| `app_key_revoked` | 403 | The mobile app key was revoked; ship a build with a new key. |
| `message_too_long` | 422 | The message is longer than allowed (see Limits). |
| `unsupported_file_type` | 422 | This file type cannot be uploaded. |
| `file_too_large` | 413 | The file is larger than allowed (see Limits). |
| `conversation_closed` | 409 | The conversation is closed and takes no new messages. |
| `product_not_enabled` | 403 | The product is not available to this business. |
| `product_disabled` | 403 | The business switched this product off (Settings → Modules). |
| `ai_budget_exhausted` | 429 |  |
| `ai_not_configured` | 503 |  |
| `ai_unavailable` | 503 |  |
| `instructions_full` | 409 |  |
| `forbidden` | 403 | You are not allowed to do this. |
| `last_owner` | 409 | A business must keep at least one owner. |
| `domain_taken` | 409 | The domain is already used by another business. |
| `host_missing` | 422 | A host name is required. |
| `host_mismatch` | 422 | The host does not match the domain. |
| `slug_taken` | 409 | The slug is already in use. |
| `slug_reserved` | 422 | The slug is reserved. |
| `environment_mismatch` | 403 | The resource belongs to the other environment (development vs production). |
| `no_bank_connection` | 422 | Link a bank account before creating payments. |
| `code_taken` | 409 | The order or payment code is already used. |
| `bank_link_failed` | 422 | The bank refused the link; check the account details. |
| `invalid_otp` | 422 | The confirmation code is wrong. |
| `otp_expired` | 410 | The confirmation code expired; request a new one. |
| `invalid_state` | 409 | The resource is not in a state that allows this action. |
| `provider_unavailable` | 503 | The bank or invoice provider is not answering; retry later. |
| `invoice_provider_error` | 422 | The e-invoice provider rejected the request; `message` has its reason. |

## Realtime protocol

`error` frames on the websocket carry one of these in `d.code`; codes shared with the REST API mean the same.

| Code | Meaning |
|---|---|
| `validation_error` | The request body or a parameter is invalid; `message` names the field. |
| `rate_limit_exceeded` | Too many requests; wait for `retry-after` seconds. |
| `not_found` | The resource does not exist or belongs to another business. |
| `unauthenticated` | A frame arrived before `auth` succeeded. |
| `invalid_visitor_token` | The visitor token is missing, expired or for another site; bootstrap again. |
| `origin_not_allowed` | The browser origin is not one of the site's domain or allowed origins. |
| `site_disabled` | The chat site is turned off. |
| `invalid_app_key` | The mobile app key (`hak_…`) is unknown or for another site. |
| `app_key_revoked` | The mobile app key was revoked; ship a build with a new key. |
| `conversation_closed` | The conversation is closed and takes no new messages. |
| `file_too_large` | The file is larger than allowed (see Limits). |
| `unsupported_file_type` | This file type cannot be uploaded. |
| `bad_frame` | The frame is not valid JSON or does not match the protocol schema. |
| `error` | Any other failure for one request. |

## SMTP relay

| Reply | Meaning |
|---|---|
| `535 5.7.8` | invalid credentials (wrong API key) |
| `530 5.7.0` | authentication required |
| `550 5.7.1` | unauthorized or forbidden (for example an unverified `From` domain) |
| `553 5.6.0` | invalid message (validation error) |
| `452 5.5.3` | more than 50 recipients |
| `421 4.7.0` | too many failed logins from this IP; try again later |
| `451 4.3.0` | temporary failure; retry |

Source: https://hatang.dev/docs/reference/errors
