> ## Documentation Index
> Fetch the complete documentation index at: https://help.retainful.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> HTTP status codes, error response shape, and how to handle each failure mode.

The API uses conventional HTTP status codes. Anything in the `2xx` range succeeded; `4xx` means the request needs fixing on your side; `5xx` means something failed on Retainful's side.

## Status codes

| Code  | Meaning                                                                                                 | What to do                                                                                 |
| ----- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `200` | Success. Returned by reads and by [adding a contact to a list](/developers/api/contact-groups).         | Carry on.                                                                                  |
| `201` | Created. Returned by [contact creation](/developers/api/contacts) and [events](/developers/api/events). | Carry on.                                                                                  |
| `400` | Bad request — malformed JSON, failed validation, or an unrecognized field.                              | Read `message`; fix the payload. Don't retry unchanged.                                    |
| `401` | Missing or invalid API key.                                                                             | Check the header, and that the key hasn't been revoked or expired.                         |
| `404` | Resource not found.                                                                                     | Check the ID; the resource may have been deleted.                                          |
| `429` | Rate limited.                                                                                           | Wait for the window named in `Retry-After-*` — see [Rate limits](/developers/rate-limits). |
| `5xx` | Server error.                                                                                           | Retry with exponential backoff; if persistent, contact support.                            |

<Note>
  Validation failures return `400`, not `422`. There is no `403` — an API key either authenticates or it doesn't.
</Note>

## Error response shape

Every error carries the same five fields:

```json 401 example theme={null}
{
  "statusCode": 401,
  "timestamp": "2026-06-11T12:00:00.000Z",
  "path": "/api/v1/events",
  "method": "POST",
  "message": "Invalid or expired API key"
}
```

Errors you can act on programmatically add three more — `code`, `title`, and `detail`. `message` and `detail` carry the same text:

```json 400 example theme={null}
{
  "statusCode": 400,
  "timestamp": "2026-06-11T12:00:00.000Z",
  "path": "/api/v1/customer/create",
  "method": "POST",
  "message": "Email must be a valid email address",
  "code": "VALIDATION_ERROR",
  "title": "Invalid request payload",
  "detail": "Email must be a valid email address"
}
```

Branch on `code` when it's present. Note that `401`, `404`, and `429` responses have **no** `code` field — check for its presence before reading it.

## Error codes

| Code                          | Meaning                                                               |
| ----------------------------- | --------------------------------------------------------------------- |
| `VALIDATION_ERROR`            | A field is missing, the wrong type, or not a recognized field name.   |
| `EVENT_NAME_REQUIRED`         | `eventName` was empty.                                                |
| `CONTACT_REQUIRED`            | An [event](/developers/api/events) was sent with no `contact` object. |
| `CONTACT_IDENTIFIER_REQUIRED` | The `contact` object had no `contactUUID`, `email`, or `phone`.       |
| `CONTACT_NOT_FOUND`           | The `contactUUID` sent with an event matches no contact.              |

<Warning>
  `message` names only the **first** field that failed validation. If several fields are wrong, you'll fix them one round-trip at a time — validate on your side before sending.
</Warning>

## Retry guidance

* **Retry**: `429` (after the `Retry-After-*` window) and `5xx` (with exponential backoff and a retry cap).
* **Don't retry unchanged**: `400`, `401`, `404` — the same request fails the same way.
* **Retries are safe by design.** Contact creation upserts, so repeating it is harmless. Events are deduplicated on [`uniqueIdentifier`](/developers/api/events#idempotency) — a retried event updates the stored record instead of triggering the automation twice. Reuse the same identifier when retrying, and only change it for a genuinely new occurrence.

<Tip>
  Log the full response body on failures, not just the status code — `message` almost always names the exact field that failed.
</Tip>
