REST API Best Practices for Financial Data Applications
Essential patterns for building reliable financial applications on top of REST APIs: retry logic, rate limiting, data validation, and error handling.
Financial Data Is Different
Building on a financial data API is not the same as calling a weather API. Stale data can cost real money. A missed rate limit can leave your bot blind during a volatile move. An unhandled error can turn a small loss into a large one. These patterns are not optional.
1. Retry with Exponential Backoff
Network blips happen. The difference between a production-grade system and a weekend project is retry logic.
import requests
import time
import logging
logger = logging.getLogger(__name__)
def api_call_with_retry(
url: str,
headers: dict,
params: dict,
max_retries: int = 3,
base_delay: float = 1.0,
) -> dict:
"""Make an API call with exponential backoff retry."""
for attempt in range(max_retries + 1):
try:
resp = requests.get(url, headers=headers, params=params, timeout=10)
if resp.status_code == 429: # Rate limited
retry_after = int(resp.headers.get("Retry-After", base_delay * 2))
logger.warning(f"Rate limited. Waiting {retry_after}s")
time.sleep(retry_after)
continue
if resp.status_code >= 500: # Server error -- retry
raise requests.exceptions.HTTPError(f"Server error: {resp.status_code}")
resp.raise_for_status()
return resp.json()
except (requests.exceptions.ConnectionError,
requests.exceptions.Timeout,
requests.exceptions.HTTPError) as e:
if attempt == max_retries:
logger.error(f"Failed after {max_retries} retries: {e}")
raise
delay = base_delay * (2 ** attempt)
logger.warning(f"Attempt {attempt + 1} failed: {e}. Retrying in {delay}s")
time.sleep(delay)
# Usage
data = api_call_with_retry(
"https://tickatlas.com/v1/indicator",
headers={"X-API-Key": "YOUR_KEY"},
params={"symbol": "EURUSD", "indicator": "RSI_14", "timeframe": "H4"},
) 2. Respect Rate Limits
import threading
import time
class RateLimiter:
"""Token bucket rate limiter for API calls."""
def __init__(self, calls_per_minute: int):
self.calls_per_minute = calls_per_minute
self.interval = 60.0 / calls_per_minute
self.last_call = 0.0
self.lock = threading.Lock()
def wait(self):
with self.lock:
now = time.monotonic()
elapsed = now - self.last_call
if elapsed < self.interval:
time.sleep(self.interval - elapsed)
self.last_call = time.monotonic()
# Create a limiter matching your plan
limiter = RateLimiter(calls_per_minute=60) # Starter plan
def safe_api_call(url: str, headers: dict, params: dict) -> dict:
limiter.wait() # Block until we can make a call
return api_call_with_retry(url, headers, params)
3. Validate Response Data
Never trust API responses blindly. Validate that the data makes sense before acting on it.
from datetime import datetime, timezone, timedelta
def validate_indicator_response(data: dict, symbol: str) -> bool:
"""Validate that indicator data is fresh and reasonable."""
if not data.get("success"):
return False
inner = data.get("data", {})
# Check symbol matches
if inner.get("symbol") != symbol:
logger.error(f"Symbol mismatch: expected {symbol}, got {inner.get('symbol')}")
return False
# Check timestamp freshness
ts = inner.get("timestamp")
if ts:
data_time = datetime.fromisoformat(ts.replace("Z", "+00:00"))
age = datetime.now(timezone.utc) - data_time
if age > timedelta(hours=2):
logger.warning(f"Stale data: {age} old for {symbol}")
return False
# Check values exist and are numeric
values = inner.get("values", {})
if not values:
return False
for key, val in values.items():
if not isinstance(val, (int, float)):
logger.error(f"Non-numeric value for {key}: {val}")
return False
return True 4. Build a Resilient Client Class
class TickAtlasClient:
"""Production-grade API client with retry, rate limiting, and validation."""
def __init__(self, api_key: str, rate_limit: int = 60):
self.base_url = "https://tickatlas.com/v1"
self.headers = {"X-API-Key": api_key}
self.limiter = RateLimiter(rate_limit)
def get_indicator(self, symbol: str, indicator: str, timeframe: str) -> dict:
self.limiter.wait()
data = api_call_with_retry(
f"{self.base_url}/indicator",
self.headers,
{"symbol": symbol, "indicator": indicator, "timeframe": timeframe},
)
if not validate_indicator_response(data, symbol):
raise ValueError(f"Invalid response for {symbol}/{indicator}")
return data["data"]
def get_ohlcv(self, symbol: str, timeframe: str, limit: int = 50) -> list:
self.limiter.wait()
data = api_call_with_retry(
f"{self.base_url}/ohlcv",
self.headers,
{"symbol": symbol, "timeframe": timeframe, "limit": limit},
)
return data["data"]["candles"]
def get_spread(self, symbol: str) -> dict:
self.limiter.wait()
data = api_call_with_retry(
f"{self.base_url}/spread",
self.headers,
{"symbol": symbol},
)
return data["data"]
# Usage
client = TickAtlasClient("YOUR_API_KEY", rate_limit=60)
rsi = client.get_indicator("EURUSD", "RSI_14", "H4")
print(f"RSI: {rsi['values']['rsi']}") 5. Log Everything
Log every API call and its latency
When something goes wrong at 2 AM, logs are the only way to reconstruct what happened. Include response times so you can spot degradation.
Log rate limit headers
Track X-RateLimit-Remaining to proactively slow down before hitting limits rather than reacting to 429 errors.
Use structured logging
JSON-formatted logs are searchable. Include symbol, indicator, timeframe, response time, and status code in every log entry.
Related Reading
Try this with live data
Every account gets $2.50 in free PAYG credits. No card required — paste your API key and run the code above against live broker data.
Keep reading
All articles- Developer 9 min read
Rate Limiting Strategies: How to Maximize Your API Quota
Practical techniques for working within API rate limits. Learn caching, request batching, smart polling, and quota management to get the most out of every API call.
March 28, 2026
- Developer 10 min read
Caching Financial Data: Redis Patterns for Trading Applications
Learn smart caching strategies for financial data using Redis. Reduce API costs, improve latency, and maintain data freshness with TTL-based patterns.
March 28, 2026
- Developer 10 min read
Error Handling in Trading Systems: Why It Matters More Than You Think
Trading systems fail differently than web apps. Learn the error handling patterns that prevent small bugs from becoming expensive losses.
March 28, 2026