Skip to main content
Resource methods raise a RequestError for any non-2xx response, so you handle failures with try/catch rather than inspecting each result. On success, a method returns its typed payload. RequestError exposes:
  • code: a stable error category, so you can branch on it instead of matching on message text. Route errors use INVALID_REQUEST, NOT_FOUND, INSUFFICIENT_BALANCE, IDEMPOTENCY_CONFLICT, ORDER_NOT_CANCELLABLE, ORDER_RECONCILIATION_REQUIRED and INTERNAL_ERROR; signed-auth rejections use UNAUTHORIZED, FORBIDDEN, AUTH_UNAVAILABLE, RATE_LIMITED and BAD_CONTENT_HASH. It is absent for a response that carries no error envelope. A write that takes an idempotency key can also answer 400 with IDEMPOTENCY_KEY_REQUIRED (the Idempotency-Key header is absent, or empty after trimming whitespace) or IDEMPOTENCY_KEY_INVALID (the key is longer than 255 characters or is not printable ASCII). The clients generate a key only when you pass none, so you see these only for a key you supplied. An empty string is sent as is and gets IDEMPOTENCY_KEY_REQUIRED.
  • status: the HTTP status code.
  • message: a human-readable explanation.
  • retryAfterSeconds in TypeScript, retry_after_seconds in Python: how long to wait, taken from the response’s Retry-After header. Rate-limit refusals and temporarily unavailable market-discovery responses can carry it. It is absent when the header is missing or unreadable, so keep a fallback.
Python also carries body, the parsed error envelope.
code is the wire string in both languages, so the examples above compare against it directly. Python also exports ApiErrorCode, AuthErrorCode and IdempotencyBadRequestErrorCode, which subclass StrEnum and so compare equal to those strings. Their members are named in lower case (ApiErrorCode.not_found, value "NOT_FOUND"), which is worth knowing before you reach for ApiErrorCode.NOT_FOUND. Authentication failures arrive in the same envelope as route errors, so code is populated: a rejected signature is a 401 with UNAUTHORIZED, and a key without the capability for a route is a 403 with FORBIDDEN.

Retrying a rate-limited request

RATE_LIMITED means your organization has spent its request budget. Every key your organization holds draws on the same budget, so opening more keys does not raise it. Wait for the time the error names, then retry. Do not sleep for a fixed interval: the budget refills continuously, so the value is the time until your next request can succeed rather than the length of a window. Both examples fall back to one second, because the header can be absent.
retry_after_seconds needs parlayx 1.1.0 or newer.
Every authenticated response also carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset, so pacing your own calls before being refused is more reliable than reacting to a refusal. See Rate limits.