API guide
WebSocket vs REST API: which to use for real-time data
REST asks, a WebSocket listens. For market data the choice is mostly arithmetic: count the requests polling would cost you, and the seconds of staleness it would add.
On this page
Key takeaways
- REST is request and response: the client asks every time. A WebSocket is one long-lived connection on which the server pushes updates as they happen.
- Polling one symbol every 5 seconds costs 720 requests an hour and shows a price that trails the latest by 2.5 seconds on average.
- Use REST for history, snapshots and occasional lookups; use a WebSocket for live prices on more than a handful of symbols.
- Production market data apps use both: REST to load history and repair gaps after a reconnect, a WebSocket for the live edge.
The WebSocket vs REST API question comes down to who speaks first. With REST, your client sends an HTTP request and receives one response, every time it wants data. With a WebSocket, the client opens one long-lived connection and the server pushes each update as soon as it has one. Use REST for history, snapshots and occasional lookups; use a WebSocket for data that changes continuously, such as live prices.
For market data the decision is mostly arithmetic, so this guide does the arithmetic: requests per hour, seconds of staleness, bytes on the wire. The connection details of our own stream are in the stock WebSocket API guide, and the wider picture of endpoints, symbols and units is in market data API explained.
What is a WebSocket?
A WebSocket is a persistent, two-way connection between a client and a server, defined in RFC 6455. It starts life as an ordinary HTTP request carrying an Upgrade: websocket header. If the server agrees, it answers 101 Switching Protocols, and from then on both sides exchange frames over the same connection, in either direction, whenever they like. There is no request per message, and each frame adds only 2 to 14 bytes of header.
That is the whole difference in WebSocket vs HTTP: plain HTTP is a conversation of questions and answers, a WebSocket is an open line. This is what an open line looks like on the TickerLayer stream:
- GET /?apiKey=… with Upgrade: websocketYour app to TickerLayer stream
- 101 Switching ProtocolsTickerLayer stream to Your app
- {"type":"system","event":"ready"}TickerLayer stream to Your app
- subscribe forex.quotes EURUSDYour app to TickerLayer stream
- subscribedTickerLayer stream to Your app
- quote EURUSDpushed as the price changesTickerLayer stream to Your app
- quote EURUSDTickerLayer stream to Your app
- pingTickerLayer stream to Your app
- pongsent by the client libraryYour app to TickerLayer stream
Every modern browser ships the WebSocket API, and every mainstream server language has a client. The URL scheme is ws://, or wss:// over TLS, which is the only one to use for anything that carries a key. From the server's side the trade-off flips: a REST server forgets you between requests, while a WebSocket server holds state for every open connection and fans each update out to every subscriber. That is why streaming plans are capped by connections and symbols rather than by requests.
WebSocket vs REST at a glance
| Feature | REST | WebSocket |
|---|---|---|
| Who starts | The client, every time | The client once, then the server pushes |
| Connection | Short requests, reusable with keep-alive | One long-lived connection |
| Freshness | As old as your polling interval | Updates arrive as they happen |
| What you pay in | Requests per second and per month | Connections and distinct symbols |
| Overhead per update | A full HTTP request and response | A 2 to 14 byte frame header |
| Sees trades between polls | ||
| HTTP caching and CDNs | ||
| Runs from cron or serverless | ||
| Client complexity | Low: retry on error | Higher: reconnect, heartbeat, resubscribe |
| Best for | History, snapshots, lookups, page loads | Live prices, trade tapes, alerts, dashboards |
The polling tax: requests and staleness
Polling is REST in a loop: ask, sleep, ask again. It works, and it has two costs that grow together. Every poll is a request against your rate limit and monthly quota, and between polls the price on screen gets older while the market keeps moving.
requests per hour = symbols × 3,600 ÷ intervalaverage staleness ≈ interval ÷ 2 + round tripworst staleness ≈ interval + round trip
- interval
- Seconds between two polls of the same symbol.
- round trip
- Network and server time for one request.
- staleness
- How far the value you show trails the latest value the API holds.
| Polling interval | Requests per hour | Requests per month, around the clock | Average added staleness |
|---|---|---|---|
| 1 s | 3,600 | 2,592,000 | 0.5 s |
| 5 s | 720 | 518,400 | 2.5 s |
| 15 s | 240 | 172,800 | 7.5 s |
| 60 s | 60 | 43,200 | 30 s |
| WebSocket | 1 subscribe message | 0 REST requests | None from polling |
How old is the price on screen?
- Polling every 5 s
- Streaming
seconds behind the latest price
Run your own numbers. The calculator turns symbols, a polling interval and hours per day into requests per day and per month:
Then hold the result against real quotas. The TickerLayer free tier includes 3,000 REST requests a month: about 100 a day, or one symbol checked every 15 minutes around the clock. Individual plans include 250,000 REST calls a month and Business plans 25 million. Ten US stocks polled through the regular session land in very different places depending on the interval:
Requests per month to poll 10 US stocks through the regular session
Bytes tell the same story. In one measurement on 28 September 2026, polling GET /forex/quote/EURUSD sent a request of about 160 bytes and received 103 bytes of JSON behind roughly 850 bytes of response headers: about 1.1 KB per update. The same quote as a stream frame is 160 bytes of JSON plus a 4-byte frame header. Header sizes vary by client and route, but the ratio holds: when data changes often, the envelope costs more than the letter.
Do keep-alive and HTTP/2 close the gap?
Partly. Keep-alive reuses one connection across polls, so you skip a TLS handshake per request, and HTTP/2 compresses repeated headers, which shrinks that 850-byte envelope a great deal. Neither touches the two costs that matter most. Each poll is still a request against your limits, and the price still waits up to a full interval before you see it. Long polling narrows the staleness but holds a request open per client while it waits, which is why it survives mostly as a fallback for clients that cannot open a WebSocket.
Polling fast also brings you closer to the per-second limit, and a 429 in a tight loop turns into a retry storm unless the client backs off properly. The fixes are in 429 Too Many Requests.
When REST is the right answer
Polling gets its bad name from live dashboards, but most market data calls are not live. REST is the better tool when:
- You need history. Bars come in pages of up to 5,000 from
GET /{asset}/agg/{symbol}/{multiplier}/{timespan}/{from}/{to}; no stream replays last year. - You need one value now. A page load, a report, a price check before a form: one snapshot beats opening a socket.
- The job runs on a schedule. Cron jobs, serverless functions and spreadsheets cannot hold a connection open between runs.
- The value changes slowly. Market status, symbol lists, daily bars and bond yields, which are daily observations, rarely justify a stream.
- You want HTTP tooling. Caches, retries, proxies,
curland every monitoring tool speak REST.
When a WebSocket earns its complexity
A stream wins when data changes faster than you could reasonably poll, or when you care about what happened between two polls:
- Many live symbols. One connection carries all of them, and the cost does not grow with every extra poll.
- Trades, not just prices. Polling the last trade shows only the latest one; trades between polls vanish. A trade channel pushes trades as they print.
- Alerts and triggers. A threshold crossed for two seconds between two polls is a threshold your alert never saw.
- Events without a schedule. US trading halts on
stocks.statusarrive when they happen; there is no sensible interval to poll for them.
The price is client complexity. A socket drops, and your code has to notice, reconnect with backoff, resubscribe and backfill any bars it missed over REST. It also has to keep up: a handler that does heavy work inline falls behind a busy stream, so hand slow work to a queue. The WebSocket reconnect guide covers heartbeats, close codes and backoff, and the Python WebSocket client tutorial builds a complete client.
The hybrid pattern most production apps use
- Load historyREST bars
- Open the streamwait for ready
- Subscribesnapshot fills the screen
- Live framesupdate in place
- On a dropreconnect, resubscribe, backfill over REST
Here are both approaches side by side for one currency pair. The polling version is shorter. The streaming version sends one subscribe message and then only listens; note that the stream sends forex prices as strings, so the code converts them.
import os
import time
import requests
session = requests.Session()
session.headers["x-api-key"] = os.environ["TICKERLAYER_API_KEY"]
while True: # one symbol every 5 s = 720 requests an hour
resp = session.get("https://api.tickerlayer.com/forex/quote/EURUSD", timeout=10)
resp.raise_for_status()
q = resp.json()
print("poll", q["bid"], q["ask"], q["timestamp"])
time.sleep(5)import asyncio
import json
import os
from urllib.parse import quote
import websockets # pip install websockets
URL = "wss://stream.tickerlayer.com/?apiKey=" + quote(os.environ["TICKERLAYER_API_KEY"])
SUBSCRIBE = {"action": "subscribe", "channels": ["forex.quotes"], "symbols": ["EURUSD"]}
async def main():
async with websockets.connect(URL, compression=None) as ws:
ready = json.loads(await ws.recv())
if ready.get("event") != "ready":
raise RuntimeError(f"expected ready, got {ready}")
await ws.send(json.dumps(SUBSCRIBE)) # one message, then the server pushes
async for raw in ws:
msg = json.loads(raw)
if msg.get("type") == "quote":
print("push", float(msg["bid"]), float(msg["ask"]), msg["ts"])
else:
print(msg)
asyncio.run(main())$ python poll.py
poll 1.137281 1.137291 1790593057001
poll 1.137252 1.137293 1790593063037
$ python stream.py
{'type': 'system', 'event': 'subscribed', 'channels': ['forex.quotes'], 'symbols': ['EURUSD']}
push 1.137258 1.137268 1790593065001
...Server-sent events, long polling and other options
WebSocket and REST polling are not the only ways to get updates from a server. Two more come up in every design review, and the server-sent events vs WebSocket comparison is the one worth knowing:
| REST polling | Long polling | Server-sent events | WebSocket | |
|---|---|---|---|---|
| Direction | Client asks | Client asks, server holds the answer | Server to client only | Both ways |
| Transport | HTTP | HTTP | HTTP, text/event-stream | Upgraded connection |
| Binary data | Yes | Yes | No, UTF-8 text only | Yes |
| Browser reconnect | Not needed | Manual | Built in (EventSource) | Manual |
| Good for | Occasional reads | Rare events, older clients | One-way feeds to browsers | High-rate, two-way or many-symbol streams |
TickerLayer serves REST and a raw WebSocket stream: standard WebSocket frames carrying JSON, not a library-specific protocol, so any client that speaks RFC 6455 works, from a browser to a Python script.
Choosing: a decision table
| Your situation | Use | Why |
|---|---|---|
| A backtest on two years of daily bars | REST | History only exists as request and response |
| A price on a product page | REST snapshot | One value per page view, and cacheable |
| A nightly report or spreadsheet refresh | REST | Runs on a schedule, with no connection to hold |
| A live watchlist of 30 symbols | WebSocket | One connection instead of thousands of requests an hour |
| Price alerts on thresholds | WebSocket | Sees moves that happen between polls |
| Trading halt notifications | WebSocket (stocks.status) | Events arrive when they happen |
| A live chart with history | Both | REST loads the bars; the stream moves the last candle |
Plans shape the choice too. The TickerLayer free tier is REST; WebSocket streaming comes with paid plans, and free accounts can request a WebSocket trial from the dashboard. An Individual plan allows one connection and 10 streamed symbols, and Business allows 10 connections with unlimited symbols. The current allowances are in the limits reference, and the WebSocket docs list every channel and frame.
Questions
Is WebSocket faster than REST?
For continuous updates, yes: the server pushes each change as it happens instead of waiting for your next poll, and each frame carries a few bytes of header instead of a full HTTP exchange. For a single lookup the difference is negligible, and REST is simpler.
Can I use a WebSocket with a REST API?
Yes, and most market data apps do. Use REST for history, snapshots and backfill after a reconnect, and a WebSocket for the live updates in between.
What is the difference between WebSocket and HTTP?
HTTP is request and response: one answer per question. A WebSocket starts as an HTTP request, upgrades with status 101, and then keeps a two-way connection open for as many messages as either side sends.
When should I not use a WebSocket?
When you need data only occasionally, run on a schedule or in serverless functions, or depend on HTTP caching. Holding a connection open costs reconnect logic that only pays off for frequent updates.
Server-sent events vs WebSocket: which should I use?
Server-sent events are simpler for one-way text updates to a browser and reconnect on their own. WebSockets are two-way and carry binary, which suits subscribe and unsubscribe traffic on market data streams.