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

# Rate limits

Limits are counted **per project**, not per key or per IP — all traffic from one
project shares one budget. Every window is 60 seconds.

## Route classes

Each endpoint is assigned a cost class, and each class has its own quota:

| Class       | Endpoints                                                                                                                           | Cost          |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `cheap`     | Lookups and cached reads — status, jobs, Bible, holidays, Roblox, Minecraft info and textures, binary encode/decode, language lists | Low           |
| `fetch`     | Calls that hit an upstream service — translate, detect, roleplay GIFs                                                               | Medium        |
| `canvas`    | Image filters and overlays                                                                                                          | High          |
| `puppeteer` | Website screenshots, Minecraft renders                                                                                              | Highest       |
| `player`    | The whole [Music API](/music)                                                                                                       | **Unmetered** |

Music is deliberately exempt. What bounds it is the concurrent-player ceiling
instead, because a music bot routinely issues far more than 80 commands a
minute while still being a well-behaved caller.

## Plan limits

|                    | `free` | `standard` | `advanced` | `scale` |
| ------------------ | ------ | ---------- | ---------- | ------- |
| `cheap` / min      | 60     | 400        | 1200       | 6000    |
| `fetch` / min      | 15     | 120        | 400        | 2000    |
| `canvas` / min     | 10     | 60         | 200        | 1000    |
| `puppeteer` / min  | —      | 20         | 60         | 300     |
| Aggregate / min    | 80     | 550        | 1700       | 8500    |
| Concurrent players | 5      | 15         | 35         | 100     |

The aggregate row is deliberately lower than the sum of the classes above it.
Without it, a client could spend its whole `cheap` quota and never notice it had
no `canvas` budget left.

> **Note**
>
> A `0` in the `puppeteer` row is a **feature gate**, not "unlimited". The `free`
> plan does not include screenshots or Minecraft renders at all, and asking for
> one answers `403`, not `429`. See [upgrade required](#upgrade-required).

## Reading your remaining budget

Every metered response carries the current state:

```http
X-RateLimit-Plan: standard
X-RateLimit-Class: fetch
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
```

`X-RateLimit-Limit` is the limit for the *class* that request hit. `Retry-After`
appears only when you are being held back.

Two responses have no headers at all: `GET /v1/status`, because the public path
skips the limiter, and every `player`-class request, because Music has no quota.

## When you hit a limit

A `429` uses the standard [error envelope](/errors). The message tells you
which budget ran out:

```json
{
  "error": {
    "code": "rate_limited",
    "message": "This project is over the limit for fetch: 120 per minute."
  },
  "requestId": "0f8fad5b-d9cb-469f-a165-70867728950e"
}
```

| Message                                               | Meaning                           |
| ----------------------------------------------------- | --------------------------------- |
| `...over the limit for {class}: {n} per minute.`      | That one class is exhausted       |
| `...over its total request limit. Try again shortly.` | The aggregate budget is exhausted |

Respect `Retry-After` rather than retrying on a fixed timer — the limiter uses a
smooth window, so a client that backs off recovers immediately while one that
retries on a schedule keeps the debt alive.

## Upgrade required

Asking for a feature your plan does not include returns `403`, not `429`, and
carries your plan in the headers so you can see which one you are on:

```json
{
  "error": {
    "code": "forbidden",
    "message": "puppeteer is not part of the free plan. Upgrade to use it."
  },
  "requestId": "0f8fad5b-d9cb-469f-a165-70867728950e"
}
```

```http
X-RateLimit-Plan: free
```

Change your plan from [aegi.run](https://aegi.run).

## Burst behaviour

Quotas are enforced with GCRA, which allows a burst equal to the per-minute
limit but then smooths the recovery. A rejected request does not push the window
forward — otherwise a client in a retry loop would build a penalty box and stall
legitimate traffic for minutes.

If the rate limiter's own storage is unavailable, requests are allowed through
rather than failed. A limiter outage should never take the API down with it.