> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.aegi.run/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.aegi.run/_mcp/server.

# Errors

## The envelope

Every failed request returns the same shape:

```json
{
  "error": {
    "code": "validation_error",
    "message": "One or more fields failed validation.",
    "details": [
      { "field": "query.to", "message": "Required" }
    ]
  },
  "requestId": "0f8fad5b-d9cb-469f-a165-70867728950e"
}
```

`code` is a stable identifier — branch on it, not on `message`, which is written
for humans and may be reworded. `details` is only present on validation errors.

## Error codes

| Code                  | Status | What it means                                               |
| --------------------- | ------ | ----------------------------------------------------------- |
| `bad_request`         | 400    | The request was understood but the parameters make no sense |
| `invalid_api_key`     | 401    | No key, a malformed key, or a key that does not exist       |
| `api_key_revoked`     | 401    | The key was revoked, or has expired                         |
| `unauthorized`        | 401    | Reserved; not currently returned                            |
| `forbidden`           | 403    | Authenticated, but the plan does not include this feature   |
| `player_limit`        | 403    | The project is already at its concurrent-player ceiling     |
| `not_found`           | 404    | No such route, or no such record                            |
| `method_not_allowed`  | 405    | Wrong verb for this path                                    |
| `conflict`            | 409    | Conflicts with the current player state                     |
| `validation_error`    | 422    | A parameter failed validation                               |
| `rate_limited`        | 429    | Over quota — see [rate limits](/rate-limits)                |
| `internal_error`      | 500    | An unexpected fault                                         |
| `service_unavailable` | 503    | A dependency is down or timed out                           |

Two statuses fall outside that table because they come from specific endpoints
rather than the shared mapping: Minecraft routes return `502` when an upstream
service fails, and [`GET /v1/jobs/{id}/wait`](/jobs) returns `504` when the
timeout elapsed before the job finished.

## Validation errors

A missing or malformed parameter is a `422`, and `details` lists each one. The
`field` is the Zod path joined with a dot, so it reads `query.to`,
`path.guildId`, or `body.volume` depending on where the parameter lives.

```json
{
  "error": {
    "code": "validation_error",
    "message": "One or more fields failed validation.",
    "details": [
      { "field": "query.amount", "message": "Too big: expected number to be <=5" },
      { "field": "query.type", "message": "Invalid enum value. Expected 'hug', received 'punch2'" }
    ]
  },
  "requestId": "0f8fad5b-d9cb-469f-a165-70867728950e"
}
```

> **Warning**
>
> `GET /v1/fun/roleplay` is the exception: it answers `400` with
> `bad_request` for validation failures, because it overrides the global handler.
> Everything else is `422`.

## Success envelopes are not uniform

Most endpoints wrap their payload in `data`, but the wrapper differs by area:

| Shape                              | Endpoints                                                   |
| ---------------------------------- | ----------------------------------------------------------- |
| `{ "success": true, "data": ... }` | Image, Tools, Fun, Translation, Roblox, Minecraft, Holidays |
| `{ "data": ... }`                  | Bible, Jobs, Music                                          |
| `{ "error": false, "data": ... }`  | Binary encode and decode — note the flag is named `error`   |
| No envelope                        | `GET /v1/status`                                            |

Read the schema in the [API reference](/api-reference) rather than assuming a
shape. The [JavaScript SDK](/sdk) hides this: it unwraps `data` for you and
resolves to the inner value.

## Request IDs

Every response carries an `X-Request-Id` header, echoed from your request if you
sent one and generated otherwise. The same value appears as `requestId` in the
error body.

Send your own to correlate a failure with your logs:

```bash
curl "https://api.aegi.run/v1/status" \
  --header "X-Request-Id: order-4821-checkout"
```

Quote that id when reporting a problem — it is what makes the request findable
in the logs.

## Handling errors

The SDK turns every non-2xx response into an `AegisError`:

```ts
import { Aegis, AegisError } from 'aegis-api'

try {
  await aegis.music.play(guildId, { query: 'lofi' })
} catch (error) {
  if (error instanceof AegisError) {
    error.status // 403
    error.code // 'forbidden'
    error.requestId // quote this when asking for help
  }
}
```

There is one error class, not a hierarchy — branch on `status` or `code`. See
[SDK errors](/sdk-errors) for the details.