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

# Errors

> One envelope shape, a closed set of codes, a docs URL on every failure.

Every error response — auth failures, validation problems, rate limits, internal hiccups — follows the same shape. There is no field that's only present on some errors and not others.

## The envelope

```json theme={"dark"}
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded: 60 requests per minute on the free tier.",
    "request_id": "req_01H...",
    "docs_url": "https://docs.elestrals.com/errors/rate_limit_exceeded"
  }
}
```

* `code` — one of the codes in the table below. Treat this as the machine-readable identifier; key your client logic off it.
* `message` — a human-readable explanation. Phrasing may evolve; don't string-match on it.
* `request_id` — quote this in support tickets. Logged on our side; lets us find your exact request.
* `docs_url` — deep link to the troubleshooting page for this code.

Successful responses use a different envelope (`data` + `pagination` + `meta`); see [Pagination](/pagination) for that shape.

## All error codes

| Code                  | HTTP | Meaning                                                                                                          |
| --------------------- | ---- | ---------------------------------------------------------------------------------------------------------------- |
| `bad_request`         | 400  | The request was malformed: invalid query params, key in the wrong place, missing required field.                 |
| `invalid_cursor`      | 400  | Cursor is malformed, expired, or from a different path/filter combination.                                       |
| `unauthorized`        | 401  | Missing, malformed, revoked, or unknown API key.                                                                 |
| `forbidden`           | 403  | The key is valid but isn't allowed to use this endpoint (e.g., a Free key hitting a Partner-only bulk endpoint). |
| `not_found`           | 404  | The requested resource doesn't exist. Check the ID format and try again.                                         |
| `rate_limit_exceeded` | 429  | Per-minute burst limit hit. Respect `Retry-After`.                                                               |
| `quota_exceeded`      | 429  | Monthly quota exhausted. Reset is the 1st of next month, UTC.                                                    |
| `internal_error`      | 500  | Something broke on our side. The request was not retried.                                                        |
| `service_unavailable` | 503  | Upstream dependency timed out. Retry with backoff.                                                               |

The set of codes is closed — you can write an exhaustive switch over them and trust we won't add a new code without a version bump.

## Handling errors

The minimum responsible client treats codes in three buckets:

* **4xx that you fix in code.** `bad_request`, `invalid_cursor`, `unauthorized`, `forbidden`, `not_found`. Don't retry — your code or config is wrong, and retrying with the same input gets the same answer.
* **4xx that you fix by waiting.** `rate_limit_exceeded`, `quota_exceeded`. Respect `Retry-After`. See [Rate limits](/rate-limits) for backoff strategy.
* **5xx that you fix by retrying.** `internal_error`, `service_unavailable`. Retry with exponential backoff and a cap (e.g., 5 attempts, then surface the failure).

```javascript theme={"dark"}
async function call(path, key) {
  const res = await fetch(`https://api.elestrals.com${path}`, {
    headers: { Authorization: `Bearer ${key}` },
  });
  if (res.ok) return res.json();

  const { error } = await res.json();
  switch (error.code) {
    case 'rate_limit_exceeded':
    case 'quota_exceeded':
      throw new RetryableError(error, Number(res.headers.get('Retry-After') ?? 1));
    case 'internal_error':
    case 'service_unavailable':
      throw new RetryableError(error);
    default:
      throw new PermanentError(error);
  }
}
```

## When to involve support

Open a ticket when:

* A `5xx` recurs across multiple `request_id`s within a few minutes.
* A `4xx` doesn't match what the [API Reference](/api-reference) describes for the endpoint.
* A `not_found` is returned for a resource you can see exists in another endpoint's response.

Always include the `request_id` from at least one failing call. Without it, support gets to triangulate from a heap of haystacks.
