Every SDK-raised failure extends PolyesterError. The Python tree is flatter than TypeScript's TransientError / RequestError hierarchy, classify with isinstance and, for MFA control flow,
structured auth codes.
For usage patterns see the Error handling guide.
Hierarchy
PolyesterError
├── PolyesterAuthError
│ └── PolyesterSessionRequiredError # also a PolyesterApiError
├── PolyesterValidationError # .code, .violations
├── PolyesterResponseContractError
├── PolyesterTransportError
│ ├── PolyesterRateLimitError # .retry_after, .detail
│ ├── PolyesterServerError
│ ├── PolyesterTimeoutError
│ │ └── PolyesterBatchReplaceTimeoutError # .last_status
│ └── PolyesterTimestampSkewError
├── PolyesterApiError # .code, .metadata, .raw, .violations, .existing_order_id
│ ├── PolyesterSubaccountOwnerRoleLockedError
│ └── PolyesterNotImplementedError
│ └── PolyesterRouteNotFoundError # .procedure
└── PolyesterRealtimeError # .code, .close_code
├── PolyesterRealtimeOverflowError
└── PolyesterSubscriptionEndedError # .channel, .reasonClass notes
PolyesterAuthError: Missing/rejected credentials; realtime HTTP errors include structured contextPolyesterValidationError: Bad SDK input (shape, enums,post_onlymisuse, precision)PolyesterResponseContractError: Successful mutation response is empty, malformed, or internally inconsistentPolyesterTransportError: Network / timeout / transport failures, usually retryable with backoffPolyesterRateLimitError: Rate limited;retry_aftercomes fromdetail.retry_after_mswhen attachedPolyesterServerError: Backend 5xxPolyesterTimeoutError: Deadline expired; retryable, but a mutation may still have appliedPolyesterTimestampSkewError: Signed timestamp outside the skew window; a retry signs againPolyesterApiError: Structured Connect/API error with optionalcodePolyesterSessionRequiredError: A session-only method refused an API keyPolyesterNotImplementedError: Server does not offer the RPC or feature; not retryablePolyesterRouteNotFoundError: Gateway has no route for the RPCPolyesterRealtimeError: Realtime connect / subscribe / decode failuresPolyesterRealtimeOverflowError: Consumer too slow; subscription failed closedPolyesterSubscriptionEndedError: The server ended a subscription and it did not resume; subscribe again if you still need it
There is no separate CatalogConversionError / CatalogLookupError class tree like TypeScript.
Catalog and precision failures typically surface as PolyesterValidationError or API errors. A
quantity above the signed 64-bit wire range (for example 10 at base scale 18) raises PolyesterValidationError, not ValueError.
Your own cancellations are not SDK errors. Cancelling a task that awaits an SDK call raises asyncio.CancelledError, and asyncio.timeout around the call raises TimeoutError; neither is a PolyesterError. A canceled answer from the server is still PolyesterServerError.
is_retryable_error(err) identifies failures that may succeed after backoff. PolyesterResponseContractError is deliberately non-retryable because an immediate replay is not
safe, while mutation_outcome_unknown(err) returns True: the server accepted the RPC but returned
an unusable success response. Reconcile first and reuse the original request identity.
PolyesterRateLimitError can also represent exhausted local signing-timestamp capacity during an
exceptionally large burst. Normal async Connect and realtime calls wait cooperatively for bounded
capacity without blocking the event loop. The low-level synchronous polyester.auth.sign_request helper does not sleep: it raises immediately with retry_after.
When the server attaches a RateLimitDetail, error.detail carries policy_class, scope, operation_id, and quota fields, and retry_after reads its delay. Use bounded exponential backoff
when retry_after is None.
PolyesterApiError.violations names the request fields to fix, each with field_path, rule_id,
and message. Branch on code, then on rule_id; messages are for people. PolyesterValidationError carries the same code and violations when the SDK refuses a price or
quantity off the pair's tick or step before sending. is_order_already_terminal(err), is_not_found(err), and is_session_required(err) classify common
refusals.
Realtime token HTTP failures populate PolyesterAuthError.status_code, code, context, endpoint, label, and a bounded body, and error.problem holds the decoded problem (code, status, detail, type). A 403 is a non-transient permission failure: inspect code and provision the required API-key policy before retrying.
MFA helpers (API / session products)
API-key trading bots rarely hit interactive MFA. Helpers exist for session/JWT products that share the same error module, prefer structured codes over message text:
| Helper | Code |
|---|---|
is_mfa_enrollment_required(err) | AUTH_MFA_NOT_ENROLLED |
is_step_up_required(err) | AUTH_STEP_UP_REQUIRED |
is_mfa_elevation_required(err) | AUTH_MFA_ELEVATION_REQUIRED |
is_mfa_last_factor_required(err) | AUTH_MFA_LAST_FACTOR_REQUIRED |
auth_error_code(err) | returns the code string or None |
auth_error_code(err) also names why an API key was refused: AUTH_SIGNATURE_INVALID, AUTH_TIMESTAMP_SKEW, AUTH_MISSING_HEADERS, AUTH_KEY_EXPIRED, AUTH_KEY_DISABLED, AUTH_IP_NOT_ALLOWED, and AUTH_API_KEY_NOT_ACCEPTED (exported as constants). AUTH_TIMESTAMP_SKEW raises the retryable PolyesterTimestampSkewError; the others raise PolyesterAuthError and need a
change by the caller.
Creating API keys requires a JWT/session client, these API-key SDKs do not expose create-key flows.
Retry sketch
from polyester import (
PolyesterError,
PolyesterRateLimitError,
PolyesterTransportError,
PolyesterRealtimeOverflowError,
)
import asyncio
async def with_retry(fn, *, attempts=3):
for i in range(attempts):
try:
return await fn()
except PolyesterRealtimeOverflowError:
raise # resubscribe; not a unary retry
except PolyesterRateLimitError as err:
await asyncio.sleep(err.retry_after or (0.5 * 2**i))
except PolyesterTransportError:
if i == attempts - 1:
raise
await asyncio.sleep(0.5 * 2**i)
except PolyesterError:
raise # permanent: fix input / auth / stateFor a single-order create, reconcile by the stable client_order_id before resubmitting; retained
reuse returns a duplicate conflict, not the earlier result. Reuse replayable request_id / client_trigger_id values for their logical mutations. See Requests & idempotency.