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

# Errors

> How the Replyful API reports failures

Every non-2xx response from the API uses the same envelope. Branch on the HTTP status code and the `error.type` enum — never on `error.message`, which is human-readable and may change.

## Envelope

```json theme={null}
{
  "error": {
    "type": "validation_error",
    "code": "invalid_query_parameter",
    "message": "Number must be less than or equal to 100",
    "param": "limit",
    "docUrl": "https://docs.replyful.com/errors/invalid_query_parameter",
    "requestId": "req_01HK7GD3H6ZSQ0Y2A5B3C4DEFG"
  }
}
```

| Field       | Type                | Description                                                                          |
| ----------- | ------------------- | ------------------------------------------------------------------------------------ |
| `type`      | string              | High-level category. Stable enum — branch on this.                                   |
| `code`      | string              | Specific machine-readable reason. Stable — branch on this for fine-grained handling. |
| `message`   | string              | Human-readable explanation. May change between releases. **Do not parse.**           |
| `param`     | string \| undefined | Dotted path to the offending field, when applicable.                                 |
| `docUrl`    | string              | Deep link to the docs page for this `code`.                                          |
| `requestId` | string              | Echoes the response `Request-Id` header. Quote this in support tickets.              |

<Note>
  Status code is the source of truth. The API will never return `200 OK` with an error body. If your status check passes, the response is success.
</Note>

## Error types

The `error.type` enum:

| Type                   | Status | When it fires                                                     |
| ---------------------- | ------ | ----------------------------------------------------------------- |
| `authentication_error` | `401`  | Missing or invalid API key.                                       |
| `not_found`            | `404`  | The resource does not exist or is not visible to your API key.    |
| `validation_error`     | `422`  | Request was well-formed but failed validation.                    |
| `rate_limit_error`     | `429`  | Too many requests. See [Rate limits](/api-reference/rate-limits). |
| `api_error`            | `5xx`  | Server-side fault. Safe to retry idempotent calls with backoff.   |

Additional `type` values (`invalid_request_error`, `permission_error`, `idempotency_error`) are reserved for endpoints that are not yet shipped.

## Error codes shipped today

| Code                      | Type                   | Meaning                                                                    |
| ------------------------- | ---------------------- | -------------------------------------------------------------------------- |
| `missing_api_key`         | `authentication_error` | The `Authorization` header is absent or does not start with `Bearer `.     |
| `invalid_api_key`         | `authentication_error` | The key is malformed, unknown, or archived.                                |
| `ticket_not_found`        | `not_found`            | The ticket does not exist or belongs to another organization.              |
| `conversation_not_found`  | `not_found`            | The conversation does not exist or belongs to another organization.        |
| `invalid_query_parameter` | `validation_error`     | A query parameter failed validation. The `param` field names the offender. |
| `invalid_path_parameter`  | `validation_error`     | A path parameter failed validation. The `param` field names the offender.  |
| `invalid_cursor`          | `validation_error`     | The `startingAfter` cursor could not be decoded.                           |
| `conflicting_date_range`  | `validation_error`     | A request mixed `createdAt[...]` and `updatedAt[...]` filters. Pick one.   |
| `too_many_requests`       | `rate_limit_error`     | Rate limit exceeded. Honor `Retry-After`.                                  |
| `internal_server_error`   | `api_error`            | Unexpected server-side fault. Retry with backoff.                          |

## Examples

### `401 missing_api_key`

```json theme={null}
{
  "error": {
    "type": "authentication_error",
    "code": "missing_api_key",
    "message": "Missing Authorization header. Provide `Authorization: Bearer rfl_…`.",
    "docUrl": "https://docs.replyful.com/errors/missing_api_key",
    "requestId": "req_..."
  }
}
```

### `422 invalid_query_parameter`

```json theme={null}
{
  "error": {
    "type": "validation_error",
    "code": "invalid_query_parameter",
    "message": "Number must be less than or equal to 100",
    "param": "limit",
    "docUrl": "https://docs.replyful.com/errors/invalid_query_parameter",
    "requestId": "req_..."
  }
}
```

### `422 conflicting_date_range`

```json theme={null}
{
  "error": {
    "type": "validation_error",
    "code": "conflicting_date_range",
    "message": "Cannot filter by both createdAt and updatedAt date ranges.",
    "docUrl": "https://docs.replyful.com/errors/conflicting_date_range",
    "requestId": "req_..."
  }
}
```

### `429 too_many_requests`

```json theme={null}
{
  "error": {
    "type": "rate_limit_error",
    "code": "too_many_requests",
    "message": "Rate limit exceeded. Slow down and try again shortly.",
    "docUrl": "https://docs.replyful.com/errors/too_many_requests",
    "requestId": "req_..."
  }
}
```

## Handling errors

A pragmatic client:

```ts theme={null}
async function callReplyful(path: string, init?: RequestInit) {
  const res = await fetch(`https://api.replyful.com${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${process.env.REPLYFUL_API_KEY}`,
      ...init?.headers,
    },
  });

  if (res.ok) {
    return res.json();
  }

  const body = await res.json().catch(() => ({}));
  const requestId = res.headers.get("Request-Id") ?? body.error?.requestId;

  // Branch on type for high-level handling.
  switch (body.error?.type) {
    case "authentication_error":
      throw new Error(`Replyful auth failed (${requestId}). Check your API key.`);
    case "rate_limit_error": {
      const retryAfter = Number(res.headers.get("Retry-After") ?? "1");
      throw new RateLimitError(retryAfter, requestId);
    }
    case "validation_error":
      throw new Error(`Validation failed: ${body.error.message} (${requestId})`);
    default:
      throw new Error(`Replyful API error (${requestId}): ${body.error?.message ?? res.statusText}`);
  }
}
```

## Request IDs

Every response carries a `Request-Id` header (e.g. `req_01HK7GD3H6ZSQ0Y2A5B3C4DEFG`). The same value appears in the error body's `requestId` field. We retain request traces for 90 days — quote the ID in support emails to get the exact context of a failing call.
