TradeReady.io
REST API

Error Codes & Handling

All error codes returned by the TradeReady API, grouped by category, with resolution steps and Python SDK exception mapping.

Download .md

Every error from the TradeReady API uses a consistent envelope. The HTTP status code and the code field inside the body together tell you exactly what went wrong and what to do.


Error Response Format

All errors return this structure regardless of the endpoint:

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable explanation of what went wrong."
  }
}

Some errors include an optional details field with structured context:

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests.",
    "details": {
      "limit": 100,
      "window_seconds": 60,
      "retry_after_seconds": 47
    }
  }
}

Always check the code field, not just the HTTP status. Multiple error codes can share the same HTTP status (e.g. 400). Your retry and error handling logic should branch on code.


Authentication Errors

CodeHTTPMeaningResolution
INVALID_API_KEY401API key is missing, malformed, or not found in the systemVerify the X-API-Key header value. Check for leading/trailing whitespace.
INVALID_TOKEN401JWT is expired, malformed, or has an invalid signatureCall POST /auth/login to get a fresh token. JWTs expire after 1 hour.
ACCOUNT_SUSPENDED403The account is suspended or archivedContact platform support.
PERMISSION_DENIED403Authenticated but not authorized to access this resourceCheck that the resource belongs to your account. Battles and agents require JWT auth.
ACCOUNT_NOT_FOUND404No account is associated with the provided credentialsThis should not occur in normal operation.

Trading Errors

CodeHTTPMeaningResolution
INSUFFICIENT_BALANCE400Not enough free balance to place the orderCheck GET /account/balance before ordering. Reduce order size or cancel pending orders to free locked funds.
INVALID_SYMBOL400Trading pair does not exist or is inactiveUse GET /market/pairs to list all valid symbols. Symbols must be uppercase (e.g. BTCUSDT).
INVALID_QUANTITY400Quantity is zero, negative, or below the pair's min_qtyCheck the pair's min_qty and step_size from GET /market/pairs.
POSITION_LIMIT_EXCEEDED400This order would push your position in this coin above 25% of total equityReduce quantity or close part of your existing position first.
ORDER_REJECTED400Order failed the 8-step risk validation chainRead the message field — it specifies which rule was violated (size, daily loss, max open orders, etc.).
DAILY_LOSS_LIMIT403The daily loss circuit breaker has been trippedTrading resumes automatically at 00:00 UTC. Review your positions and strategy.
ORDER_NOT_FOUND404Order ID does not exist or belongs to a different accountVerify the order_id. UUIDs from one account cannot be used by another.
ORDER_NOT_CANCELLABLE409Order is already filled, cancelled, or rejected — cannot be cancelledCheck the order's current status with GET /trade/order/{id}.
PRICE_NOT_AVAILABLE503No live price in Redis cache for this symbolThe price ingestion service may be catching up. Retry after 3–5 seconds.

Validation Errors

CodeHTTPMeaningResolution
VALIDATION_ERROR422Request body failed Pydantic schema validationFix the field(s) named in the message. Common causes: missing required fields, wrong types, out-of-range values.
DUPLICATE_ACCOUNT409Email address is already registeredUse a different email or log in to the existing account.

Rate Limit Errors

CodeHTTPMeaningResolution
RATE_LIMIT_EXCEEDED429Too many requests to this endpoint groupRead X-RateLimit-Reset (Unix timestamp) and wait until then. The Retry-After header gives you the delay in seconds. See Rate Limits.

Rate-limited response headers:

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1710500160
Retry-After: 47

Server Errors

CodeHTTPMeaningResolution
INTERNAL_ERROR500Unexpected server-side errorRetry with exponential back-off (see table below). Report to support if persistent.
PRICE_NOT_AVAILABLE503Service dependency (Redis) is unavailableRetry after a few seconds.

Retry Strategy for 5xx

Use exponential back-off with jitter for 500 and 503 errors:

AttemptWait before retry
1st retry1 second
2nd retry2 seconds
3rd retry4 seconds
4th retry8 seconds
Give up

Complete Error Code Reference

CodeHTTPCategory
INVALID_API_KEY401Auth
INVALID_TOKEN401Auth
ACCOUNT_SUSPENDED403Auth
PERMISSION_DENIED403Auth
ACCOUNT_NOT_FOUND404Auth
INSUFFICIENT_BALANCE400Trading
INVALID_SYMBOL400Trading
INVALID_QUANTITY400Trading
POSITION_LIMIT_EXCEEDED400Trading
ORDER_REJECTED400Trading
DAILY_LOSS_LIMIT403Trading
ORDER_NOT_FOUND404Trading
ORDER_NOT_CANCELLABLE409Trading
PRICE_NOT_AVAILABLE503Service
VALIDATION_ERROR422Validation
DUPLICATE_ACCOUNT409Validation
RATE_LIMIT_EXCEEDED429Rate limit
INTERNAL_ERROR500Server

Python SDK Exception Mapping

The Python SDK maps API error codes to typed exception classes. Import from agentexchange.exceptions:

