Guides
Error Handling

Error Handling

Handle errors gracefully and implement retry strategies

Overview

SnipeRoute uses HTTP status codes and structured error responses to indicate success or failure. This guide covers common errors and how to handle them.

All API errors return a JSON response with a detail field explaining what went wrong.

Error Response Format

{
  "detail": "Error message describing what went wrong",
  "error_code": "specific_error_code",
  "request_id": "req_abc123"
}

HTTP Status Codes

Status CodeMeaningAction
200 OKRequest successfulProcess response
201 CreatedTrade Intent createdTrack intent ID
400 Bad RequestInvalid requestFix parameters
401 UnauthorizedInvalid API keyCheck authentication
403 ForbiddenNo permissionCheck API key scope
404 Not FoundResource not foundVerify intent_id/broker_id
409 ConflictDuplicate intent_idUse unique intent_id
422 Unprocessable EntityValidation errorFix validation errors
429 Too Many RequestsRate limitedSlow down requests
500 Internal Server ErrorServer errorRetry with backoff
503 Service UnavailableService downRetry with backoff

Common Errors

400 Bad Request

Cause: Invalid request parameters

{
  "detail": "limit_price is required for limit orders"
}

Solution: Add missing required fields

# Missing limit_price
intent = TradeIntentRequest(
    intent_id="trade_001",
    symbol="AAPL",
    side=OrderSide.BUY,
    quantity=Decimal("10"),
    order_type=OrderType.LIMIT  # Missing limit_price!
)
 
# Correct
intent = TradeIntentRequest(
    intent_id="trade_001",
    symbol="AAPL",
    side=OrderSide.BUY,
    quantity=Decimal("10"),
    order_type=OrderType.LIMIT,
    limit_price=Decimal("175.00")  # Added!
)

401 Unauthorized

Cause: Invalid or missing API key

{
  "detail": "Invalid API key"
}

Solution: Check your API key

# Invalid API key
client = SnipeRouteClient(
    api_key="sk_test_invalid_key"
)
 
# Correct
client = SnipeRouteClient(
    api_key=os.getenv("SNIPEROUTE_API_KEY")
)

404 Not Found

Cause: Intent or resource doesn't exist

{
  "detail": "Trade Intent not found"
}

Solution: Verify intent_id

# Check if intent exists before querying
try:
    intent = await client.get_intent_by_external_id("trade_001")
except SnipeRouteAPIError as e:
    if e.status_code == 404:
        print("Intent not found")

409 Conflict

Cause: Duplicate intent_id

{
  "detail": "Trade Intent with this intent_id already exists"
}

Solution: Use unique intent IDs

import uuid
 
# Generate unique ID
intent_id = f"trade_{uuid.uuid4()}"
 
intent = TradeIntentRequest(
    intent_id=intent_id,  # Guaranteed unique
    symbol="AAPL",
    side=OrderSide.BUY,
    quantity=Decimal("10"),
    order_type=OrderType.MARKET,
    broker_id="my_broker"
)

422 Unprocessable Entity

Cause: Validation errors

{
  "detail": [
    {
      "loc": ["body", "quantity"],
      "msg": "ensure this value is greater than 0",
      "type": "value_error.number.not_gt"
    }
  ]
}

Solution: Fix validation errors

# Invalid quantity
intent = TradeIntentRequest(
    intent_id="trade_001",
    symbol="AAPL",
    side=OrderSide.BUY,
    quantity=Decimal("0"),  # Must be > 0!
    order_type=OrderType.MARKET,
    broker_id="my_broker"
)
 
# Correct
intent = TradeIntentRequest(
    intent_id="trade_001",
    symbol="AAPL",
    side=OrderSide.BUY,
    quantity=Decimal("10"),
    order_type=OrderType.MARKET,
    broker_id="my_broker"
)

429 Too Many Requests

Cause: Rate limit exceeded

{
  "detail": "Rate limit exceeded. Retry after 60 seconds.",
  "retry_after": 60
}

Solution: Implement rate limiting

import asyncio
from sniperoute.exceptions import RateLimitError
 
async def submit_with_retry(client, intent):
    while True:
        try:
            return await client.create_intent(intent)
        except RateLimitError as e:
            retry_after = e.retry_after or 60
            print(f"Rate limited. Waiting {retry_after}s...")
            await asyncio.sleep(retry_after)

500 Internal Server Error

Cause: Server-side error

{
  "detail": "Internal server error",
  "request_id": "req_abc123"
}

Solution: Retry with exponential backoff

import asyncio
 
async def submit_with_backoff(client, intent, max_retries=3):
    for attempt in range(max_retries):
        try:
            return await client.create_intent(intent)
        except SnipeRouteAPIError as e:
            if e.status_code >= 500:
                if attempt == max_retries - 1:
                    raise
                wait_time = 2 ** attempt
                print(f"Server error. Retrying in {wait_time}s...")
                await asyncio.sleep(wait_time)
            else:
                raise

Python SDK Error Handling

Using try/except

from sniperoute.exceptions import (
    SnipeRouteAPIError,
    ValidationError,
    AuthenticationError,
    NotFoundError,
    ConflictError,
    RateLimitError
)
 
