Every non-success path through the SDK raises a subclass of STXException. The hierarchy is designed so you can catch at whatever specificity you need.
Exception hierarchy
STXException — catch-all for anything the SDK raises
├── STXAuthException — any auth problem
│ ├── STXAuthenticationFailedException — bad email / password
│ ├── STXTwoFactorRequiredException — account needs 2FA
│ └── STXTokenExpiredException — JWT expired between refreshes
├── STXForbiddenException — 403 — authenticated but not allowed
├── STXValidationException — 4xx — server rejected your input
│ └── STXInvalidParameterException
├── STXNotFoundException — 404
├── STXRateLimitException — 429 — has `.retry_after: float`
├── STXServerException — 5xx — server-side error
└── STXTransportException — network, DNS, TLS, connection reset
Import from stx.exceptions:
from stx.exceptions import (
STXException,
STXAuthException,
STXAuthenticationFailedException,
STXTwoFactorRequiredException,
STXTokenExpiredException,
STXForbiddenException,
STXValidationException,
STXInvalidParameterException,
STXNotFoundException,
STXRateLimitException,
STXServerException,
STXTransportException,
)
Catching strategies
Coarse (catch-all)
Medium (group by class)
Fine (per-subclass)
from stx.exceptions import STXException
try :
client.confirmOrder( params = ... )
except STXException as e:
log.error( "SDK error: %s " , e.message)
Error fields
Every STXException exposes:
Field Type Notes messagestrHuman-readable summary status_codeOptional[int]HTTP status when applicable errorslist[dict]Normalized GraphQL / HTTP error records request_idOptional[str]Server-side trace ID (include when reporting bugs)
STXRateLimitException additionally exposes:
retry_after: float — seconds the server told you to wait (from Retry-After header, or a sane default).
Automatic retries
Every call through STX/AsyncSTX is wrapped in a RetryPolicy. Defaults:
Setting Default max_attempts3 initial_backoff0.25 s max_backoff10 s jitterDecorrelated jitter (AWS-style) Honors Retry-After Yes
What gets retried
Automatically retried:
STXTransportException — network blips, connection resets, TLS errors
STXRateLimitException — with the server’s Retry-After
STXServerException (5xx) — transient server errors
Never retried:
STXAuthException and subclasses — won’t fix itself on a retry
STXForbiddenException — permission doesn’t change on retry
STXValidationException — your input won’t change on retry
STXNotFoundException — resource doesn’t exist
confirmOrder, cancelOrder, and other non-idempotent mutations — excluded by default to avoid double-submits
Overriding the policy
Per-client:
from stx import STX
from stx._retry import RetryPolicy
client = STX(
region = "ontario" , env = "production" ,
retry = RetryPolicy(
max_attempts = 5 ,
initial_backoff = 0.5 ,
max_backoff = 30.0 ,
jitter = True ,
),
)
Per-call:
from stx._retry import RetryPolicy
client.marketInfos(
retry = RetryPolicy( max_attempts = 10 , initial_backoff = 0.1 ),
)
Disable entirely:
client.confirmOrder( params = ... , retry = RetryPolicy. DISABLED )
The SDK already disables retries on non-idempotent mutations (confirmOrder, cancelOrder). You rarely need RetryPolicy.DISABLED yourself — the common case is raising max_attempts for flaky networks, not lowering it.
Backoff schedule
With defaults (initial=0.25, max=10), the delays after each failure roughly follow:
attempt 1 → wait 0.25–0.5 s → attempt 2 → wait 0.5–1.5 s → attempt 3 → wait 1.5–4 s → …
Decorrelated jitter means two clients retrying in parallel won’t synchronize their attempts.
Handling rate limits
STXRateLimitException carries retry_after — always use it instead of guessing:
from stx.exceptions import STXRateLimitException
try :
client.marketInfos()
except STXRateLimitException as e:
time.sleep(e.retry_after)
client.marketInfos()
Async:
import asyncio
from stx.exceptions import STXRateLimitException
try :
await client.marketInfos()
except STXRateLimitException as e:
await asyncio.sleep(e.retry_after)
await client.marketInfos()
The built-in retry policy does this for you already — you only need to handle it manually if you’ve disabled retries or exhausted max_attempts.
Debugging
Every exception includes a request_id — quote it when filing a bug:
try :
client.confirmOrder( params = ... )
except STXException as e:
log.error( "request_id= %s errors= %s " , e.request_id, e.errors)
raise
Enable debug logging to see every retry attempt and backoff delay:
import logging
logging.getLogger( "stx" ).setLevel(logging. DEBUG )
Example: robust wrapper
A paranoid wrapper around confirmOrder that handles the interesting cases:
import asyncio
from stx.exceptions import (
STXAuthException, STXRateLimitException, STXValidationException, STXException,
)
async def place_order_safely ( client , params ):
try :
return await client.confirmOrder( params = params)
except STXValidationException as e:
# Input was bad — don't retry, surface to the caller.
raise
except STXAuthException:
# JWT refresh failed — force re-login on next call.
from stx.user import User
User().clear()
return await client.confirmOrder( params = params)
except STXRateLimitException as e:
await asyncio.sleep(e.retry_after)
return await client.confirmOrder( params = params)
except STXException as e:
log.error( "confirmOrder failed: request_id= %s " , e.request_id)
raise
Next
Configuration Tune the retry policy, logging, and schema source.
WebSockets WS reconnect uses the same RetryPolicy surface.