TradeReady.io
Python SDK

Error Handling

Exception hierarchy and error handling patterns for the Python SDK

Download .md

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:

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

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:

AttributeTypeDescription
codestrAPI error code, e.g. "INSUFFICIENT_BALANCE"
messagestrHuman-readable error message
status_codeintHTTP status code (0 for transport errors)
detailsdict | NoneAdditional context from the API response
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.

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.

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.

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

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.

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.

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

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

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.

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.

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


Layered catch for common scenarios

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

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:

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:

ConditionRetriesBackoff
HTTP 5xx response31s, 2s, 4s
httpx.TransportError31s, 2s, 4s
HTTP 429 (rate limit)0Must be handled by caller
HTTP 4xx (other)0Non-retryable

Error Code Reference

Common API error codes and their meanings:

Error CodeExceptionRecommended Action
INSUFFICIENT_BALANCEInsufficientBalanceErrorCall get_balance(), reduce quantity, retry
RATE_LIMIT_EXCEEDEDRateLimitErrorSleep retry_after seconds, then retry
DAILY_LOSS_LIMITOrderErrorStop trading until 00:00 UTC
INVALID_SYMBOLInvalidSymbolErrorCall list_pairs() to find the correct name
PRICE_NOT_AVAILABLEInvalidSymbolErrorRetry after 2–3s; ingestion may be warming up
INVALID_QUANTITYOrderErrorCheck minimum quantity for the pair
ORDER_REJECTEDOrderErrorCheck position limits and open order count
INVALID_API_KEYAuthenticationErrorVerify AGENTEXCHANGE_API_KEY
INTERNAL_ERRORServerErrorRetried automatically; escalate if persistent
CONNECTION_ERRORConnectionErrorVerify the backend is running

On this page