Historical Market Data REST API Development
REST API for historical data is an interface between time series storage and consumers: trading bots, analytics systems, backtesting platforms. API quality is determined by response speed, filtering flexibility, and reliability under load.
Endpoint Design
Basic set of endpoints for market data:
GET /v1/ohlcv/{exchange}/{symbol}
?from=2024-01-01T00:00:00Z
&to=2024-01-31T23:59:59Z
&interval=1h
&limit=1000
GET /v1/trades/{exchange}/{symbol}
?from=1704067200000
&to=1704153600000
&limit=10000
GET /v1/orderbook/{exchange}/{symbol}/snapshot
?timestamp=1704067200000
&depth=20
GET /v1/tickers/{exchange}/{symbol}/history
?from=2024-01-01
&to=2024-01-02
&fields=close,volume
Use ISO 8601 for user interface and Unix timestamp (milliseconds) for programmatic access. Support both formats through automatic detection.
Parameters and Validation
from fastapi import FastAPI, Query
from datetime import datetime
from typing import Optional
@app.get("/v1/ohlcv/{exchange}/{symbol}")
async def get_ohlcv(
exchange: str,
symbol: str,
interval: str = Query("1h", regex="^(1m|5m|15m|1h|4h|1d|1w)$"),
from_time: datetime = Query(..., alias="from"),
to_time: datetime = Query(..., alias="to"),
limit: int = Query(1000, ge=1, le=50000),
):
if (to_time - from_time).days > 365:
raise HTTPException(400, "Date range cannot exceed 365 days")
data = await candle_service.get_candles(
exchange, symbol, interval, from_time, to_time, limit
)
return {"data": data, "count": len(data)}
Pagination for Large Datasets
Cursor-based pagination is more efficient than offset for time series:
{
"data": [...],
"cursor": {
"next": "eyJ0aW1lc3RhbXAiOiAxNzA0MDY3MjAwMDAwfQ==",
"has_more": true
}
}
Cursor — base64-encoded JSON with the last timestamp in the current page. On the next request, the client sends ?cursor=... instead of ?from=....
Caching
Historical data is immutable — an ideal candidate for aggressive caching:
-
HTTP Cache-Control:
public, max-age=3600for data older than one day - Redis Cache: for frequently requested ranges (popular symbols, last 30 days)
- Query Cache in TimescaleDB / ClickHouse for heavy aggregations
Strategy: if requested range is completely in the past (closed) — cache for 24 hours. If includes current time — cache for 60 seconds.
Rate Limiting and Authentication
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.get("/v1/ohlcv/{exchange}/{symbol}")
@limiter.limit("100/minute")
async def get_ohlcv(...):
...
For commercial API — pricing plans via API keys with different limits: free (10 req/min, 30 days history), paid (1000 req/min, full history).
Documentation via OpenAPI
FastAPI automatically generates OpenAPI schema. Additionally — examples of requests/responses in documentation, format descriptions, error codes. Swagger UI and ReDoc out of the box — clients can test API directly in browser without additional tools.







