Errors
Every error — 4xx and 5xx alike — uses one shape:
{ "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
Section titled “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, which is generated from the contract that gates the implementation.
NOT_SUPPORTED is not a failure
Section titled “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.
Retrying
Section titled “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.
Typed errors in the SDK
Section titled “Typed errors in the SDK”The TypeScript 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.
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;}