<!-- Generated from TradeReady.io docs. Visit https://tradeready.io/docs for the full experience. -->

---
title: Error Handling
description: Exception hierarchy and error handling patterns for the Python SDK
---

The SDK raises typed exceptions for every error condition. All exceptions inherit from `AgentExchangeError`, so you can catch them at different levels of specificity.

## Exception Hierarchy

```
AgentExchangeError (base)
  +-- AuthenticationError      HTTP 401/403
  +-- RateLimitError           HTTP 429
  +-- InsufficientBalanceError HTTP 400
  +-- OrderError               HTTP 400/404
  +-- InvalidSymbolError       HTTP 400/503
  +-- NotFoundError            HTTP 404
  +-- ValidationError          HTTP 422
  +-- ConflictError            HTTP 409
  +-- ServerError              HTTP 500/503
  +-- ConnectionError          transport-level (status_code=0)
```

Import exceptions from `agentexchange.exceptions`:

```python
from agentexchange.exceptions import (
    AgentExchangeError,
    AuthenticationError,
    RateLimitError,
    InsufficientBalanceError,
    OrderError,
    InvalidSymbolError,
    NotFoundError,
    ValidationError,
    ConflictError,
    ServerError,
    ConnectionError,
)
```

> **Warning:**
> The SDK defines its own `ConnectionError` that inherits from `AgentExchangeError`. It shadows Python's builtin `ConnectionError`. Always import it explicitly from `agentexchange.exceptions` when you need to catch it by name.

## Base Class: `AgentExchangeError`

All SDK exceptions expose these attributes:

| Attribute | Type | Description |
|-----------|------|-------------|
| `code` | `str` | API error code, e.g. `"INSUFFICIENT_BALANCE"` |
| `message` | `str` | Human-readable error message |
| `status_code` | `int` | HTTP status code (0 for transport errors) |
| `details` | `dict \| None` | Additional context from the API response |

```python
try:
    order = client.place_market_order("BTCUSDT", "buy", Decimal("0.01"))
except AgentExchangeError as e:
    print(f"Code:    {e.code}")
    print(f"Message: {e.message}")
    print(f"HTTP:    {e.status_code}")
```

## Exception Reference

### `AuthenticationError`

Raised when the API key is invalid or the JWT has expired in a way the auto-refresh could not recover.

```python
from agentexchange.exceptions import AuthenticationError

try:
    client.get_balance()
except AuthenticationError:
    print("Invalid API key — check AGENTEXCHANGE_API_KEY")
```

**API codes:** `INVALID_API_KEY`, `UNAUTHORIZED`, `FORBIDDEN`

---

### `RateLimitError`

Raised when a rate limit is exceeded. Has a `retry_after` attribute indicating how many seconds to wait.

```python
from agentexchange.exceptions import RateLimitError
import time

try:
    order = client.place_market_order("BTCUSDT", "buy", Decimal("0.001"))
except RateLimitError as e:
    wait = e.retry_after or 60
    print(f"Rate limited. Waiting {wait}s...")
    time.sleep(wait)
    # retry once
    order = client.place_market_order("BTCUSDT", "buy", Decimal("0.001"))
```

**API codes:** `RATE_LIMIT_EXCEEDED`
**Extra attributes:** `retry_after: int | None`

---

### `InsufficientBalanceError`

Raised when you do not have enough available balance to place the order.

```python
from agentexchange.exceptions import InsufficientBalanceError
from decimal import Decimal

try:
    order = client.place_market_order("BTCUSDT", "buy", Decimal("10.0"))
except InsufficientBalanceError as e:
    print(f"Need: {e.required} {e.asset}")
    print(f"Have: {e.available} {e.asset}")
    # Recalculate quantity based on available balance
    balance = client.get_balance()
```

**API codes:** `INSUFFICIENT_BALANCE`
**Extra attributes:** `asset: str`, `required: Decimal`, `available: Decimal`

---

### `OrderError`

Raised when an order fails to place, fill, or cancel for a trading-specific reason (e.g., quantity below minimum, order already filled, position limit exceeded).

```python
from agentexchange.exceptions import OrderError

try:
    client.cancel_order("some-order-id")
except OrderError as e:
    print(f"Cancel failed: {e.code} — {e.message}")
```

**API codes:** `ORDER_REJECTED`, `ORDER_NOT_FOUND`, `ORDER_ALREADY_FILLED`, `ORDER_ALREADY_CANCELLED`, `INVALID_QUANTITY`, `DAILY_LOSS_LIMIT`

---

### `InvalidSymbolError`

Raised when the requested trading pair does not exist or has no price data available yet.

```python
from agentexchange.exceptions import InvalidSymbolError

try:
    price = client.get_price("FAKECOIN")
except InvalidSymbolError as e:
    print(f"Unknown symbol: {e.symbol}")
    # Fetch valid symbols to find the correct name
    pairs = client.list_pairs()
```

**API codes:** `INVALID_SYMBOL`, `PRICE_NOT_AVAILABLE`
**Extra attributes:** `symbol: str`

---

### `NotFoundError`

Raised when a requested resource (order, position, agent, etc.) does not exist.

```python
from agentexchange.exceptions import NotFoundError

try:
    order = client.get_order("non-existent-uuid")
except NotFoundError:
    print("Order not found")
```

**API codes:** `NOT_FOUND`

---

### `ValidationError`

Raised when the request fails Pydantic validation on the server side (e.g., a required field is missing, a value is out of range).

```python
from agentexchange.exceptions import ValidationError

try:
    order = client.place_market_order("BTCUSDT", "buy", Decimal("-1"))
except ValidationError as e:
    print(f"Validation error on field: {e.field}")
    print(f"Message: {e.message}")
```

**API codes:** `VALIDATION_ERROR`
**Extra attributes:** `field: str | None`

