Every error the SDK raises extends PolyesterError, with a stable machine-readable code and a retryable flag. RPC failures keep the original ConnectRPC error as cause. Errors mapped from a
failed response carry its HTTP status (for a Connect error without a raw HTTP status, the
Connect protocol's status for its code), including realtime token request failures; status is undefined when no response arrived. For usage patterns, see the Error handling guide.
Unknown protobuf enum values in read responses do not raise ValidationError. The affected field
decodes as "unspecified", preserving the rest of the response when a newer server adds an enum
member that this SDK version does not recognize.
Hierarchy
PolyesterError code retryable
├── TransientError TRANSIENT_FAILURE true
│ ├── NetworkError NETWORK_ERROR true
│ ├── TimeoutError TIMEOUT true
│ ├── RateLimitError RATE_LIMITED true
│ ├── TimestampSkewError TIMESTAMP_SKEW true
│ └── ServiceUnavailableError SERVICE_UNAVAILABLE true
├── RequestError REQUEST_FAILED false
│ ├── ValidationError VALIDATION_FAILED false
│ │ ├── StaleQuoteError STALE_QUOTE
│ │ ├── WithdrawDeadlineExpiredError WITHDRAW_DEADLINE_EXPIRED
│ │ └── PolicyScopeMismatchError POLICY_SCOPE_MISMATCH
│ ├── ResourceNotFoundError RESOURCE_NOT_FOUND false
│ ├── NotImplementedError NOT_IMPLEMENTED false
│ ├── AlreadyExistsError ALREADY_EXISTS false
│ │ └── UserOperationAlreadyKnownError USER_OPERATION_ALREADY_KNOWN
│ ├── PermissionError PERMISSION_DENIED false
│ ├── AuthenticationError UNAUTHENTICATED false
│ ├── PreconditionFailedError PRECONDITION_FAILED false
│ │ ├── RevisionConflictError REVISION_CONFLICT
│ │ ├── PolicyInUseError POLICY_IN_USE
│ │ ├── PolicyLockedError POLICY_LOCKED
│ │ ├── SubaccountChallengeInvalidError SUBACCOUNT_CHALLENGE_INVALID
│ │ └── MfaLastFactorRequiredError MFA_LAST_FACTOR_REQUIRED
│ ├── ConfigurationError INVALID_CONFIGURATION false
│ ├── MfaRequiredError MFA_REQUIRED false
│ │ ├── MfaEnrollmentRequiredError MFA_ENROLLMENT_REQUIRED
│ │ ├── StepUpRequiredError STEP_UP_REQUIRED
│ │ └── SessionElevationRequiredError SESSION_ELEVATION_REQUIRED
│ ├── MfaVerificationError MFA_VERIFICATION_FAILED false
│ ├── UserOperationSendTimeoutError USER_OPERATION_SEND_TIMEOUT false
│ ├── OutcomeUnknownError OUTCOME_UNKNOWN false
│ └── UserOperationNotExecutedError USER_OPERATION_NOT_EXECUTED false
├── InternalServerError INTERNAL_SERVER_ERROR false
└── RealtimeError REALTIME_ERROR variesClass notes
| Class | When |
|---|---|
TransientError | The request may not have reached the backend, or it was temporarily unable to serve it. Reconcile a single-order create by clientOrderId; reuse replayable requestId values. |
NetworkError | The request could not be sent, the connection failed mid-flight, or fetch resolved without a Response (for example, patched by a browser extension). |
TimeoutError | No response before the deadline: the timeoutMs request option, an AbortSignal.timeout() signal from the caller or an interceptor, or a backend deadline without an outcome-unknown detail. |
RateLimitError | Backend rate limiting. Carries retryAfterMs?: number when a safe wait is known and rateLimit?: RateLimitDetail when the backend sends quota state. An orders OVERLOADED detail means the account already has its maximum order requests in flight; it has no retryAfterMs, so retry after an outstanding request completes. |
TimestampSkewError | The API-key signature timestamp fell outside the backend's skew window, from a skewed clock or a request that waited in the runtime's fetch queue. Retrying signs the request again. Bound concurrency for large bursts of API-key requests. |
ServiceUnavailableError | Backend overloaded or restarting (502/503/504), or a temporary dependency failure such as auth AUTH_TEMPORARILY_UNAVAILABLE. The same request can be retried. |
RequestError | The request itself was rejected; an identical retry fails identically. |
ValidationError | Input failed validation (client- or server-side). Client-side messages name the failing field path, such as items.0.symbolId: .... |
StaleQuoteError | A submitted market quote exceeded the backend's permitted drift. |
WithdrawDeadlineExpiredError | A prepared trading withdraw's signed deadline has passed, checked before sending or rejected by the backend. Prepare the withdraw again. |
ResourceNotFoundError | Resource missing or not visible to the caller. |
NotImplementedError | The backend does not implement the operation (HTTP 501 or Connect unimplemented). |
AlreadyExistsError | Duplicate (e.g. a reused clientOrderId), or auth AUTH_SOCIAL_ACCOUNT_ALREADY_LINKED: the social identity is linked to another Polyester account. |
UserOperationAlreadyKnownError | sendPolyesterUserOperation: the bundler already holds the identical operation. Wait on userOpHash instead of signing again. |
UserOperationSendTimeoutError | sendPolyesterUserOperation: eth_sendUserOperation timed out after signing, so the bundler may have accepted it. Wait on userOpHash with waitForPolyesterUserOperationReceipt; do not sign again. |
UserOperationNotExecutedError | waitForPolyesterUserOperationReceipt: the bundler reported the operation rejected or failed (bundlerStatus, with userOpHash). Nothing moved; a fresh operation is safe. |
OutcomeUnknownError | An order or trigger change was sent but not confirmed in time and may still apply. detail.code names the operation. Read the order by client order ID, or list triggers by client trigger ID, before sending it again. |
PermissionError | Authenticated but not allowed. |
AuthenticationError | Missing/expired/invalid credentials. |
PreconditionFailedError | System state forbids it (for example insufficient balance). |
RevisionConflictError | An optimistic-concurrency mutation used a stale resource revision. Refetch and ask the user to review; never retry the same mutation blindly. |
PolicyInUseError | A policy cannot be deleted or changed because something still references it. |
PolicyLockedError | A policy is locked and cannot be mutated until unlocked. |
SubaccountChallengeInvalidError | The subaccount authorization expired, was replaced by a newer challenge, was replayed, or is otherwise invalid. Request a fresh one with subaccounts.createChallenge. |
PolicyScopeMismatchError | The policy does not belong to the account scope targeted by the request. |
ConfigurationError | The SDK itself is misconfigured (bad environment, missing credentials). |
MfaRequiredError | Umbrella for the three MFA flows below. |
MfaEnrollmentRequiredError | User must enroll an MFA factor first. |
StepUpRequiredError | Needs a fresh one-use proof; retry with options.stepUpToken. |
SessionElevationRequiredError | Needs a recently MFA-elevated session. |
MfaLastFactorRequiredError | The requested deletion would leave the account without an active MFA factor. |
MfaVerificationError | An MFA challenge response was rejected (wrong code, expired challenge, and similar). |
InternalServerError | Backend failure or malformed response. Not auto-retryable; mutations may have partially applied. |
RealtimeError | A realtime server or WebSocket failure delivered to a subscription's onError. realtimeCode is the Centrifugo code (0 when none applies). retryable is true when the realtime client is already retrying, such as code 109 (token expired). |
Catalog errors
Raised by catalog reads and decimal conversion; they plug into the same tree under RequestError / ValidationError:
| Class | Code | When |
|---|---|---|
CatalogLookupError | CATALOG_LOOKUP_MISS | require* lookup for an unknown pair/asset/chain. |
CatalogNotReadyError | CATALOG_NOT_READY | Direct catalog read before a snapshot exists. |
CatalogConversionError | CATALOG_CONVERSION_INVALID | Invalid decimal, excess precision, or value above a protobuf wire ceiling. |
CatalogValidationFailedError | CATALOG_VALIDATION_FAILED | Order input violates pair constraints (tick/step/min). |
The catalog classes are exported from @polyester/sdk/catalogs; everything else on this page
comes from the root export.
Structured backend details
Typed RPC errors expose the first recognized backend rejection as PolyesterError.detail. The value is undefined when the backend provided no recognized detail.
It survives SDK error wrapping, so inspect it directly instead of walking an error's cause chain.
import { PolyesterError } from "@polyester/sdk";
try {
await client.orders.cancelAll({ symbolIds: [symbolId] });
} catch (error) {
if (
error instanceof PolyesterError &&
error.detail?.service === "orders" &&
error.detail.code === "CANCEL_REQUEST_EXPIRED"
) {
// The cancellation replay window elapsed; use a fresh requestId.
}
}SDK transports load the detail decoders before mapping an error. Outside SDK transports, for
example when mapping a raw ConnectError from your own client with toPolyesterError, call await loadErrorDetailDecoders(error) first, or loadErrorDetailDecoders() once to preload every
decoder. Without them the error still maps, but detail is undefined.
import { loadErrorDetailDecoders, toPolyesterError } from "@polyester/sdk";
try {
await rawClient.someMethod(request);
} catch (error) {
await loadErrorDetailDecoders(error);
throw toPolyesterError(error);
}For market-order stale quotes, use error instanceof StaleQuoteError (imported from @polyester/sdk) or inspect an orders detail with code STALE_QUOTE.
API-key refusals carry an auth detail.code naming the reason: AUTH_SIGNATURE_INVALID, AUTH_TIMESTAMP_SKEW, AUTH_MISSING_HEADERS, AUTH_KEY_EXPIRED, AUTH_KEY_DISABLED, AUTH_IP_NOT_ALLOWED, or AUTH_API_KEY_NOT_ACCEPTED. AUTH_TIMESTAMP_SKEW maps to the retryable TimestampSkewError. AUTH_TEMPORARILY_UNAVAILABLE maps to a retryable ServiceUnavailableError, and AUTH_SOCIAL_ACCOUNT_ALREADY_LINKED to AlreadyExistsError.
Orders details with ORDER_CREATE_OUTCOME_UNKNOWN, ORDER_CANCEL_OUTCOME_UNKNOWN, ORDER_MODIFY_OUTCOME_UNKNOWN, TRIGGER_CREATE_OUTCOME_UNKNOWN, or TRIGGER_CONTROL_OUTCOME_UNKNOWN throw OutcomeUnknownError. A deadline without one of these
details is a retryable TimeoutError.
For transfer failures, detail.service is "withdraw" or "internal_transfer", with the
service's stable backend error code. Transfer rate-limit details map to RateLimitError; temporary
withdrawal or internal-transfer dependency failures map to ServiceUnavailableError.
Daily reward claim failures use detail.service === "claims". Claims rate-limit details map to RateLimitError; SERVICE_UNAVAILABLE and CLAIM_TEMPORARILY_UNAVAILABLE map to ServiceUnavailableError. SOCIAL_VERIFICATION_REQUIRED is a non-retryable PreconditionFailedError: the user must verify an X or Discord account before claiming. CLAIM_PAYOUT_UNAVAILABLE means the campaign's payouts are paused; it also appears as a daily
claim's payoutDelayReason. When a claim names a payout subaccount, SUBACCOUNT_NOT_FOUND (Connect not_found, HTTP 404) maps to ResourceNotFoundError and is also returned for subaccounts the
authenticated root account does not own, including shared ones. SUBACCOUNT_INACTIVE (Connect failed_precondition, HTTP 400) maps to PreconditionFailedError for an owned subaccount that is
disabled. See client.claims.
Order details have service: "orders" plus the existing OrderErrorDetail fields. Rejected
batch-create, batch-replace, and batch-cancel result items continue to expose that optional error field.
OrderErrorCode includes policy-specific rejections such as POLICY_MAX_OPEN_ORDERS, SUBACCOUNT_READ_FORBIDDEN, POLICY_SPOT_READ_DENY, API_KEY_SPOT_READ_DENY, and CANCEL_REQUEST_EXPIRED.
Rate-limit details
RateLimitError.rateLimit preserves quota values that may exceed JavaScript's safe integer range:
import type {
RateLimitFailureReason,
RateLimitPolicyClass,
RateLimitRefillModel,
RateLimitScope,
} from "@polyester/sdk";
interface RateLimitDetail {
reason: RateLimitFailureReason;
operationId: string;
policyClass: RateLimitPolicyClass;
scope: RateLimitScope;
refillModel: RateLimitRefillModel;
limit?: string;
remaining?: string;
retryAfterMs?: string;
policyVersion?: string;
}retryAfterMs on the error is a number only when conversion is safe or a response header supplies
one. Use the string fields in rateLimit when exact quota data matters.
client.tradingRateLimits is the discovery API for those trading pools: public catalog via getConfig, effective account limits via getTradingLimits. See Trading rate limits.
Helper functions
| Function | Purpose |
|---|---|
isAbortError(err) | Caller cancellation, including transport cancellation when the caller's signal is aborted; outside the PolyesterError tree and never retryable. A caller signal that already expired before the call started, including an AbortSignal.timeout() signal, rejects as an abort, not a TimeoutError, so retry loops stop. |
isTimeoutAbortError(err) | The raw DOMException named TimeoutError that AbortSignal.timeout() raises. SDK calls map an expiry during the call to TimeoutError. |
loadErrorDetailDecoders(error?) | Load the decoders error's details need, or every decoder when called without an argument. Call before synchronously mapping a raw ConnectError from outside SDK transports. |
isRetryableError(err) | true for TransientError subclasses and raw unavailable / deadline_exceeded / resource_exhausted / aborted Connect failures. |
isResourceNotFoundError(err) | Convenience type guard. |
isRevisionConflictError(err) | Detect a typed or raw stale-revision failure. |
isPolicyInUseError(err) / isPolicyLockedError(err) / isPolicyScopeMismatchError(err) | Policy mutation guards for unknown error values. |
isFreshStepUpRequiredError(err) / isSessionElevationRequiredError(err) / isMfaEnrollmentRequiredError(err) | MFA flow guards for unknown error values. |
isMfaLastFactorRequiredError(err) | Detect last-factor MFA requirement for unknown error values. |
errorFromHttpStatus(status, message, options?) | Map a plain HTTP status onto the tree (400/422→ValidationError, 401→AuthenticationError, 412→PreconditionFailedError, 429→RateLimitError, 501→NotImplementedError, …). The SDK applies it to bare HTTP error responses without a Connect body; a bare 404 means a missing route and maps to NotImplementedError. |
toPolyesterError(err) / connectErrorToPolyesterError(err) | Normalize arbitrary/Connect errors into the tree. |
createErrorMappingInterceptor() | The interceptor the SDK installs so every RPC failure is typed (exported for custom transports). |
normalizeErrorMessage(message) | Strip Connect's [code] prefixes from messages. |
formatConnectError(err, fallback) | Human-readable message from any error. |
formatUserFacingError(err, fallback) | Stable user-facing copy for typed SDK errors; cancellation formats as "Request canceled.". |
Types
PolyesterErrorCode: union of every code string above.PolyesterErrorDetail: discriminated backend rejection detail for auth, profile, orders, withdrawal, internal transfer, ledger, market-overview, or claims RPC errors.PolyesterErrorOptions:{ cause?: unknown; detail?: PolyesterErrorDetail; status?: number }accepted by every constructor.RateLimitErrorOptions: addsretryAfterMs?: numberandrateLimit?: RateLimitDetail.RealtimeErrorOptions: addsrealtimeCode?: numberandretryable?: boolean.RateLimitDetail: structured quota state with exact decimal-string counter values.OrderErrorDetail: an Orders rejection code, violations, and optionalrateLimitstate.