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 Code | Meaning | Action |
|---|---|---|
200 OK | Request successful | Process response |
201 Created | Trade Intent created | Track intent ID |
400 Bad Request | Invalid request | Fix parameters |
401 Unauthorized | Invalid API key | Check authentication |
403 Forbidden | No permission | Check API key scope |
404 Not Found | Resource not found | Verify intent_id/broker_id |
409 Conflict | Duplicate intent_id | Use unique intent_id |
422 Unprocessable Entity | Validation error | Fix validation errors |
429 Too Many Requests | Rate limited | Slow down requests |
500 Internal Server Error | Server error | Retry with backoff |
503 Service Unavailable | Service down | Retry 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:
raisePython 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 errorsRetry 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 NoneCircuit 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
}
)
raiseMonitoring 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