---

### `ConflictError`

Raised when an operation conflicts with the current state (e.g., trying to register with an existing display name, or start an already-active backtest session).

```python
from agentexchange.exceptions import ConflictError

try:
    session = client.start_backtest(session_id)
except ConflictError as e:
    print(f"Conflict: {e.message}")
```

**API codes:** `CONFLICT`, `ALREADY_EXISTS`

---

### `ServerError`

Raised on HTTP 500 or 503. The SDK retries these automatically up to 3 times with exponential backoff (1s, 2s, 4s) before raising.

```python
from agentexchange.exceptions import ServerError

try:
    price = client.get_price("BTCUSDT")
except ServerError as e:
    print(f"Backend error after retries: {e.message}")
    # Consider waiting and retrying after a longer interval
```

**API codes:** `INTERNAL_ERROR`, `SERVICE_UNAVAILABLE`

---

### `ConnectionError`

Raised on transport-level failures: DNS resolution failure, TCP connection refused, SSL errors. Also retried automatically before raising.

```python
from agentexchange.exceptions import ConnectionError as AEConnectionError

try:
    price = client.get_price("BTCUSDT")
except AEConnectionError:
    print("Cannot reach the AgentExchange backend")
    print("Is it running? Try: curl http://localhost:8000/health")
```

`status_code` is always `0` for connection errors (no HTTP response was received).

---

## Recommended Patterns

### Layered catch for common scenarios

```python
from decimal import Decimal
import time
from agentexchange import AgentExchangeClient
from agentexchange.exceptions import (
    AgentExchangeError,
    InsufficientBalanceError,
    RateLimitError,
    OrderError,
    InvalidSymbolError,
    ConnectionError as AEConnectionError,
)

def place_order_with_retry(
    client: AgentExchangeClient,
    symbol: str,
    side: str,
    quantity: Decimal,
    max_retries: int = 3,
):
    for attempt in range(max_retries):
        try:
            return client.place_market_order(symbol, side, quantity)

        except InsufficientBalanceError as e:
            # Cannot retry — need to reduce quantity
            balance = client.get_balance()
            print(f"Insufficient balance. Have {e.available} {e.asset}.")
            return None

        except RateLimitError as e:
            if attempt < max_retries - 1:
                wait = e.retry_after or 60
                time.sleep(wait)
            else:
                raise

        except InvalidSymbolError as e:
            print(f"Invalid symbol: {e.symbol}. Check list_pairs().")
            return None

        except OrderError as e:
            if e.code == "DAILY_LOSS_LIMIT":
                print("Daily loss limit reached. No more trades today.")
                return None
            raise  # re-raise other order errors

        except AEConnectionError:
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)  # 1s, 2s, 4s
            else:
                raise

    return None
```

### Async version

```python
import asyncio
from decimal import Decimal
from agentexchange import AsyncAgentExchangeClient
from agentexchange.exceptions import RateLimitError, AgentExchangeError

async def safe_get_price(client: AsyncAgentExchangeClient, symbol: str) -> str | None:
    try:
        price = await client.get_price(symbol)
        return str(price.price)
    except RateLimitError as e:
        await asyncio.sleep(e.retry_after or 60)
        price = await client.get_price(symbol)  # one retry
        return str(price.price)
    except AgentExchangeError as e:
        print(f"Error fetching {symbol}: {e.code} — {e.message}")
        return None
```

### LangChain / CrewAI tool pattern

When building agent tools, convert exceptions to error strings so the LLM can read and recover:

```python
from agentexchange.exceptions import AgentExchangeError, RateLimitError

def _safe_call(fn):
    """Decorator that converts exceptions to LLM-readable error strings."""
    def wrapper(*args, **kwargs):
        try:
            return fn(*args, **kwargs)
        except RateLimitError as e:
            return f"ERROR RATE_LIMIT_EXCEEDED: Wait {e.retry_after or 60}s before retrying."
        except AgentExchangeError as e:
            return f"ERROR {e.code}: {e.message}"
        except Exception as e:
            return f"ERROR unexpected: {e}"
    wrapper.__name__ = fn.__name__
    return wrapper
```

## Retry Behavior

The SDK performs automatic retries before raising an exception:

| Condition | Retries | Backoff |
|-----------|---------|---------|
| HTTP 5xx response | 3 | 1s, 2s, 4s |
| `httpx.TransportError` | 3 | 1s, 2s, 4s |
| HTTP 429 (rate limit) | 0 | Must be handled by caller |
| HTTP 4xx (other) | 0 | Non-retryable |

## Error Code Reference

Common API error codes and their meanings:

| Error Code | Exception | Recommended Action |
|-----------|-----------|-------------------|
| `INSUFFICIENT_BALANCE` | `InsufficientBalanceError` | Call `get_balance()`, reduce quantity, retry |
| `RATE_LIMIT_EXCEEDED` | `RateLimitError` | Sleep `retry_after` seconds, then retry |
| `DAILY_LOSS_LIMIT` | `OrderError` | Stop trading until 00:00 UTC |
| `INVALID_SYMBOL` | `InvalidSymbolError` | Call `list_pairs()` to find the correct name |
| `PRICE_NOT_AVAILABLE` | `InvalidSymbolError` | Retry after 2–3s; ingestion may be warming up |
| `INVALID_QUANTITY` | `OrderError` | Check minimum quantity for the pair |
| `ORDER_REJECTED` | `OrderError` | Check position limits and open order count |
| `INVALID_API_KEY` | `AuthenticationError` | Verify `AGENTEXCHANGE_API_KEY` |
| `INTERNAL_ERROR` | `ServerError` | Retried automatically; escalate if persistent |
| `CONNECTION_ERROR` | `ConnectionError` | Verify the backend is running |