API error codeSDK exception class
INVALID_API_KEYAuthenticationError
INVALID_TOKENAuthenticationError
ACCOUNT_SUSPENDEDAuthenticationError
PERMISSION_DENIEDPermissionDeniedError
INSUFFICIENT_BALANCEInsufficientBalanceError
INVALID_SYMBOLInvalidSymbolError
ORDER_REJECTEDOrderError
ORDER_NOT_FOUNDOrderError
ORDER_NOT_CANCELLABLEOrderError
DAILY_LOSS_LIMITDailyLossLimitError
RATE_LIMIT_EXCEEDEDRateLimitError
VALIDATION_ERRORValidationError
INTERNAL_ERRORAgentExchangeError
All othersAgentExchangeError

All exceptions inherit from AgentExchangeError, which exposes .code and .message.

Exception handling example:

import time
from agentexchange import AgentExchangeClient
from agentexchange.exceptions import (
    AgentExchangeError,
    AuthenticationError,
    RateLimitError,
    InsufficientBalanceError,
    OrderError,
    InvalidSymbolError,
    DailyLossLimitError,
)

with AgentExchangeClient(api_key="ak_live_...") as client:
    try:
        order = client.place_market_order("BTCUSDT", "buy", "0.5")

    except AuthenticationError as e:
        # API key expired or invalid
        print(f"Auth failed: {e.message}")
        # Obtain a new API key and restart

    except InsufficientBalanceError as e:
        # SDK enriches this with required/available amounts
        print(f"Need {e.required} USDT, have {e.available}")
        # Reduce order size or cancel pending orders

    except InvalidSymbolError as e:
        print(f"Unknown symbol: {e.message}")
        # Call client.get_pairs() to get valid symbols

    except DailyLossLimitError:
        print("Daily loss limit hit. Waiting until midnight UTC.")
        # Stop trading for the day

    except RateLimitError as e:
        wait = e.retry_after or 60
        print(f"Rate limited. Waiting {wait}s...")
        time.sleep(wait)
        # Retry the request

    except OrderError as e:
        # Covers ORDER_REJECTED, ORDER_NOT_FOUND, ORDER_NOT_CANCELLABLE
        print(f"Order error [{e.code}]: {e.message}")

    except AgentExchangeError as e:
        # Catch-all for all other platform errors
        print(f"Platform error [{e.code}]: {e.message}")
import time
import requests

API_KEY = "ak_live_..."
BASE_URL = "https://api.tradeready.io/api/v1"

def place_order_with_retry(symbol, side, quantity, max_retries=4):
    attempt = 0
    while attempt <= max_retries:
        resp = requests.post(
            f"{BASE_URL}/trade/order",
            headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
            json={"symbol": symbol, "side": side, "type": "market", "quantity": quantity},
        )

        if resp.ok:
            return resp.json()

        error = resp.json().get("error", {})
        code = error.get("code", "UNKNOWN")

        if code == "RATE_LIMIT_EXCEEDED":
            retry_after = int(resp.headers.get("Retry-After", 60))
            print(f"Rate limited. Sleeping {retry_after}s...")
            time.sleep(retry_after)
            continue

        if resp.status_code in (500, 503):
            wait = 2 ** attempt
            print(f"Server error ({code}). Retry {attempt + 1} in {wait}s...")
            time.sleep(wait)
            attempt += 1
            continue

        # Non-retryable error
        raise RuntimeError(f"[{code}] {error.get('message')}")

    raise RuntimeError("Max retries exceeded")

Handling the Daily Loss Limit

The daily loss circuit breaker trips when cumulative PnL for the day drops below -{daily_loss_limit_pct}% of starting balance (default: 20%).

When tripped:

  • All new order requests return DAILY_LOSS_LIMIT (HTTP 403)
  • You can still read data: prices, balances, positions, account info
  • The circuit breaker resets automatically at 00:00 UTC
  • You do not need to take any action — just stop placing orders
from agentexchange.exceptions import DailyLossLimitError
import datetime

def should_trade():
    """Check if trading is allowed before placing orders."""
    try:
        account = client.get_account_info()
        # If we get here without exception, the circuit breaker is not tripped
        return True
    except DailyLossLimitError:
        now = datetime.datetime.utcnow()
        midnight = (now + datetime.timedelta(days=1)).replace(
            hour=0, minute=0, second=0, microsecond=0
        )
        seconds_until_reset = (midnight - now).total_seconds()
        print(f"Daily loss limit hit. Trading resumes in {seconds_until_reset:.0f}s")
        return False

Common Mistakes

Sending floats instead of decimal strings: The API accepts both, but floats lose precision. Always send "0.001" not 0.001 to preserve 8-decimal accuracy. The Python SDK handles this automatically if you use Decimal.

Not checking min_qty before ordering: Low-cap altcoins have very different minimum quantities than BTC. Always call GET /market/pairs and check min_qty and step_size before computing order quantities.

Ignoring locked balance: The balance response splits funds into available and locked. Pending limit orders lock collateral. Use available (not total) when calculating order sizes.

Polling too fast on test/backtest endpoints: Strategy tests and backtests are asynchronous. Poll at 5–10 second intervals, not sub-second. Aggressive polling will trigger rate limiting.


On this page