> ## Documentation index
> Fetch the complete documentation index at: https://docs.predictefy.com/llms.txt
> Use it to discover every available page before exploring further.

# Errors

> The error envelope, the codes you will actually see, and which ones are worth retrying.

Every error — 4xx and 5xx alike — uses one shape:

```json
{
  "success": false,
  "error": {
    "code": "INSUFFICIENT_CREDITS",
    "message": "…",
    "retryable": false
  }
}
```

`code`, `message`, and `retryable` are always present. Treat everything else as optional.

**Branch on `code`, not on the HTTP status.** Several codes share a status — `NOT_SUPPORTED`
arrives as both 400 and 501 depending on where the gap is — and the code is the specific
one. Use `retryable` to decide whether to try again at all.

## The codes you will see

| HTTP    | Code                                                         | Meaning                                                | Retry                |
| ------- | ------------------------------------------------------------ | ------------------------------------------------------ | -------------------- |
| 400     | `VALIDATION_ERROR`                                           | Bad or missing parameters                              | No                   |
| 400     | `CATALOG_QUERY_TOO_BROAD`                                    | The filter matches too much to serve; narrow it        | No                   |
| 401     | `UNAUTHORIZED` / `AUTHENTICATION_ERROR`                      | Missing, unknown, or revoked key                       | No                   |
| 402     | `INSUFFICIENT_CREDITS`                                       | Balance below the endpoint weight                      | After top-up         |
| 403     | `PLAN_REQUIRED` / `PERMISSION_DENIED`                        | The plan does not include this route or window         | After upgrade        |
| 403     | `SCOPE_MISSING`                                              | The key lacks the required scope, e.g. `trade`         | No                   |
| 404     | `MARKET_NOT_FOUND` / `EVENT_NOT_FOUND` / `OUTCOME_NOT_FOUND` | Unknown record                                         | No                   |
| 404     | `EXCHANGE_NOT_AVAILABLE` / `VENUE_NOT_AVAILABLE`             | Unknown or unserved venue                              | No                   |
| 404     | `CLUSTER_NOT_FOUND`                                          | Cluster detail lookup missed                           | No                   |
| 404     | `SNAPSHOT_NOT_FOUND`                                         | No stored order-book snapshot in that archive window   | No                   |
| 404     | `ROUTE_NOT_FOUND`                                            | The route is not mounted                               | No                   |
| 409     | `API_KEY_LIMIT`                                              | The account's active-key cap is reached                | After revoking a key |
| 429     | `RATE_LIMITED` / `RATE_LIMIT_EXCEEDED`                       | Request window exceeded                                | Yes, with backoff    |
| 400/501 | `NOT_SUPPORTED` / `ACCOUNTS_UNSUPPORTED`                     | An honest capability gap                               | No                   |
| 503     | `CATALOG_UNAVAILABLE` / `HISTORY_UNAVAILABLE`                | The lane is temporarily unavailable                    | Yes, with backoff    |
| 503     | `MATCHES_UNAVAILABLE`                                        | The cross-match lane is not enabled on this deployment | Yes, once enabled    |
| 503     | `PLATFORM_UNAVAILABLE` / `BILLING_UNAVAILABLE`               | Temporary outage                                       | Yes, with backoff    |
| 5xx     | `INTERNAL` / `NETWORK_ERROR`                                 | Unexpected failure or transport error                  | Yes, with backoff    |

`MATCHES_UNAVAILABLE` is the one code above that is about a deployment rather than a request. The
six cross-match verbs — `fetchMarketMatches`, `fetchMatchedMarkets`, `compareMarketPrices`,
`fetchHedges`, and the deprecated `fetchMatches` / `fetchMatchedPrices` aliases — are always
mounted, so an unenabled lane answers an honest `503` instead of a `404`. It is marked retryable
because enabling the lane is a deployment change, not something a caller can fix by retrying now,
and it is deliberately raised **before** any credit is debited: a dark lane never charges you and
never answers `402`.

The authoritative list is the `ErrorDetail` enum in the
[API reference](/api/), which is generated from the contract that gates the implementation.

## NOT_SUPPORTED is not a failure

`NOT_SUPPORTED` means the venue does not expose that capability at all — no public trades
tape, no per-address order list. It is a correct answer about the world.

Do not retry it, do not fall back to a different venue silently, and do not render it as an
empty result. An empty array means "ran and found nothing"; `NOT_SUPPORTED` means "cannot
run here". See [Capability-honest data](/guides/honest-data/).

## Retrying

Only two families are worth retrying: `429` and `503`. Everything else will fail
identically on the second attempt.

`INSUFFICIENT_CREDITS` is the one that looks retryable and is not — the balance will not
change because you asked again. Surface it and stop.

For backoff shape, jitter, and why writes are never auto-retried, see
[Rate limits & retries](/guides/rate-limits/).

## Typed errors in the SDK

The [TypeScript SDK](/guides/sdk/) throws typed subclasses of `PredictefyError`, so you can
branch on the class instead of parsing strings. The server's `code` and `message` are
preserved on the thrown error either way.

```ts
import { InsufficientCreditsError, NotSupportedError } from '@predictefy/sdk';

try {
  await client.gemini.fetchTrades(marketId);
} catch (err) {
  if (err instanceof NotSupportedError) return renderUnavailable();
  if (err instanceof InsufficientCreditsError) return renderTopUp();
  throw err;
}
```
