Predictefy Docs
Browse documentation

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:

{
  "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

HTTPCodeMeaningRetry
400VALIDATION_ERRORBad or missing parametersNo
400CATALOG_QUERY_TOO_BROADThe filter matches too much to serve; narrow itNo
401UNAUTHORIZED / AUTHENTICATION_ERRORMissing, unknown, or revoked keyNo
402INSUFFICIENT_CREDITSBalance below the endpoint weightAfter top-up
403PLAN_REQUIRED / PERMISSION_DENIEDThe plan does not include this route or windowAfter upgrade
403SCOPE_MISSINGThe key lacks the required scope, e.g. tradeNo
404MARKET_NOT_FOUND / EVENT_NOT_FOUND / OUTCOME_NOT_FOUNDUnknown recordNo
404EXCHANGE_NOT_AVAILABLE / VENUE_NOT_AVAILABLEUnknown or unserved venueNo
404CLUSTER_NOT_FOUNDCluster detail lookup missedNo
404SNAPSHOT_NOT_FOUNDNo stored order-book snapshot in that archive windowNo
404ROUTE_NOT_FOUNDThe route is not mountedNo
409API_KEY_LIMITThe account's active-key cap is reachedAfter revoking a key
429RATE_LIMITED / RATE_LIMIT_EXCEEDEDRequest window exceededYes, with backoff
400/501NOT_SUPPORTED / ACCOUNTS_UNSUPPORTEDAn honest capability gapNo
503CATALOG_UNAVAILABLE / HISTORY_UNAVAILABLEThe lane is temporarily unavailableYes, with backoff
503MATCHES_UNAVAILABLEThe cross-match lane is not enabled on this deploymentYes, once enabled
503ARBITRAGE_UNAVAILABLEExecutable-arbitrage lane disabled on this deploymentYes, once enabled
503PLATFORM_UNAVAILABLE / BILLING_UNAVAILABLETemporary outageYes, with backoff
5xxINTERNAL / NETWORK_ERRORUnexpected failure or transport errorYes, with backoff

MATCHES_UNAVAILABLE and ARBITRAGE_UNAVAILABLE are the two codes above that are 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 /api/{exchange}/fetchArbitrage route is always mounted the same way and stays dark with ARBITRAGE_UNAVAILABLE until READS_ENABLE_ARBITRAGE is set and the live order-book fetcher is wired through READS_ENABLE_ORDERBOOK. It is likewise retryable and raised before any credit is debited for the same deployment-change reason.

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

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

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

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