REST vs WebSocket: Choosing APIs for Market Data Workflows
A trader opens a dashboard before the market opens, requests the last 2,000 candles, and receives a complete historical series. Thirty seconds later, the same screen updates because a new order book message arrived. The dashboard needs two different behaviors: bounded historical retrieval and continuous real-time delivery.
The distinction matters because a historical query has a finite answer, while a live feed produces an unbounded stream. Treating both as “the same data over the Internet” often creates unnecessary latency, fragile reconnection logic, or systems that cannot recover cleanly after a network interruption.
This article explains when to use REST, when to use WebSocket, and how to design the boundary between them. It also provides production-oriented Python examples, a deployment comparison, and a design for combining historical context with live updates without creating inconsistent state.
1. The Core Design Principle
The correct choice depends on the semantic shape of the data request.
| Requirement | Preferred interface | Reason |
|---|---|---|
| Retrieve a bounded historical range | REST | The request has explicit parameters and a finite response |
| Retrieve the current candle or latest quote | REST | The response represents a point-in-time snapshot |
| Subscribe to continuous market events | WebSocket | The client maintains a persistent connection and receives updates |
| Receive periodic snapshots | REST | Polling provides simple recovery and predictable request volume |
| Reconcile missed real-time messages | REST | A fresh snapshot provides a recovery anchor |
| Stream an unbounded event sequence | WebSocket | Push delivery avoids repeated polling overhead |
REST is request-response communication. A client sends a request, the server returns a response, and the interaction ends. WebSocket is a persistent, bidirectional channel. After the handshake, either side can send messages without opening a new HTTP request for every update.
That architectural difference explains why the interfaces should not be reversed in normal market-data workflows. Historical data is a bounded query, so REST maps naturally to its semantics. Real-time data is an open-ended event stream, so WebSocket maps naturally to that semantic.
A REST endpoint can technically support long polling or server-sent events. A WebSocket can technically request a snapshot. Neither capability makes the alternative design inherently wrong. The question is whether the interface matches the application’s state model, failure model, and traffic pattern.
2. Why Historical Data Naturally Fits REST
A historical request usually has a clear boundary:
symbol + interval + start_time + end_time + limit
The server can validate the request, query a time-indexed store, and return a complete result. The client knows when the operation has finished. This makes REST particularly suitable for:
- Backtesting across completed historical periods
- Loading candles before a strategy starts
- Exporting data for offline analysis
- Recovering a known time range after an interruption
- Requesting a current snapshot for a dashboard initialization
- Integrating with batch jobs and scheduled research pipelines
The response is naturally bounded. A request for 500 one-hour candles should return 500 records, subject to the server’s pagination and limit rules.
A representative historical request looks like this:
import os
import requests
API_KEY = os.environ.get("TICKDB_API_KEY")
BASE_URL = "https://api.tickdb.ai/v1/market/kline"
if not API_KEY:
raise RuntimeError("TICKDB_API_KEY is not configured")
response = requests.get(
f"{BASE_URL}/AAPL.US",
headers={"X-API-Key": API_KEY},
params={
"interval": "1h",
"limit": 500,
},
timeout=(3.05, 10),
)
response.raise_for_status()
payload = response.json()
if payload.get("code", 0) != 0:
raise RuntimeError(
f"Market data request failed: "
f"{payload.get('code')} {payload.get('message')}"
)
candles = payload["data"]
The key design choice is the timeout. A market-data request must not wait indefinitely for a server response. A connect timeout of slightly more than three seconds and a read timeout of ten seconds provide a practical balance between failure detection and normal network latency.
Production systems should also handle API-level errors explicitly. The following helper separates successful responses from authentication, symbol, and rate-limit failures:
import os
import time
from typing import Any
def handle_api_error(response, symbol: str | None = None) -> Any:
"""Handle a TickDB JSON response and return its data payload."""
payload = response.json()
code = payload.get("code", 0)
if code == 0:
return payload.get("data")
if code in (1001, 1002):
raise ValueError(
"Invalid or missing API key. Check TICKDB_API_KEY."
)
if code == 2002:
raise KeyError(
f"Symbol {symbol!r} was not found. "
"Verify it through the available-symbols endpoint."
)
if code == 3001:
retry_after = int(response.headers.get("Retry-After", "5"))
time.sleep(retry_after)
return None
raise RuntimeError(
f"Unexpected API error {code}: {payload.get('message')}"
)
The 3001 response is not a generic failure. It indicates that the client exceeded a rate limit. The correct behavior is to read the Retry-After header and wait before retrying. Blindly retrying at the same speed can worsen the congestion.
Historical Data Requires a Consistent Boundary
A backtest should not mix completed periods with an endpoint intended to return the current candle. Use the historical kline endpoint for completed historical data, and reserve the latest-candle endpoint for a live dashboard or monitoring service.
That separation prevents a subtle but damaging class of inconsistency: the same bar can change while a backtest is running. A backtest needs a stable input. A live dashboard needs a changing snapshot.
3. Why Real-Time Data Naturally Fits WebSocket
A real-time market feed changes continuously. The client does not know the final size of the response in advance. Polling every second can create three problems:
Unnecessary request volume
Most responses may contain no meaningful change.Detection latency
The newest event is observed only when the next poll completes.Poor connection economics
Each poll repeats HTTP request and response overhead.
A WebSocket changes the communication model. The client opens one connection, subscribes to a symbol or channel, and receives messages as they occur.
Client Server
|------- WebSocket handshake --->|
|<------ connection established --|
|------- subscribe request ------->|
|<------ market message 1 ---------|
|<------ market message 2 ---------|
|<------ market message 3 ---------|
This model is appropriate for:
- Live price or candle updates
- Order book depth snapshots and incremental changes
- Trade-by-trade events where supported
- Exchange or market-status notifications
- Low-latency alert evaluation
- Maintaining a continuously updated strategy state
The depth channel is particularly well suited to a persistent connection. A client can maintain the current order book state and apply incoming updates instead of repeatedly reconstructing the full book from snapshots.
The available depth coverage is market-dependent: L1 for US equities, L1–L10 for Hong Kong equities and crypto, and no depth support for forex, precious metals, or indices. A client should therefore discover the channel’s actual capabilities rather than assume that every symbol exposes the same number of levels.
Production WebSocket Client
A minimal WebSocket example is easy to write and easy to misuse. A production client must handle heartbeats, reconnects, rate limits, malformed messages, and stale sessions.
import asyncio
import json
import os
import random
import time
from typing import Any
import websockets
API_KEY = os.environ.get("TICKDB_API_KEY")
WS_URL = "wss://api.tickdb.ai/v1/stream"
if not API_KEY:
raise RuntimeError("TICKDB_API_KEY is not configured")
def parse_message(raw: str) -> dict[str, Any]:
"""Parse and validate a market-data message."""
try:
message = json.loads(raw)
except json.JSONDecodeError as exc:
raise ValueError("WebSocket returned invalid JSON") from exc
if not isinstance(message, dict):
raise ValueError("WebSocket message must be a JSON object")
return message
async def run() -> None:
retry = 0
reconnect_base = 1.0
reconnect_max = 30.0
heartbeat_interval = 20.0
last_ping = time.monotonic()
async with websockets.connect(
f"{WS_URL}?api_key={API_KEY}",
ping_interval=20,
ping_timeout=10,
close_timeout=5,
) as websocket:
await websocket.send(
json.dumps(
{
"cmd": "subscribe",
"channel": "depth",
"symbol": "AAPL.US",
}
)
)
while True:
try:
raw = await asyncio.wait_for(
websocket.recv(),
timeout=heartbeat_interval * 2,
)
message = parse_message(raw)
print(json.dumps(message))
retry = 0
except asyncio.TimeoutError:
# The connection is idle longer than expected. Check liveness.
await websocket.send(json.dumps({"cmd": "ping"}))
last_ping = time.monotonic()
except websockets.exceptions.ConnectionClosed:
delay = min(
reconnect_base * (2**retry),
reconnect_max,
)
jitter = random.uniform(0, delay * 0.1)
await asyncio.sleep(delay + jitter)
retry += 1
break
except ValueError as exc:
print(f"Discarded invalid message: {exc}")
continue
The code illustrates the required failure controls:
- The API key comes from an environment variable.
- The WebSocket authentication is passed through the
api_keyquery parameter, as required by the WebSocket authentication model. - The client sends a heartbeat message when the connection becomes idle.
- Reconnection uses exponential backoff.
- Random jitter prevents multiple clients from reconnecting simultaneously.
- Malformed messages are discarded without terminating the entire consumer.
- The client is ready to establish a new session after a closed connection.
For high-frequency production workloads, use a native asynchronous client or an event-driven architecture with explicit backpressure. A standard Python WebSocket loop can be appropriate for dashboards and research systems, but it should not be treated as a complete high-frequency execution platform.
4. REST and WebSocket Are Complements, Not Competitors
The strongest market-data design usually uses both interfaces.
A common pattern separates the system into three layers:
+---------------------------+
| Client and strategy |
| - state reconstruction |
| - signal evaluation |
| - risk controls |
+-------------+-------------+
|
+-------+--------+
| |
v v
+------------+ +----------------+
| REST API | | WebSocket feed |
| history | | live events |
| snapshots | | subscriptions |
+------------+ +----------------+
| |
v v
+-------------------------------+
| Market-data state manager |
| - initial snapshot |
| - event application |
| - gap detection |
| - reconciliation |
+-------------------------------+
Startup Sequence
A reliable client normally follows this sequence:
- Request a current snapshot through REST.
- Initialize the local state from the snapshot.
- Open the WebSocket connection.
- Subscribe to the relevant channel.
- Process incoming events.
- Detect gaps or sequence inconsistencies.
- Request a fresh snapshot through REST when recovery is necessary.
- Resume event processing after reconciliation.
This sequence prevents a race condition. Without the initial snapshot, live events may arrive before the client has enough state to interpret them.
Recovery Sequence
A reconnect does not mean “continue exactly where the old socket stopped.” A network failure may have hidden messages. The safer recovery path is:
WebSocket disconnect
|
v
Pause event application
|
v
Reconnect with backoff and jitter
|
v
Request a fresh REST snapshot
|
v
Replace or reconcile local state
|
v
Resume WebSocket processing
The exact reconciliation method depends on the channel’s protocol. Some feeds provide sequence numbers, timestamps, or snapshot identifiers. The application should record those fields when available.
The key principle is that REST provides the recovery anchor. WebSocket provides the low-latency event path.
5. Why the Interfaces Should Not Be Reversed
Why Not Use WebSocket for All Historical Retrieval?
It is technically possible to open a WebSocket and request a historical range, but doing so changes the operational model without necessarily improving the result.
The request still has a finite response. The client still needs to know when the transfer is complete. The server still needs to validate the same parameters. The advantages of REST remain:
- Natural request-response semantics
- Straightforward HTTP caching and observability
- Standard timeout and status-code behavior
- Easy pagination and retry handling
- Simple integration with batch and analytical workloads
- Clear separation between request and response
A WebSocket-based historical request adds persistent-connection management to a process that usually does not need it. It can be justified when the historical response is extremely large, streamed incrementally, or shared across many real-time consumers. That is an architectural decision, not a default.
Why Not Use REST for All Real-Time Delivery?
REST can support near-real-time behavior through frequent polling or long polling, but those approaches introduce a tradeoff between freshness, request volume, and implementation complexity.
Polling every 100 milliseconds may reduce observation latency while multiplying request volume by ten compared with one-second polling. The server also processes repeated requests that often return the same state. A long-polling request keeps a connection open until a change occurs or a timeout expires, but it still requires repeated HTTP negotiation and complicates horizontal scaling.
A WebSocket is a better fit when the server can push updates and the client needs to maintain a continuous state. It reduces repeated request overhead and gives the client a direct event path.
The important distinction is not that REST is “old” or WebSocket is “modern.” The distinction is semantic. REST represents a discrete query; WebSocket represents an ongoing conversation.
6. Interface Design Tradeoffs
| Design dimension | REST | WebSocket |
|---|---|---|
| Communication model | Request-response | Persistent, bidirectional |
| Natural data shape | Bounded range or snapshot | Unbounded sequence of events |
| Connection cost | Per request | Per session |
| Failure recovery | Retry the request | Reconnect and reconcile state |
| Backpressure | Naturally visible at request level | Requires explicit buffering or flow control |
| Observability | Standard HTTP logs and status codes | Requires connection and message metrics |
| Authentication | Request headers | Handshake and session authentication |
| Caching and pagination | Straightforward | Application-defined |
| Browser support | Universal | Widely supported, subject to environment |
| Best fit | History, snapshots, batch retrieval | Live subscriptions and event processing |
State and Consistency
REST responses are usually easier to reason about because each request returns a coherent snapshot. WebSocket messages arrive over time, and local state can temporarily reflect a partially applied sequence.
For example, if an order book update arrives while the client is reconnecting, applying it to stale state can produce a negative quantity or an invalid spread. The client should therefore track:
- Last applied message identifier
- Last applied event timestamp
- Snapshot timestamp
- Exchange or symbol session
- Whether the local state is synchronized
- Whether a reconciliation request is in progress
A state flag such as synchronized or stale is often more useful than a complex boolean combination scattered throughout the application.
Backpressure
REST clients naturally limit load by controlling how many requests they send. WebSocket clients need explicit control when processing is slower than incoming messages.
A robust pipeline may use:
- A bounded queue between the socket reader and the strategy
- Backpressure signaling when the queue reaches capacity
- A reconnect or snapshot strategy when updates cannot be applied safely
- Metrics for queue depth, dropped messages, and processing latency
- A rule that prevents the strategy from trading on a stale book
A large unbounded queue can hide a broken consumer while increasing memory usage and delaying the moment when the system recognizes that its state is no longer current.
Cost and Operational Complexity
WebSockets reduce repeated request overhead, but they introduce session-management costs. A production deployment must monitor:
- Active connections
- Authentication failures
- Reconnect frequency
- Heartbeat latency
- Message-processing delay
- Queue depth
- Snapshot and reconciliation frequency
- Stale-state incidents
REST is not automatically cheaper. Polling at high frequency can create more server work than a single maintained session. The right comparison is total system cost at the required freshness and reliability level.
7. Deployment Recommendations
The best interface depends on the workload, not merely the market category.
| User segment | Historical workload | Real-time workload | Recommended pattern |
|---|---|---|---|
| Individual researcher | REST kline and export endpoints | REST latest snapshot for infrequent checks | REST-first, polling only when needed |
| Individual quant developer | REST history for backtests | WebSocket depth or market feed for monitoring | REST bootstrap plus WebSocket |
| Institutional research team | REST for bulk historical retrieval and reconciliation | WebSocket for shared live state | Stateful service with explicit snapshots |
| Live execution team | REST for reference data and controlled recovery | WebSocket for low-latency updates | WebSocket-primary with REST recovery |
| Dashboard operator | REST for initial page load | WebSocket for live UI updates | REST snapshot followed by streaming |
| Batch or scheduled job | REST only | Not required | Rate-limited request scheduler |
Recommended Defaults
Start with REST when:
- The data has a known start and end time.
- The result can be stored and reused.
- The application processes data in batches.
- Recovery can occur by repeating a request.
- The consumer does not need continuous state.
Use WebSocket when:
- The consumer needs updates as events occur.
- Polling would create excessive traffic.
- The system maintains a live local state.
- The consumer can tolerate a reconnect and reconciliation procedure.
- The channel is supported for the selected market and symbol.
A hybrid system is usually the most maintainable option for a new market-data application.
8. Choosing the Right TickDB Endpoint
Endpoint selection should follow the state that the application needs.
| Application need | Endpoint or channel | Interface |
|---|---|---|
| Historical candles for a completed period | /v1/market/kline |
REST |
| Current real-time candle | /v1/market/kline/latest |
REST |
| Live market updates | WebSocket market stream | WebSocket |
| Order book depth, where supported | depth channel |
WebSocket |
| Symbol discovery and validation | Available-symbols endpoint | REST |
The historical kline endpoint supports more than 10 years of cleaned and aligned US equity OHLCV data, which makes it suitable for cross-cycle backtesting. This does not mean that every asset or endpoint provides full tick-level history. For example, the trades endpoint does not support US equities or A-shares. Hong Kong equity and crypto trade data are supported.
The depth channel also has market-specific boundaries:
| Market | Supported depth |
|---|---|
| US equities | L1 |
| Hong Kong equities | L1–L10 |
| Crypto | L1–L10 |
| Forex | Not supported |
| Precious metals | Not supported |
| Indices | Not supported |
A client should not infer channel support from the existence of a symbol. It should validate the symbol and the requested channel before deploying a live consumer.
9. A Production Checklist
Before shipping a combined REST and WebSocket client, verify the following controls.
REST client
- Load the API key from
TICKDB_API_KEY. - Send the key through the
X-API-Keyrequest header. - Apply a timeout to every HTTP request.
- Handle error codes
1001,1002,2002, and3001. - Read
Retry-Afterfor rate-limit responses. - Use the historical endpoint for completed periods.
- Use the latest endpoint for current snapshots.
- Avoid retrying non-idempotent operations without a safe policy.
WebSocket client
- Authenticate during the connection handshake.
- Send periodic heartbeat or ping messages.
- Reconnect with exponential backoff.
- Add random jitter to reconnect delays.
- Validate incoming message structure.
- Detect sequence gaps when identifiers are available.
- Refresh state through REST after an uncertain interruption.
- Bound the internal message queue.
- Monitor stale state and processing latency.
Application state
- Bootstrap from a REST snapshot.
- Track the latest applied event and snapshot.
- Expose a clear
staleorsynchronizedstate. - Block trading decisions on stale data where appropriate.
- Record the source and timestamp of every state transition.
10. The Key Takeaway
REST is the right tool when the application needs an answer. WebSocket is the right tool when the application needs a conversation.
Historical bars, completed periods, latest snapshots, and recovery queries benefit from REST’s bounded request-response model. Live prices, order book updates, and continuous events benefit from WebSocket’s persistent push channel. Using both creates a system that combines the reliability of snapshots with the responsiveness of streaming.
Start with REST for historical context and initial state. Add WebSocket when the strategy or dashboard requires continuous updates. Define the recovery path before the happy path, because a live feed that reconnects without reconciliation is not resilient—it is merely connected.
Next Steps
If you are an individual quant developer, sign up at tickdb.ai to obtain an API key, configure TICKDB_API_KEY, and start with a REST historical query before adding a live stream.
If you are building a production market-data service, combine REST snapshots with WebSocket subscriptions and instrument the snapshot, sequence-gap, reconnect, and stale-state paths.
If you need long historical OHLCV data for backtesting, review the available coverage and request options for individual, professional, or enterprise use.
If you use AI coding assistants, search for and install the tickdb-market-data SKILL in your tool’s marketplace.
This article does not constitute investment advice. Markets involve risk; past performance does not guarantee future results.