WebSocket Streaming
The server bridges streaming onto a local WebSocket at ws://127.0.0.1:25520/v1/events. Connect, send one JSON envelope per command, and receive one JSON message per event.
Subscribe envelope
{
"msg_type": "STREAM",
"sec_type": "STOCK",
"req_type": "QUOTE",
"add": true,
"id": 1,
"contract": {"symbol": "AAPL"}
}| Field | Values |
|---|---|
sec_type | STOCK, OPTION, INDEX |
req_type | QUOTE, TRADE, OHLC, OPEN_INTEREST, FULL_TRADES, FULL_OPEN_INTEREST |
add | true subscribes, false unsubscribes |
id | Your request id; echoed in the acknowledgement |
contract | Omit for FULL_* streams |
Option contracts carry the four-tuple. By default the strike is the terminal's 1/10-cent integer (a JSON integer, e.g. 570000 for a $570 strike); pass the server's --strike-format dollars flag to take a dollar value (570) instead:
{"symbol": "SPY", "expiration": 20250321, "strike": 570000, "right": "C"}{"msg_type": "STOP", "id": 2} removes every active stream at once. Each command is acknowledged with a stream-request verification value in the response field:
{ "header": { "type": "REQ_RESPONSE", "response": "SUBSCRIBED", "req_id": 1 } }SUBSCRIBED confirms the request was accepted; it acknowledges subscribe (add: true), unsubscribe (add: false), and STOP, since there is no removal-specific value. Rejected commands answer with "response": "ERROR" and an error field naming the cause — a bad envelope, an offending field, or a subscribe sent before streaming has started (which installs nothing, so it is never acknowledged as a success). The values MAX_STREAMS_REACHED and INVALID_PERMS are also part of the verification vocabulary.
Event messages
Events arrive as JSON with a header.type of QUOTE, TRADE, or OHLC, plus a STATUS heartbeat every second. OHLC bars arrive from upstream automatically — one per traded contract, ahead of that contract's trade; they are not derived from your subscriptions and are not subscribed to separately. An OPEN_INTEREST subscription is accepted, but open interest has no WebSocket frame: the data is delivered through the native SDK callbacks and the REST surface, not over this endpoint.
Try it
websocat ws://127.0.0.1:25520/v1/events
{"msg_type": "STREAM", "sec_type": "OPTION", "req_type": "TRADE", "id": 1, "add": true, "contract": {"symbol": "SPY", "expiration": 20250321, "strike": 570000, "right": "C"}}Limits
- One client at a time. A second connection takes over the stream; the first receives a Close frame (code 1000, reason
replaced by a new client connection). Run one server instance per consumer for multi-client setups. - Text frames are capped at 4 KiB — far above any legitimate envelope.
- Each connection has a per-client send buffer (default 4096 events). On a high-rate stream a slow consumer that fills the buffer starts dropping events; raise the buffer with the
THETADATADX_WS_CLIENT_CAPACITYenvironment variable (trades memory for headroom). See Environment variables. - Programmatic consumers should prefer the SDK streaming surface, which adds typed events, automatic reconnect, and drop monitoring.