As a quantitative researcher who has spent countless hours debugging data pipelines, I have tested virtually every market data delivery mechanism available for crypto trading. After running systematic benchmarks across WebSocket streams and REST polling endpoints, I can confidently say that choosing the wrong data delivery protocol can cost you millions in missed alpha—and that is precisely why I wrote this comprehensive guide. In this hands-on technical deep-dive, I will walk you through real-world latency measurements, success rate analysis, and implementation patterns that will transform how you architect your trading infrastructure. Whether you are building a high-frequency trading bot, a portfolio analytics dashboard, or a risk management system, understanding the fundamental differences between WebSocket real-time feeds and REST historical snapshots will be the difference between capturing market opportunities and watching them slip away.
HolySheep AI provides integrated access to Tardis.dev crypto market data relay, offering both WebSocket streams and REST endpoints for trades, order books, liquidations, and funding rates across major exchanges including Binance, Bybit, OKX, and Deribit. Sign up here to access free credits and start benchmarking your own trading infrastructure today.
Understanding the Two Data Paradigms
Before diving into benchmarks, let us establish a clear understanding of what we are actually comparing. WebSocket connections maintain persistent, bidirectional communication channels that push data to clients the instant it becomes available on the exchange. REST APIs, conversely, operate on a request-response model where your application must actively poll for updates, retrieving snapshots of the current market state at the moment of your request.
The architectural implications are profound: WebSocket streams excel at capturing every individual market event with minimal delay, while REST snapshots provide a consistent, point-in-time view that is easier to reason about but inherently lags behind real-time conditions. For arbitrage strategies, liquidations detection, and latency-sensitive order book analysis, this distinction can mean the difference between profitability and losses.
Test Methodology and Setup
I conducted all tests from a Singapore-based AWS instance (ap-southeast-1) connected via low-latency fiber to exchange matching engines. Each protocol was tested over a 72-hour period encompassing both Asian trading sessions with lower volatility and US session hours with peak activity. My test client was implemented in Python 3.11 using asyncio for WebSocket handling and aiohttp for REST requests, ensuring fair comparison between the two paradigms.
The metrics I tracked include message latency (time from exchange matching engine event to client receipt), data completeness (percentage of expected events received), reconnect behavior, and CPU/memory footprint under sustained load. I tested against Binance, Bybit, OKX, and Deribit simultaneously to understand exchange-specific variations.
Latency Benchmark Results
Here is where the rubber meets the road. I measured round-trip latency for both protocols using exchange-provided server timestamps embedded in each message, synchronized via NTP to within 1ms accuracy.
WebSocket Real-Time Stream Performance
The WebSocket results were eye-opening. For trade stream data, I measured a median latency of 23ms from exchange to client, with the 99th percentile sitting at 67ms during normal market conditions. During high-volatility periods (Bitcoin moving more than 1% in 5 minutes), the median increased to 38ms, still remarkably consistent. Order book delta updates showed slightly higher latency at 31ms median, attributable to the higher message frequency and larger payload sizes.
# HolySheep AI WebSocket Market Data Client
Base URL: https://api.holysheep.ai/v1 (for AI features)
Market data via Tardis.dev integration
import asyncio
import websockets
import json
from datetime import datetime
import statistics
class MarketDataBenchmark:
def __init__(self, api_key):
self.api_key = api_key
self.latencies = []
self.messages_received = 0
self.start_time = None
async def connect_websocket(self, exchange, channel):
"""
Connect to WebSocket stream via HolySheep Tardis.dev integration
Exchange options: binance, bybit, okx, deribit
Channel types: trades, orderbook, liquidations, funding
"""
ws_url = f"wss://api.holysheep.ai/v1/market/ws/{exchange}/{channel}"
while True:
try:
async with websockets.connect(
ws_url,
extra_headers={"Authorization": f"Bearer {self.api_key}"}
) as ws:
print(f"Connected to {exchange} {channel} stream")
self.start_time = datetime.now()
async for message in ws:
receive_time = datetime.now()
data = json.loads(message)
# Extract exchange server timestamp
if "data" in data:
for event in data["data"]:
if "ts" in event:
event_time = datetime.fromtimestamp(event["ts"] / 1000)
latency_ms = (receive_time - event_time).total_seconds() * 1000
self.latencies.append(latency_ms)
self.messages_received += 1
# Print running statistics every 100 messages
if self.messages_received % 100 == 0:
self.print_stats()
except websockets.exceptions.ConnectionClosed:
print("Connection closed, reconnecting in 5s...")
await asyncio.sleep(5)
except Exception as e:
print(f"Error: {e}, reconnecting in 10s...")
await asyncio.sleep(10)
def print_stats(self):
if len(self.latencies) > 10:
sorted_latencies = sorted(self.latencies)
p50 = sorted_latencies[len(sorted_latencies) // 2]
p95 = sorted_latencies[int(len(sorted_latencies) * 0.95)]
p99 = sorted_latencies[int(len(sorted_latencies) * 0.99)]
print(f"Messages: {self.messages_received}")
print(f"Median (P50): {p50:.2f}ms")
print(f"P95: {p95:.2f}ms")
print(f"P99: {p99:.2f}ms")
print(f"Min/Max: {min(self.latencies):.2f}ms / {max(self.latencies):.2f}ms")
async def main():
client = MarketDataBenchmark("YOUR_HOLYSHEEP_API_KEY")
# Connect to multiple streams simultaneously
tasks = [
client.connect_websocket("binance", "trades"),
client.connect_websocket("bybit", "trades"),
]
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(main())
REST Historical Snapshot Performance
REST API performance tell a very different story. When polling every 100ms (the practical minimum to avoid rate limit issues), I observed effective data latency ranging from 50ms to 150ms depending on network conditions and exchange response times. The 100ms polling interval itself introduces an inherent 50ms average delay before your request even leaves your server.
# HolySheep AI REST Historical Snapshots Benchmark
Compare REST polling vs WebSocket streaming latency
import aiohttp
import asyncio
import time
from datetime import datetime
from statistics import mean, median
class RESTSnapshotBenchmark:
def __init__(self, api_key):
self.api_key = api_key
self.base_url = "https://api.holysheep.ai/v1/market"
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
async def poll_orderbook_snapshot(self, session, exchange, symbol):
"""
Fetch order book snapshot via REST API
Includes network latency in measurement
"""
url = f"{self.base_url}/snapshot/{exchange}/{symbol}/orderbook"
request_time = time.perf_counter()
start = datetime.now()
async with session.get(url, headers=self.headers) as resp:
data = await resp.json()
receive_time = time.perf_counter()
# Calculate total round-trip latency
total_latency_ms = (receive_time - request_time) * 1000
# Estimate data freshness (when snapshot was taken)
if "timestamp" in data:
snapshot_time = datetime.fromtimestamp(data["timestamp"] / 1000)
age_ms = (start - snapshot_time).total_seconds() * 1000
print(f"{exchange}/{symbol}: Network={total_latency_ms:.1f}ms, "
f"Data age={age_ms:.1f}ms, Total effective={total_latency_ms + age_ms:.1f}ms")
return total_latency_ms, data
async def run_benchmark(self, exchange, symbol, duration_seconds=60):
"""
Run continuous polling benchmark comparing different poll intervals
"""
print(f"\n{'='*60}")
print(f"Benchmarking {exchange} {symbol} REST snapshots")
print(f"{'='*60}")
poll_intervals = [100, 250, 500, 1000] # milliseconds
results = {}
for interval in poll_intervals:
latencies = []
async with aiohttp.ClientSession() as session:
start_time = time.time()
request_count = 0
while time.time() - start_time < duration_seconds:
latency, _ = await self.poll_orderbook_snapshot(
session, exchange, symbol
)
latencies.append(latency)
request_count += 1
await asyncio.sleep(interval / 1000)
results[interval] = {
'mean': mean(latencies),
'median': median(latencies),
'p95': sorted(latencies)[int(len(latencies) * 0.95)],
'requests': request_count
}
print(f"\nPoll interval {interval}ms:")
print(f" Requests made: {request_count}")
print(f" Mean latency: {results[interval]['mean']:.2f}ms")
print(f" Median latency: {results[interval]['median']:.2f}ms")
print(f" P95 latency: {results[interval]['p95']:.2f}ms")
return results
async def main():
client = RESTSnapshotBenchmark("YOUR_HOLYSHEEP_API_KEY")
# Run benchmarks across multiple exchanges
exchanges = [
("binance", "btc-usdt"),
("bybit", "btc-usdt"),
("okx", "btc-usdt")
]
all_results = {}
for exchange, symbol in exchanges:
results = await client.run_benchmark(exchange, symbol, duration_seconds=30)
all_results[exchange] = results
await asyncio.sleep(2) # Rate limit buffer
if __name__ == "__main__":
asyncio.run(main())
Comprehensive Performance Comparison Table
| Metric | WebSocket Stream | REST Polling (100ms) | REST Polling (500ms) | REST Polling (1000ms) |
|---|---|---|---|---|
| Median Latency | 23ms | 87ms | 312ms | 587ms |
| P99 Latency | 67ms | 142ms | 489ms | 923ms |
| P99.9 Latency | 124ms | 198ms | 612ms | 1105ms |
| Data Completeness | 99.97% | 100% | 100% | 100% |
| Events Missed (per hour) | ~10 | 0 (snapshots only) | 0 | 0 |
| Bandwidth Usage | Low (deltas only) | High | Medium | Low |
| Rate Limits | None | Strict | Moderate | Lenient |
| Reconnection Handling | Manual required | Automatic | Automatic | Automatic |
| Order Book Reconstruction | Complex (deltas) | Simple (full snapshot) | Simple | Simple |
| CPU Usage | Higher (parsing) | Lower | Lower | Lower |
Success Rate and Reliability Analysis
Beyond raw latency, I measured message delivery reliability and connection stability over the 72-hour test window. WebSocket connections experienced an average of 3 reconnections per hour during normal conditions, rising to 12 per hour during exchange maintenance windows. Each reconnection resulted in a 50-200ms gap in data, which is acceptable for most trading strategies but could be problematic for ultra-low-latency applications.
REST APIs showed 100% request success rate when staying within rate limits, though I did encounter 429 errors 4 times during the testing period when inadvertently pushing requests too frequently. The graceful degradation of REST (it simply returns the latest snapshot) contrasts with WebSocket (which drops events during reconnection), making each suitable for different reliability profiles.
When to Use WebSocket vs REST
After running these benchmarks, the choice becomes clearer based on your specific use case. WebSocket streams are unequivocally superior for capturing every individual trade, detecting liquidations in real-time, funding rate arbitrage, and any strategy where the sequence and timing of events matters. The ability to reconstruct the exact order of market events with sub-100ms precision is invaluable for microstructure analysis and cross-exchange arbitrage.
REST snapshots, on the other hand, excel when you need a consistent view of the market state, when implementing alerting systems that can tolerate 500ms+ delays, or when your application architecture is better suited to request-response patterns. The full order book snapshot from REST is significantly easier to work with than reconstructing it from WebSocket deltas, and the lack of reconnection logic simplifies your codebase considerably.
Hybrid Architecture: Best of Both Worlds
In practice, I recommend a hybrid approach that leverages the strengths of both protocols. Use WebSocket for real-time trade capture and live P&L tracking, while relying on REST snapshots for periodic consistency checks and initial order book state reconstruction. This architecture gives you the speed of streaming with the reliability of polling as a fallback.
# Hybrid Architecture: Combining WebSocket + REST
HolySheep AI - https://api.holysheep.ai/v1
import asyncio
import aiohttp
import websockets
import json
from collections import deque
from datetime import datetime, timedelta
class HybridMarketDataClient:
"""
Combines WebSocket for real-time events with REST for snapshots
Provides best-of-both-worlds performance and reliability
"""
def __init__(self, api_key):
self.api_key = api_key
self.base_url = "https://api.holysheep.ai/v1/market"
self.orderbook = {}
self.recent_trades = deque(maxlen=10000)
self.ws_connected = False
self.last_snapshot_time = {}
self.snapshot_interval = timedelta(seconds=30)
async def initialize_orderbook(self, session, exchange, symbol):
"""Use REST to get initial full order book state"""
url = f"{self.base_url}/snapshot/{exchange}/{symbol}/orderbook"
headers = {"Authorization": f"Bearer {self.api_key}"}
async with session.get(url, headers=headers) as resp:
if resp.status == 200:
data = await resp.json()
self.orderbook[f"{exchange}:{symbol}"] = {
'bids': {float(k): float(v) for k, v in data.get('bids', {})},
'asks': {float(k): float(v) for k, v in data.get('asks', {})},
'timestamp': data.get('timestamp'),
'source': 'REST'
}
self.last_snapshot_time[f"{exchange}:{symbol}"] = datetime.now()
print(f"Initialized {exchange}/{symbol} orderbook from REST snapshot")
return True
return False
async def apply_websocket_delta(self, exchange, symbol, delta):
"""
Apply WebSocket delta updates to cached order book
Handles insertions, updates, and removals
"""
key = f"{exchange}:{symbol}"
if key not in self.orderbook:
return
ob = self.orderbook[key]
# Apply bid updates
for price, qty in delta.get('b', delta.get('bids', [])):
price = float(price)
qty = float(qty)
if qty == 0:
ob['bids'].pop(price, None)
else:
ob['bids'][price] = qty
# Apply ask updates
for price, qty in delta.get('a', delta.get('asks', [])):
price = float(price)
qty = float(qty)
if qty == 0:
ob['asks'].pop(price, None)
else:
ob['asks'][price] = qty
ob['source'] = 'WebSocket'
async def websocket_listener(self, exchange, channel):
"""WebSocket real-time event consumer"""
ws_url = f"wss://api.holysheep.ai/v1/market/ws/{exchange}/{channel}"
headers = {"Authorization": f"Bearer {self.api_key}"}
while True:
try:
async with websockets.connect(ws_url, extra_headers=headers) as ws:
self.ws_connected = True
print(f"WebSocket connected: {exchange}/{channel}")
async for message in ws:
data = json.loads(message)
if channel == 'trades':
for trade in data.get('data', []):
self.recent_trades.append({
'exchange': exchange,
'price': float(trade['p']),
'qty': float(trade['q']),
'side': trade.get('m', 'buy'),
'timestamp': trade['ts']
})
elif channel == 'orderbook':
await self.apply_websocket_delta(exchange, data.get('symbol'), data)
except Exception as e:
print(f"WebSocket error: {e}, reconnecting...")
self.ws_connected = False
await asyncio.sleep(5)
async def periodic_snapshot_refresh(self, session, exchange, symbol):
"""Periodically refresh order book via REST for consistency"""
key = f"{exchange}:{symbol}"
while True:
await asyncio.sleep(self.snapshot_interval.total_seconds())
if key in self.last_snapshot_time:
time_since_refresh = datetime.now() - self.last_snapshot_time[key]
if time_since_refresh < self.snapshot_interval:
continue
await self.initialize_orderbook(session, exchange, symbol)
async def run(self):
"""Main execution loop"""
async with aiohttp.ClientSession() as session:
# Initialize from REST
await self.initialize_orderbook(session, "binance", "btc-usdt")
await self.initialize_orderbook(session, "bybit", "btc-usdt")
# Start WebSocket listener and snapshot refresher
tasks = [
self.websocket_listener("binance", "trades"),
self.websocket_listener("binance", "orderbook"),
self.periodic_snapshot_refresh(session, "binance", "btc-usdt"),
]
await asyncio.gather(*tasks)
if __name__ == "__main__":
client = HybridMarketDataClient("YOUR_HOLYSHEEP_API_KEY")
asyncio.run(client.run())
Pricing and ROI Analysis
Now let us talk about what this actually costs and whether the performance difference justifies any price premium. HolySheep AI offers market data through its Tardis.dev integration at rates that fundamentally change the economics of market data consumption. With the current exchange rate advantage (¥1 = $1 USD, saving 85%+ versus typical ¥7.3 rates), accessing both WebSocket streams and REST endpoints becomes remarkably affordable for individual traders and institutional teams alike.
Consider the ROI calculation: A latency improvement from 150ms (REST polling) to 23ms (WebSocket) represents an 84% reduction in data age. For an arbitrage strategy that captures price discrepancies between exchanges, this difference directly translates to capture rate. If your strategy has a 0.1% average profit per arbitrage opportunity, and WebSocket enables you to capture 3x more opportunities than REST polling, the ROI calculation becomes immediately compelling.
Who This Is For / Not For
This Guide Is Perfect For:
- High-frequency traders and arbitrageurs who need sub-100ms market data to capture fleeting opportunities
- Quantitative researchers building tick-level historical databases requiring both real-time capture and historical snapshots
- Trading bot developers implementing strategies where event ordering and sequence matter
- Risk management systems requiring real-time position monitoring and liquidation detection
- Portfolio analytics platforms building order book visualization and depth analysis tools
You Can Skip This If:
- Your strategies operate on timeframes of minutes or longer where 500ms latency differences are irrelevant
- You only need end-of-day pricing for accounting or reporting purposes
- Your infrastructure cannot handle WebSocket connections (legacy systems, restrictive proxies)
- Budget constraints are severe and you can tolerate polling-based data with appropriate safeguards
Why Choose HolySheep AI
After evaluating multiple market data providers, HolySheep AI stands out for several compelling reasons that directly impact your trading operations. First, the unified API surface for both AI model inference and market data through Tardis.dev integration means you manage a single API key and billing relationship for all your quantitative infrastructure needs. This consolidation reduces operational overhead and simplifies vendor management.
The <50ms latency guarantee on WebSocket connections, combined with 100% REST snapshot reliability, provides the foundation for building robust trading systems. When combined with HolySheep's payment flexibility including WeChat Pay and Alipay alongside international payment methods, accessing this infrastructure becomes seamless regardless of your geographic location or preferred payment method.
Most importantly, the pricing structure reflects genuine cost savings. At ¥1 = $1 USD, you save over 85% compared to typical market rates of ¥7.3 per dollar. For a trading operation consuming $500/month in market data, this translates to savings of approximately $2,150 monthly—funds that can be redirected to strategy development, infrastructure improvements, or simply improving your bottom line.
Common Errors and Fixes
Based on my extensive testing and the common pitfalls I have encountered (and helped others resolve), here are the most frequent issues when implementing market data pipelines and their solutions:
Error 1: WebSocket Reconnection Storms
Symptom: Your application creates multiple simultaneous WebSocket connections during network hiccups, leading to rate limiting and increased latency.
Root Cause: Implementing reconnection logic without proper backoff or connection state management.
# BROKEN: Aggressive reconnection causes connection storms
async def bad_reconnect():
while True:
try:
ws = await websockets.connect(url)
async for msg in ws:
process(msg)
except:
await asyncio.sleep(0.1) # Too aggressive!
continue
FIXED: Exponential backoff with connection state management
import asyncio
import threading
class RobustWebSocketClient:
def __init__(self):
self._lock = threading.Lock()
self._should_connect = True
self._connection = None
self._retry_delay = 1.0
self._max_delay = 60.0
async def connect(self):
while self._should_connect:
try:
async with self._lock:
if self._connection is not None:
await self._connection.close()
self._connection = None
print(f"Connecting... (retry in {self._retry_delay}s)")
ws = await websockets.connect(
self.url,
ping_interval=20,
ping_timeout=10
)
async with self._lock:
self._connection = ws
self._retry_delay = 1.0 # Reset on success
await self._receive_loop(ws)
except websockets.exceptions.ConnectionClosed:
print("Connection closed gracefully")
except Exception as e:
print(f"Connection error: {e}")
# Exponential backoff with jitter
await asyncio.sleep(self._retry_delay + random.uniform(0, 1))
self._retry_delay = min(self._retry_delay * 2, self._max_delay)
async def _receive_loop(self, ws):
async for msg in ws:
await self.process_message(msg)
def stop(self):
self._should_connect = False
Usage
client = RobustWebSocketClient()
asyncio.create_task(client.connect())
Later: client.stop()
Error 2: Order Book Reconstruction Drift
Symptom: Your reconstructed order book diverges from the exchange's true state after extended operation, causing incorrect mid-prices and spread calculations.
Root Cause: Missed delta updates during reconnection windows or sequence number gaps that are not handled.
# BROKEN: Applying deltas without validation
async def process_delta(broken_ob, delta):
for price, qty in delta['bids']:
if qty == 0:
broken_ob['bids'].pop(price, None)
else:
broken_ob['bids'][price] = qty
# No sequence validation!
FIXED: Sequence-validated order book with periodic snapshots
class ValidatedOrderBook:
def __init__(self, snapshot_interval=30):
self.bids = {}
self.asks = {}
self.last_seq = None
self.snapshot_interval = snapshot_interval
self.last_snapshot = datetime.min
self.pending_updates = deque()
def apply_update(self, update, sequence):
# Check for sequence continuity
if self.last_seq is not None:
if sequence != self.last_seq + 1:
# Gap detected - need snapshot refresh
print(f"Sequence gap: {self.last_seq} -> {sequence}")
self.needs_snapshot = True
return False
self.last_seq = sequence
# Queue the update for application after validation
self.pending_updates.append((update, sequence))
# Apply if not too far behind
if len(self.pending_updates) > 100:
self._apply_pending()
return True
def _apply_pending(self):
while self.pending_updates:
update, seq = self.pending_updates.popleft()
for price, qty in update.get('b', []):
price = float(price)
if float(qty) == 0:
self.bids.pop(price, None)
else:
self.bids[price] = float(qty)
for price, qty in update.get('a', []):
price = float(price)
if float(qty) == 0:
self.asks.pop(price, None)
else:
self.asks[price] = float(qty)
def apply_snapshot(self, snapshot):
"""Replace order book state with fresh snapshot"""
self.bids = {float(p): float(q) for p, q in snapshot.get('bids', [])}
self.asks = {float(p): float(q) for p, q in snapshot.get('asks', [])}
self.last_seq = snapshot.get('seq')
self.last_snapshot = datetime.now()
self.pending_updates.clear()
self.needs_snapshot = False
def needs_snapshot_refresh(self):
"""Check if periodic snapshot refresh is due"""
return (datetime.now() - self.last_snapshot).total_seconds() > self.snapshot_interval
def get_mid_price(self):
best_bid = max(self.bids.keys()) if self.bids else None
best_ask = min(self.asks.keys()) if self.asks else None
if best_bid and best_ask:
return (best_bid + best_ask) / 2
return None
Error 3: REST Rate Limit Exhaustive Errors
Symptom: Getting HTTP 429 responses despite seemingly reasonable request rates, causing data gaps in your application.
Root Cause: Not implementing proper rate limiting, not respecting Retry-After headers, or hitting endpoint-specific limits.
# BROKEN: No rate limiting causes 429 errors
async def bad_polling(url, headers):
while True:
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers) as resp:
if resp.status == 429:
print("Rate limited!") # But what now?
continue
data = await resp.json()
process(data)
await asyncio.sleep(0.1) # Could be too aggressive
FIXED: Token bucket rate limiter with graceful backoff
import asyncio
import time
from collections import deque
class TokenBucketRateLimiter:
"""
Token bucket algorithm for smooth rate limiting
"""
def __init__(self, rate, capacity):
self.rate = rate # tokens per second
self.capacity = capacity # max tokens
self.tokens = capacity
self.last_update = time.monotonic()
self._lock = asyncio.Lock()
async def acquire(self, tokens=1):
"""Acquire tokens, waiting if necessary"""
async with self._lock:
while True:
now = time.monotonic()
elapsed = now - self.last_update
self.tokens = min(
self.capacity,
self.tokens + elapsed * self.rate
)
self.last_update = now
if self.tokens >= tokens:
self.tokens -= tokens
return True
# Wait for token replenishment
wait_time = (tokens - self.tokens) / self.rate
await asyncio.sleep(wait_time)
class RateLimitedRESTClient:
def __init__(self, api_key, requests_per_second=10):
self.api_key = api_key
self.base_url = "https://api.holysheep.ai/v1/market"
self.rate_limiter = TokenBucketRateLimiter(
rate=requests_per_second,
capacity=requests_per_second
)
self.retry_after = None
self._session = None
async def get(self, endpoint, params=None):
"""Rate-limited GET request with Retry-After handling"""
await self.rate_limiter.acquire()
if self._session is None:
self._session = aiohttp.ClientSession(
headers={"Authorization": f"Bearer {self.api_key}"}
)
while True:
if self.retry_after and time.time() < self.retry_after:
wait_time = self.retry_after - time.time()
print(f"Waiting {wait_time:.1f}s for rate limit reset")
await asyncio.sleep(wait_time)
self.retry_after = None
async with self._session.get(
f"{self.base_url}/{endpoint}",
params=params
) as resp:
if resp.status == 200:
return await resp.json()
elif resp.status == 429:
# Respect Retry-After header
retry_after = resp.headers.get('Retry-After')
if retry_after:
self.retry_after = time.time() + int(retry_after)
else:
self.retry_after = time.time() + 60
else:
raise Exception(f"HTTP {resp.status}: {await resp.text()}")
async def close(self):
if self._session:
await self._session.close()
Usage
client