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

# Errors, limits and retries

> The shape of every error, every error code, the limits a key meets, and which errors to retry.

Every error the API raises has the same body:

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "One or more parameters are invalid.",
    "details": [
      { "field": "countBack", "message": "Must be between 1 and 5000." }
    ]
  }
}
```

| Field | Meaning |
| - | - |
| `code` | A stable, machine-readable code. Switch on this. |
| `message` | A sentence for the developer: what happened, and what to do next. Safe to log and display. Never parse it; the wording can change. |
| `details` | Present on some codes only. Its shape is fixed per code. |

The HTTP status always matches the code.

Two responses carry no JSON body: a request with the wrong method for its path gets `405 Method Not Allowed`, and a request to `/v1/stream` without WebSocket upgrade headers gets a plain-text `4xx`.

## Error codes

| Code | Status | When | Retry? |
| - | - | - | - |
| `VALIDATION_ERROR` | 400 | A path parameter, query parameter or body is malformed | No. Fix the request. |
| `UNSUPPORTED_CHAIN` | 400 | The chain slug is not [supported](/concepts/chains) | No. Fix the request. |
| `UNAUTHORIZED` | 401 | The key is missing, malformed, unknown, revoked or expired | No. See [Authentication](/get-started/authentication). |
| `NOT_FOUND` | 404 | The path or resource does not exist | No. |
| `RATE_LIMITED` | 429 | You met a [rate or connection limit](#limits) | Yes, after `Retry-After`. |
| `INTERNAL_ERROR` | 500 | An unexpected failure on our side | Yes, with backoff, a few times. |
| `SERVICE_UNAVAILABLE` | 503 | Maintenance, or a temporary outage | Yes, with backoff. |

### Validation details

Only two codes carry `details`: `UNSUPPORTED_CHAIN` carries `{chain}`, and `VALIDATION_ERROR` carries a list of `{field, message}`, one per problem, as in the example at the top of this page. `field` is the parameter's name as you sent it, or `path`, `query` or `body` when the problem is not tied to one field.

## Limits

The limits exist to stop runaway clients, not to ration normal use. They sit far above what a working integration needs. If you meet one, your client is most likely looping or leaking connections.

| Limit | Value | When you meet it |
| - | - | - |
| Requests per API key | 12,000 per minute | `429 RATE_LIMITED`. Wait for `Retry-After`. |
| Requests per IP address | 60,000 per minute | `429 RATE_LIMITED`. Wait for `Retry-After`. |
| Open stream connections per API key | 100 | `429 RATE_LIMITED` on the handshake. Waiting frees nothing: close a connection you no longer need. |
| Subscriptions per stream connection | 100 | An `error` frame refusing the subscribe |
| Unsent frames per stream connection | 1,024 | The server closes the socket with code `4008` |
| Trades per page | 100 | Larger `limit` values are lowered to 100 |
| Candles per request | 5,000 | `countBack` above 5,000 returns `400 VALIDATION_ERROR` |
| Addresses per `spot.tokens` subscription | 100 | An `error` frame refusing the subscribe |

Every key, evaluation or production, has the same limits.

A key's request budget refills evenly over each minute, and a full minute's budget can be spent at once, so a backfill can burst and then settle to the steady rate. The IP limit sits above the key limit, so a fleet of your servers behind one outbound IP still gets its full key budget. Treat every number as a ceiling to stay well under, not as an exact quota to run at.

A stream connection counts against your key from its handshake until it closes, and requests over the socket spend no request budget. One connection holds up to 100 subscriptions, so most integrations need only a few.

`GET /openapi.json` needs no key, spends no budget, and carries no `X-RateLimit-Remaining` header.

## Response headers

| Header | On | Meaning |
| - | - | - |
| `X-Request-ID` | Every response | The request's trace ID. Echoes yours, or a generated UUID. See [Request IDs](#request-ids). |
| `X-RateLimit-Remaining` | Every `/v1` response, refusals included | Requests left in the current window |
| `Retry-After` | Every `429` | Whole seconds to wait before retrying. Never `0`. |

`X-RateLimit-Remaining` reports your key's budget. It reports your IP address's budget instead on a `401`, on a `429` from the IP limit, and on a `503` when the key could not be checked.

Pace your client on `X-RateLimit-Remaining` to slow down before you reach the limit.

## Retry strategy

1. **Honour `Retry-After`.** Wait at least that long.
2. **Back off exponentially on `500` and `503`,** with jitter, so parallel workers do not retry in step.
3. **Cap your retries.** Three to five attempts are enough. Past that, the problem needs attention.
4. **Never retry a `4xx` other than `429`.** It fails the same way every time.

```python theme={null}
import random
import time

import requests

RETRYABLE = {429, 500, 503}

def get_with_retries(url, headers, attempts=5):
    for attempt in range(attempts):
        response = requests.get(url, headers=headers, timeout=10)
        if response.status_code not in RETRYABLE or attempt == attempts - 1:
            return response
        retry_after = response.headers.get("Retry-After")
        delay = int(retry_after) if retry_after else min(30, 2 ** attempt) + random.random()
        time.sleep(delay)
```

## Request IDs

Every response carries an `X-Request-ID` header. You can also send your own `X-Request-ID`: up to 128 visible ASCII characters, with no spaces. The API echoes it back. It replaces any other value with a generated ID rather than rejecting the request.

When you contact support about a failed request, include its `X-Request-ID`.

## Errors on streams

A refused stream frame gets an `error` frame with the same `code`, `message` and `details`. See [Connecting to streams](/streams/overview#errors).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.