async def create_trade_intent():
    try:
        intent = TradeIntentRequest(
            intent_id="trade_001",
            symbol="AAPL",
            side=OrderSide.BUY,
            quantity=Decimal("10"),
            order_type=OrderType.MARKET,
            broker_id="my_broker"
        )
 
        response = await client.create_intent(intent)
        print(f"Success: {response.intent_id}")
 
    except ValidationError as e:
        print(f"Validation error: {e.message}")
        # Fix validation issues and retry
 
    except AuthenticationError as e:
        print(f"Authentication failed: {e.message}")
        # Check API key
 
    except ConflictError as e:
        print(f"Duplicate intent_id: {e.message}")
        # Use a different intent_id
 
    except RateLimitError as e:
        print(f"Rate limited. Retry after {e.retry_after}s")
        # Wait and retry
 
    except NotFoundError as e:
        print(f"Resource not found: {e.message}")
        # Verify broker_id exists
 
    except SnipeRouteAPIError as e:
        print(f"API error: {e.message}")
        print(f"   Status code: {e.status_code}")
        print(f"   Request ID: {e.request_id}")
        # Handle other errors

Retry Strategies

Exponential Backoff

import asyncio
from typing import Optional
 
async def create_intent_with_retry(
    client: SnipeRouteClient,
    intent: TradeIntentRequest,
    max_retries: int = 3,
    base_delay: float = 1.0
) -> Optional[TradeIntentResponse]:
    """Create Trade Intent with exponential backoff retry."""
 
    for attempt in range(max_retries):
        try:
            return await client.create_intent(intent)
 
        except RateLimitError as e:
            # Rate limited - use retry_after from response
            wait_time = e.retry_after or (base_delay * (2 ** attempt))
            print(f"Rate limited. Waiting {wait_time}s...")
            await asyncio.sleep(wait_time)
 
        except SnipeRouteAPIError as e:
            # Server error - retry with backoff
            if e.status_code >= 500:
                if attempt == max_retries - 1:
                    raise  # Last attempt failed
 
                wait_time = base_delay * (2 ** attempt)
                print(f"Server error. Retrying in {wait_time}s...")
                await asyncio.sleep(wait_time)
            else:
                # Client error - don't retry
                raise
 
    return None

Circuit Breaker

import asyncio
from datetime import datetime, timedelta
 
class CircuitBreaker:
    def __init__(self, failure_threshold: int = 5, timeout: int = 60):
        self.failure_threshold = failure_threshold
        self.timeout = timeout
        self.failures = 0
        self.last_failure_time: Optional[datetime] = None
        self.state = "closed"  # closed, open, half_open
 
    async def call(self, func, *args, **kwargs):
        if self.state == "open":
            # Check if timeout has passed
            if datetime.now() - self.last_failure_time > timedelta(seconds=self.timeout):
                self.state = "half_open"
            else:
                raise Exception("Circuit breaker is open")
 
        try:
            result = await func(*args, **kwargs)
            # Success - reset failures
            if self.state == "half_open":
                self.state = "closed"
            self.failures = 0
            return result
 
        except SnipeRouteAPIError as e:
            if e.status_code >= 500:
                self.failures += 1
                self.last_failure_time = datetime.now()
 
                if self.failures >= self.failure_threshold:
                    self.state = "open"
                    print(f"Circuit breaker opened after {self.failures} failures")
 
            raise
 
# Usage
circuit_breaker = CircuitBreaker()
 
async def create_intent_with_circuit_breaker(client, intent):
    return await circuit_breaker.call(client.create_intent, intent)

Broker-Specific Errors

Insufficient Funds

{
  "detail": "Broker rejected order: Insufficient buying power",
  "broker_error_code": "insufficient_funds"
}

Solution: Check account balance before submitting

Invalid Symbol

{
  "detail": "Broker rejected order: Symbol not found",
  "broker_error_code": "invalid_symbol"
}

Solution: Verify symbol is valid for the broker

Market Closed

{
  "detail": "Broker rejected order: Market is closed",
  "broker_error_code": "market_closed"
}

Solution: Check market hours or use extended_hours: true

Logging Errors

import logging
 
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
 
async def create_intent_with_logging(client, intent):
    try:
        response = await client.create_intent(intent)
        logger.info(f"Intent created: {response.intent_id}")
        return response
 
    except SnipeRouteAPIError as e:
        logger.error(
            f"Failed to create intent: {e.message}",
            extra={
                "intent_id": intent.intent_id,
                "status_code": e.status_code,
                "request_id": e.request_id
            }
        )
        raise

Monitoring and Alerts

Track Error Rates

from collections import defaultdict
 
error_counts = defaultdict(int)
 
async def create_intent_with_metrics(client, intent):
    try:
        return await client.create_intent(intent)
    except SnipeRouteAPIError as e:
        error_counts[e.status_code] += 1
 
        # Alert if error rate is high
        if error_counts[e.status_code] > 10:
            await send_alert(f"High error rate for {e.status_code}")
 
        raise

Next Steps