# Errors & retries

> The typed exceptions the SDK raises, timeouts, and what it retries.

Source: https://docs.stxapp.io/sdks/python/errors-and-retries/

What each endpoint can return is in the [API reference](/api/rest/), and request limits are in [Rate limits](/concepts/rate-limits/). The SDK raises one exception per HTTP status, so you can catch the case you care about.

## Exceptions

`str(exc)` is the API's error message, and `exc.status_code`, `exc.body`, `exc.method` and `exc.path` describe the request.

| Status | Exception | Typical cause |
|---|---|---|
| 400 | `STXValidationException` | A bad parameter: `status has invalid value: foo` |
| 401 | `STXAuthenticationException` | Bad key, bad signature, or a clock more than 30 s off |
| 403 | `STXForbiddenException` | A `read_only` key on an order route |
| 404 | `STXNotFoundException` | Unknown id, or one belonging to another account |
| 422 | `STXRejectedException` | The exchange refused: closed market, insufficient funds, fractional quantity |
| 429 | `STXRateLimitException` | Too many requests; `retry_after` in seconds when sent |
| 5xx | `STXServerException` | Server failure |

All of those derive from `STXAPIException`, which derives from `STXException`. Three more:

- `STXTransportException`: no answer at all (connection refused, DNS, TLS, timeout)
- `STXConfigException`: the client is misconfigured (unknown region, missing key, bad PEM)
- `STXChannelException`: a WebSocket join or control event was refused; `.reply` holds the reason

```python
from stx import STX, STXNotFoundException, STXRejectedException, STXValidationException

with STX() as client:
    try:
        client.order("00000000-0000-0000-0000-000000000000")
    except STXNotFoundException as exc:
        print("404:", exc)

    try:
        client.markets(status="not-a-status")
    except STXValidationException as exc:
        print(exc.status_code, exc)

    market = next(
        m
        for m in client.iter_markets(status="open", trading=True, sort_by="event_start", sort_direction="desc")
        if m.event_status == "scheduled"
    )
    try:
        client.place_order(market.market_id, "buy", "limit", quantity="1")
    except (STXRejectedException, STXValidationException) as exc:
        print(exc.status_code, exc)
```

## What is retried

| Failure | `GET`, `DELETE` | `POST` |
|---|---|---|
| 429 | retried, waiting `Retry-After` | retried, waiting `Retry-After` |
| 5xx | retried | **not** retried |
| Connection error or timeout | retried | **not** retried |
| 400, 401, 403, 404, 422 | not retried | not retried |

A `POST` that failed with a server error or a dropped connection may still have placed the order, so the SDK does not send it again. Use `client_order_id` and look the order up (see [Avoid duplicate orders](/sdks/python/trading#avoid-duplicate-orders)).

Every attempt is signed afresh. Backoff is exponential with jitter: 0.5 s, 1 s, 2 s, capped at 30 s, over 3 attempts by default. `timeout` is seconds per HTTP request (default 30).

```python
from stx import NO_RETRY, STX, RetryPolicy

policy = RetryPolicy(max_attempts=5, initial_backoff=0.25, max_backoff=10)
with STX(retry=policy, timeout=10.0) as client:
    print(len(client.markets(limit=1)))

with STX(retry=NO_RETRY) as client:
    print(client.me().scope)
```

## WebSocket failures

The socket reconnects on its own (see [Reconnects](/sdks/python/websockets#reconnects)). A channel that the server closes or errors is rejoined; a rejoin refused as `unauthorized` closes that channel instead of retrying forever. `ReconnectPolicy(max_attempts=...)` bounds reconnects; after the last one, `run_forever()` returns and every channel's iterator ends.

## Logging

The SDK logs to the `stx` logger (and `stx.ws` for the socket): requests at DEBUG, retries and reconnects at INFO and WARNING.

```python
import logging

from stx import STX

logging.basicConfig(level=logging.INFO)
logging.getLogger("stx").setLevel(logging.DEBUG)

with STX() as client:
    client.me()
```
