<!-- tempo-docs-context -->
> Source: [/docs/api/rate-limits](https://tempo.xyz/developers/docs/api/rate-limits.md)
> Section: [API reference](https://tempo.xyz/developers/docs/api.md). Tempo API reference: authentication, conventions, endpoints grouped by product, Console management endpoints, JSON-RPC, and the indexer. An API key authenticates API requests; an account signing key authorizes transactions.
> [Documentation index](https://tempo.xyz/developers/llms.txt) · [Docs skill](https://tempo.xyz/developers/SKILL.md)

> Tempo MCP: Use `search`, `find_pages`, `read_page`, and `code` at `https://mcp.tempo.xyz` for Tempo and related documentation.
> If an indexed page is missing or reflects an older site, read this site’s [documentation index](https://tempo.xyz/developers/llms.txt) and Markdown pages directly.

# Tempo API rate limits and quota structure

The Tempo API enforces rate limits to ensure fair usage and system stability. Limits use fixed windows configured per endpoint and key. Responses that consume quota report it in `RateLimit-*` headers; cached responses can omit these headers.

## Tempo API quota structure

Each request consumes from one quota bucket, chosen by how the request is authenticated:

| Caller | Quota bucket |
| --- | --- |
| API key | Per key, per [scope](https://tempo.xyz/developers/docs/api/console/api-keys) |
| Anonymous | Per client IP |

* **API key requests** are counted per key *and* per scope. Each endpoint draws from the bucket for the scope it requires (such as `data:read`), so traffic to one scope does not exhaust another.
* **Anonymous requests** — endpoints that allow access without a key — are counted per client IP at a lower limit.

:::info
Limits depend on the endpoint, key configuration, and billing state. Sandbox projects without active billing use the public quota ceiling. The authoritative limit for any request is always the value in its `RateLimit-Limit` response header — read the headers rather than hardcoding a number.
:::

## Rate-limit response headers

A response that consumes quota includes headers such as:

```http
RateLimit-Limit: 100
RateLimit-Remaining: 97
RateLimit-Reset: 1735689600
RateLimit-Scope: data:read
```

| Header | Description |
| --- | --- |
| `RateLimit-Limit` | Requests allowed in the current window. |
| `RateLimit-Remaining` | Requests remaining in the current window. |
| `RateLimit-Reset` | Unix timestamp (seconds) when the window resets and the count returns to the full limit. |
| `RateLimit-Scope` | The scope bucket the request was counted against. Present on API-key requests. |

## Exceeding Tempo API rate limits

A rejected over-quota request normally returns `429 Too Many Requests` with a `Retry-After` header. Edge-level rate limits can return a different body; check the status before parsing JSON:

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 12
```

```json
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded"
  },
  "requestId": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9"
}
```

:::info
On endpoints that allow anonymous access, an over-quota request can instead be answered with `402 Payment Required`, letting you pay per request with MPP rather than wait. See [Authentication](https://tempo.xyz/developers/docs/api/authentication) for details.
:::

## Rate-limit best practices for Tempo API clients

### Respect rate-limit headers

Read `RateLimit-Remaining` and `RateLimit-Reset` to pace your requests, and pause until `RateLimit-Reset` when `RateLimit-Remaining` reaches `0` rather than retrying blindly.

### Retry rate-limited requests with backoff

When you receive a `429`, honor `Retry-After` and use backoff with jitter. This helper makes at most five attempts and returns the last response if the server asks it to wait longer than a minute:

```typescript
async function withRetry(fn: () => Promise<Response>): Promise<Response> {
  for (let attempt = 0; ; attempt++) {
    const response = await fn()
    if (response.status !== 429 || attempt === 4) return response
    const header = response.headers.get('Retry-After') ?? '0'
    const delay = /^\d+$/.test(header)
      ? Number(header) * 1000 : Date.parse(header) - Date.now()
    const wait = Math.max(Number.isFinite(delay) ? delay : 0, 2 ** attempt * 1000)
    if (wait > 60_000) return response
    await new Promise((resolve) => setTimeout(resolve, wait + Math.random() * 250))
  }
}
```

### Cache and paginate API reads

* Cache responses for read-heavy data that does not need to be real-time.
* Use [pagination](https://tempo.xyz/developers/docs/api/pagination) with a large `limit` to fetch more per request instead of issuing many small calls.
* Request only the fields you need with `include`, so expensive computations run only when required.

### Authenticate for higher API quotas

Anonymous traffic is held to the lower IP-based limit. Send an [API key](https://tempo.xyz/developers/docs/api/authentication) to get a per-key, per-scope quota, and request a higher limit if your workload needs it.
