Skip to main content

Overview

The SDK provides structured error types, a retry decorator for transient failures, and configurable logging to help you build resilient trading applications.

APIError

All non-2xx HTTP responses from the API raise an APIError exception:
from limitless_sdk.api import APIError

try:
    result = await order_client.create_order(
        token_id=market.tokens.yes,
        price=0.65,
        size=10.0,
        side=Side.BUY,
        order_type=OrderType.GTC,
        market_slug="btc-above-100k-march-2025",
    )
except APIError as e:
    print(f"Status: {e.status_code}")
    print(f"Message: {e.message}")
PropertyTypeDescription
status_codeintHTTP status code (e.g. 400, 401, 429, 500)
messagestrRaw JSON response body from the API

Common Status Codes

CodeMeaningTypical Cause
400Bad RequestInvalid order parameters, malformed payload
401UnauthorizedMissing or invalid API key
403ForbiddenInsufficient permissions or geographic restriction
404Not FoundInvalid market slug or order ID
429Too Many RequestsRate limit exceeded
500Internal Server ErrorTransient server-side failure

Retry Decorator

Use the @retry_on_errors decorator to automatically retry operations that fail with specific status codes:
from limitless_sdk.api import retry_on_errors

@retry_on_errors(
    status_codes={500, 429},
    max_retries=3,
    delays=[1, 2, 4],  # seconds between retries (exponential backoff)
    on_retry=lambda attempt, error: print(f"Retry {attempt}: {error}"),
)
async def place_order_with_retry():
    return await order_client.create_order(
        token_id=market.tokens.yes,
        price=0.65,
        size=10.0,
        side=Side.BUY,
        order_type=OrderType.GTC,
        market_slug="btc-above-100k-march-2025",
    )

result = await place_order_with_retry()
ParameterTypeDescription
status_codesset[int]HTTP status codes that trigger a retry
max_retriesintMaximum number of retry attempts
delayslist[float]Delay in seconds before each retry (supports exponential backoff)
on_retrycallable | NoneOptional callback invoked on each retry with (attempt, error)
The decorator only retries on APIError exceptions whose status_code is in the status_codes set. All other exceptions propagate immediately.

RetryableClient

For broader retry coverage, wrap your HttpClient with the RetryableClient. This applies retry logic to all API calls made through the client:
from limitless_sdk.api import HttpClient, RetryableClient

http_client = HttpClient()
retryable_client = RetryableClient(
    client=http_client,
    status_codes={500, 502, 503, 429},
    max_retries=3,
    delays=[1, 2, 4],
)

# Use retryable_client in place of http_client
market_fetcher = MarketFetcher(retryable_client)
order_client = OrderClient(retryable_client, account)
Use RetryableClient for automated trading bots that need resilience against transient failures without decorating every function individually.

Debugging with ConsoleLogger

Enable verbose logging to trace requests, responses, and internal operations:
from limitless_sdk.types import ConsoleLogger, LogLevel

logger = ConsoleLogger(level=LogLevel.DEBUG)
At DEBUG level, the logger outputs:
  • Request and response headers
  • Venue cache hits and misses
  • Full API response bodies
  • Retry attempts and delays
  • WebSocket connection state changes
[DEBUG] GET /markets/btc-above-100k-march-2025
[DEBUG] Headers: {'X-API-Key': 'lmts_***', 'Content-Type': 'application/json'}
[DEBUG] Response 200 in 142ms
[DEBUG] Venue cached for btc-above-100k-march-2025: exchange=0xA1b2... adapter=0xD4e5...
[DEBUG] POST /orders
[DEBUG] Response 429 in 38ms
[DEBUG] Retry 1/3 after 1s — status 429
[DEBUG] POST /orders
[DEBUG] Response 200 in 156ms

Best Practices

1

Always handle APIError

Wrap every API call in a try/except block. Log the status_code and message for diagnostics:
try:
    result = await order_client.create_order(...)
except APIError as e:
    logger.error(f"Order failed: {e.status_code}{e.message}")
2

Retry only transient errors

Limit retries to status codes like 429 (rate limit) and 5xx (server errors). Do not retry 400 (bad request) or 401 (auth failure) as these require corrective action:
@retry_on_errors(status_codes={429, 500, 502, 503})
async def resilient_call():
    ...
3

Use exponential backoff

Increase delays between retries to avoid overwhelming the API:
@retry_on_errors(
    status_codes={429, 500},
    max_retries=4,
    delays=[1, 2, 4, 8],
)
async def backoff_call():
    ...
4

Close the client on exit

Always close the HttpClient to release connections, even if an error occurs:
try:
    # ... your trading logic
finally:
    await http_client.close()
5

Use DEBUG logging during development

Enable LogLevel.DEBUG to see the full request/response cycle. Switch to INFO or WARN in production to reduce noise.

Known Issues and Workarounds

The SDK’s NoOpLogger calls .warning() but the logger interface only defines .warn(). Add this patch at the top of your script:
from limitless_sdk.types.logger import NoOpLogger
NoOpLogger.warning = lambda self, msg, context=None: None
MarketFetcher.get_orderbook() may throw a Pydantic ValidationError when lastTradePrice is null. Use curl as a workaround:
import subprocess, json

def get_orderbook(slug: str):
    r = subprocess.run(
        ['curl', '-s', f'https://api.limitless.exchange/markets/{slug}/orderbook'],
        capture_output=True, text=True,
    )
    return json.loads(r.stdout)
Python’s built-in urllib.request gets blocked by the API (403 Forbidden). Use the SDK’s HttpClient, the requests library, or curl via subprocess instead.