Skip to main content
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

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:
FieldTypeNotes
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:
SettingDefault
max_attempts3
initial_backoff0.25 s
max_backoff10 s
jitterDecorrelated jitter (AWS-style)
Honors Retry-AfterYes

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_afteralways 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.