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

# Usage limits

> How to read the extensions.limits block ION returns when a request crosses a guardrail, so integrations and agentic workflows can adapt before enforcement rejects the request.

## Overview

ION applies fair-use guardrails to the `/graphql` endpoint so one caller's heavy queries can't degrade the API for everyone. To make guardrails **discoverable rather than surprising**, responses that touch a guardrail carry a `limits` block inside GraphQL `extensions`:

```json theme={null}
{
  "data": { "...": "..." },
  "extensions": {
    "limits": {
      "violations": [
        {
          "rule": "max_pagination_first",
          "field": "parts",
          "actual": "N",
          "limit": "N",
          "message": "Requested first: N on 'parts' exceeds the max page size of N."
        }
      ]
    }
  }
}
```

The block contains one field:

* **`violations`**: a list of guardrails your request bumped against. **Only present when non-empty.** A clean request omits the block entirely.

<Note>
  Static caps (page size, depth, aliases, tokens, root fields) carry `actual` and `limit` numbers as shown. Rate-limit entries carry only the rule, `retryAfter` when applicable, and a message. The underlying rate budget is plan-dependent and intentionally not published in responses; follow the guidance in `message` rather than keying off a threshold.
</Note>

## When the block appears

The `limits` block is part of the response `extensions`, alongside anything else ION reports there. Treat it as **optional and additive**: parse it if present, ignore it if absent. Your existing `data` / `errors` handling does not change.

A guardrail surfaces one of two ways, depending on where ION is in rolling a limit out to your org:

| Phase        | What you get                                                         | Request outcome                                                        |
| ------------ | -------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| **Advisory** | The condition is reported under `extensions.limits.violations`       | Request still **succeeds**: `data` is returned, no `errors`            |
| **Enforced** | The condition is returned as a GraphQL **error** with a machine code | Request is **rejected**. See [Error Codes](/api-reference/error-codes) |

Advisory is your window to adapt a query **before** it starts getting rejected. Treat any `violations` entry as "fix this and you'll be fine when enforcement turns on."

<Note>
  A successful response with no violations carries no `limits` block at all. If you see `extensions.limits`, there's something to address.
</Note>

## The `violations` array

Each entry is an object identified by its `rule`. Every entry carries a human-readable `message`; the other fields depend on the rule family.

### Structural (query shape)

Static caps on the shape of the query document: page size, nesting depth, aliases, document size, and number of top-level fields.

```json theme={null}
{
  "rule": "max_pagination_first",
  "field": "parts",
  "actual": "N",
  "limit": "N",
  "message": "Requested first: N on 'parts' exceeds the max page size of N."
}
```

| `rule`                                         | Trips on                                         | Fix                                                        |
| ---------------------------------------------- | ------------------------------------------------ | ---------------------------------------------------------- |
| `max_pagination_first` / `max_pagination_last` | A `first` / `last` page size larger than allowed | Lower the page size and paginate with cursors              |
| `max_depth`                                    | Selection set nested deeper than allowed         | Flatten the query; fetch nested data in follow-up requests |
| `max_aliases`                                  | Too many aliased fields in one document          | Split into multiple requests                               |
| `max_tokens`                                   | Query document too large                         | Break the operation up                                     |
| `max_root_fields`                              | Too many top-level fields in one operation       | Split into multiple operations                             |

`actual` and `limit` are always present on structural entries. `field` is included where a specific field is implicated (for example, pagination or depth) to point you at the offending part of the query.

### Rate limit

A per-tenant token bucket meters sustained throughput. Because it's a throughput signal, this entry carries the retry hint you need to back off:

```json theme={null}
{
  "rule": "rate_limit",
  "retryAfter": "seconds",
  "message": "Rate limit exceeded — retry after Ns."
}
```

| Field        | Meaning                                          |
| ------------ | ------------------------------------------------ |
| `retryAfter` | Seconds to wait before the bucket has room again |

Back off for `retryAfter` seconds, then resume. Spacing requests out helps you stay under the sustained rate.

<Note>
  A `rate_limit` entry **without** `retryAfter` is a permanent block: `{ "rule": "rate_limit", "message": "Access blocked. Contact support if you believe this is in error." }`. Retrying doesn't succeed until an operator lifts the block. Surface the message to the user and contact FirstResonance support, and don't build a retry loop around it.
</Note>

## What enforcement looks like

When a guardrail is enforced rather than advisory, the same condition comes back as a GraphQL error carrying a machine-readable `code`, and the request is rejected. Key off `errors[].extensions.code`, not the message text:

| `code`                      | Corresponds to                                                                                                                                                          |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `QUERY_COMPLEXITY_EXCEEDED` | Any structural violation above                                                                                                                                          |
| `RATE_LIMITED`              | A transient `rate_limit` violation. Also returns **HTTP 429** with a `Retry-After` header                                                                               |
| `BLOCKED`                   | A permanent-block `rate_limit` violation (no `retryAfter`). Returns HTTP 200 with the GraphQL error rather than HTTP 429. Retrying doesn't succeed, so contact support. |

Full payload shapes and remediation live in [Error Codes](/api-reference/error-codes). The field vocabulary matches the advisory `violations` entries, so one parser can handle both paths.

## Recommended client handling

For any integration, and especially unattended or agentic workflows that generate queries dynamically:

1. **Check `extensions.limits.violations` on every response.** No `limits` block means everything is clean; if the block is present, treat each entry as a to-do before enforcement flips on.
2. **On a transient `rate_limit` (or HTTP 429), honor `retryAfter`** with backoff and jitter. Don't hot-loop.
3. **On a `rate_limit` without `retryAfter` (or the `BLOCKED` error code), stop retrying.** This is a permanent block that only lifts when an operator changes the configuration. Surface the message and contact support.
4. **On structural violations, adapt the query.** Use smaller pages, fewer fields, paginate, or split the operation. Retrying the same shape won't help.
5. **Don't hard-code specific limit values.** Structural `actual` and `limit` numbers are stable but plan-dependent; read them from the response so your client stays correct as plans evolve.

## Related

* [Error Codes](/api-reference/error-codes): enforcement payloads (`QUERY_COMPLEXITY_EXCEEDED`, `RATE_LIMITED`, `BLOCKED`) and HTTP status reference.
* [Pagination](/api-reference/guides/pagination): cursor paging to keep page sizes down.
* [Build a Production Integration](/api-reference/guides/build-a-production-integration): retry, backoff, and resilience patterns for unattended clients.
