Skip to content
Get an API key

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.

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 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.

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.

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;
}