> 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.

# Authentication

## Sending the key

Aegis authenticates with an API key in the `Authorization` header. There is no
other mechanism, and no query parameter alternative for regular endpoints.

```bash
curl "https://api.aegi.run/v1/status" \
  --header "Authorization: Bearer aegis.3f9a1c8e5b7d2049..."
```

The `Bearer` scheme is matched case-insensitively, so `bearer` and `BEARER` work
just as well.

> **Note**
>
> Keys are sent in a header rather than a query string on purpose — query
> strings end up in access logs and browser history. The one exception is the
> [events socket](/events), where a `?key=` fallback exists because browsers
> cannot set headers on a WebSocket handshake.

## Key format

A key is the literal prefix `aegis.` followed by exactly 64 lowercase hex
characters — 32 random bytes.

```
aegis.[0-9a-f]{64}
```

Anything else is rejected as malformed before Aegis touches the database. The
prefix is what lets you identify a key in a list without exposing it; only the
first eight hex characters are ever stored in the clear.

Keys are stored as a SHA-256 digest. The plaintext is displayed once, at
creation, and cannot be retrieved afterwards.

## Managing keys

Keys belong to a **project**, and a project is what [rate limits](/rate-limits)
and billing attach to. A project holds up to 25 active keys at a time, so you
can give each of your bots its own and revoke one without touching the rest.

Create, name, and revoke keys from [aegi.run](https://aegi.run), or attach one
to a Discord bot so the [Music API](/music) knows which bot to play as.

## The one public endpoint

`GET /v1/status` is the only endpoint that answers without a key:

```bash
curl "https://api.aegi.run/v1/status"
```

```json
{
  "status": "ok",
  "timestamp": "2026-02-09T14:12:00.000Z",
  "uptime": 48213.4,
  "version": "1.0.0",
  "environment": "production"
}
```

It is deliberately unauthenticated so uptime monitors can reach it. Because
public paths skip rate limiting entirely, this response carries no
`X-RateLimit-*` headers.

## Linking a key to a bot

The Music API acts as a Discord bot, and it works out which one from the key
rather than from the request. A key with no bot attached gets:

```json
{
  "error": {
    "code": "forbidden",
    "message": "This API key is not linked to a bot. Link it to one to use the music API."
  },
  "requestId": "0f8fad5b-d9cb-469f-a165-70867728950e"
}
```

Every other feature works with an unlinked key, so you only need to link one if
you are building a music bot.

## When a key is rejected

All four failures answer `401` with the standard [error envelope](/errors).
They are distinguished by `error.code` and the message:

| Situation                                | `error.code`      | Message                                                    |
| ---------------------------------------- | ----------------- | ---------------------------------------------------------- |
| Header missing, or not `aegis.` + 64 hex | `invalid_api_key` | `Provide your key as "Authorization: Bearer aegis.<key>".` |
| Well-formed, but no such key             | `invalid_api_key` | `That key does not exist.`                                 |
| Revoked from the dashboard               | `api_key_revoked` | `That key revoked.`                                        |
| Past its expiry                          | `api_key_revoked` | `That key expired.`                                        |

Revoked and expired keys are reported separately from invalid ones on purpose,
so you can tell "rotate this key" apart from "you sent it wrong".