# API errors and rate limits

> The JSON shape every Endless REST API failure returns, the data.code field to branch on, and the RateLimit headers for 1,000 requests a minute.

Source: https://endless.app/docs/reference/api-errors

Every failure from the Endless REST API at `https://api.endless.app` returns JSON in one shape, and every response reports your remaining request budget. Versioning has [its own page](/docs/reference/api-versioning).

## The error shape

```json
{
  "code": "FORBIDDEN",
  "status": 403,
  "message": "Your role does not have permission to do this",
  "data": { "code": "permission_denied" }
}
```

| Field                | What it is                                                                       |
| -------------------- | -------------------------------------------------------------------------------- |
| `code`               | The HTTP status as a word: `BAD_REQUEST`, `UNAUTHORIZED`, `NOT_FOUND`, …         |
| `status`             | The HTTP status as a number                                                      |
| `message`            | A sentence for a person. It may be reworded, so don't match on it                |
| `data.code`          | The machine-readable reason, in `snake_case`. **This is the field to branch on** |
| `data.hint`          | When present, the step that resolves it                                          |
| `data.documentation` | When present, a link to the API reference                                        |

The `Error` schema in the [OpenAPI document](https://api.endless.app/openapi.json) lists every `data.code`, and each operation in the [API reference](/docs/api) lists its statuses.

## Common codes

| Status | `data.code`                                      | What to do                                              |
| ------ | ------------------------------------------------ | ------------------------------------------------------- |
| 400    | (validation)                                     | `message` names the field and the rule it broke         |
| 400    | `unsupported_api_version`                        | See [API versioning](/docs/reference/api-versioning)    |
| 401    | `missing_token`                                  | Send `Authorization: Bearer <token>`                    |
| 401    | `invalid_token`                                  | The token didn't verify or expired. Get a new one       |
| 402    | `insufficient_credits`, `exceeds_spending_limit` | The workspace or the caller can't pay for this call     |
| 403    | `permission_denied`, `forbidden`                 | The caller's role or access doesn't allow it            |
| 404    | `not_found`, `organization_not_found`, …         | The resource doesn't exist, or the caller can't open it |
| 404    | `route_not_found`                                | No operation matches the path (never an HTML page)      |
| 404    | `invalid_path_parameter`                         | An id in the path can't name a resource                 |
| 429    | `rate_limited`                                   | Wait the seconds in `retry_after`, then retry           |

## Rate limits

* **Limit:** 1,000 requests per 60 seconds per caller, in a policy named `default`. One counter per caller across all servers.
* **Fixed window:** it resets all at once, so pace by `RateLimit-Remaining`, not by an assumed even rate.
* **Credits are separate:** running out of balance or spending limit returns `402`, not `429`.

Every API response, success or failure, carries the counter in two spellings (browsers can read them too):

```http
RateLimit-Policy: "default";q=1000;w=60
RateLimit: "default";r=997;t=42
RateLimit-Limit: 1000
RateLimit-Remaining: 997
RateLimit-Reset: 42
```

`r` / `RateLimit-Remaining` are requests left; `t` / `RateLimit-Reset` are seconds until reset. Over the limit, the API returns `429` with a `Retry-After` header and the same seconds in the body:

```json
{
  "code": "TOO_MANY_REQUESTS",
  "status": 429,
  "message": "Too many requests.",
  "data": {
    "code": "rate_limited",
    "hint": "Wait 42s, then retry. Every response carries RateLimit headers — read them to pace yourself before you are refused.",
    "documentation": "https://endless.app/docs/api"
  },
  "retry_after": 42
}
```

## Machine-readable sources

* [API index](https://api.endless.app/): its `rate_limit` and `errors` objects state this policy.
* [OpenAPI document](https://api.endless.app/openapi.json): the `Error` schema on every failure response.
* The [MCP server](/docs/connect) has its own limit: 60 tool calls per minute per account.
