Streaming
tastytrade exposes two independent WebSocket streamers. They serve different purposes, use different tokens, and connect to different hosts:
- The DXLink market-data streamer pushes live quotes, trades, Greeks, and candles from tastytrade's quote provider (dxFeed).
- The account streamer pushes one-directional notifications about your account state (orders, balances, positions) plus non-account data: public watchlists and user-level quote alerts.
Both let you subscribe to real-time updates instead of polling REST endpoints. Streaming is for staying in sync; the REST API in /reference remains the source of truth and the only way to take action (place orders, etc.).
DXLink market-data streamer
1. Get an API quote token
Request a token with GET /api-quote-tokens. The token is tied to the customer identified by your OAuth access token, and the response includes the dxlink-url to connect to:
{
"data": {
"token": "<redacted>",
"dxlink-url": "wss://tasty-openapi-ws.dxfeed.com/realtime",
"level": "api"
},
"context": "/api-quote-tokens"
}
API quote tokens expire after 24 hours — refresh before they lapse. You must be a fully onboarded tastytrade customer; a username/password-only registration is rejected with quote_streamer.customer_not_found_error.
2. Connect and exchange messages
Open a WebSocket to the returned dxlink-url. Message order matters; the high-level handshake is:
SETUP— initiate the connection (negotiate version and keepalive timeout).AUTH— after the server sendsAUTH_STATEwithstate: UNAUTHORIZED, authorize with your API quote token.CHANNEL_REQUEST— open a channel (a virtual sub-connection; use separate channels to organize subscriptions, e.g. equities vs. futures).FEED_SETUP— configure which event fields to receive on the channel.FEED_SUBSCRIPTION— subscribe to (orremove) events for one or more symbols.KEEPALIVE— send periodically to hold the connection open.
A SETUP plus AUTH exchange looks like this:
{ "type": "SETUP", "channel": 0, "version": "0.1-DXF-JS/0.3.0", "keepaliveTimeout": 60, "acceptKeepaliveTimeout": 60 }
{ "type": "AUTH", "channel": 0, "token": "<redacted>" }
A channel configured for COMPACT data (requested via FEED_SETUP's acceptDataFormat) delivers events as positional arrays rather than verbose objects:
{
"type": "FEED_SUBSCRIPTION",
"channel": 3,
"reset": true,
"add": [
{ "type": "Quote", "symbol": "SPY" },
{ "type": "Trade", "symbol": "SPY" }
]
}
{ "type": "FEED_DATA", "channel": 3, "data": ["Trade", ["Trade", "SPY", 559.36, 13743299, 100.0]] }
Subscriptions are capped, and the caps shape how you should batch them: 5
concurrent sessions, 25,000 subscriptions per session, 100 each for Candle,
Order and Series, and 10,000 subscription changes per minute. Full detail in
Streaming rate limits.
Common event types include Quote, Trade, Greeks, Summary, Profile, and Candle. Candle events return historical aggregates (open/high/low/close) — you supply a period and type in the symbol (for example AAPL{=5m}) plus a fromTime Unix-epoch timestamp.
Keepalive and quiet feeds
If DXLink does not receive a keepalive within its 60-second timeout, it closes the connection. Sending KEEPALIVE every ~30 seconds keeps it open indefinitely:
{ "type": "KEEPALIVE", "channel": 0 }
DXLink only publishes events as they occur. A connected, heartbeating socket that receives nothing usually means the symbol simply has no trading activity — that is normal, not a bug.
Symbology
You must subscribe using DXLink-formatted symbols. tastytrade provides these on instrument responses in the streamer-symbol field — for example a futures contract /6AM3 exposes streamer-symbol: "/6AM23:XCME". The same field is available across the instruments endpoints (GET /instruments/equities/{symbol}, GET /instruments/futures, GET /instruments/cryptocurrencies, GET /option-chains/{symbol}, GET /futures-option-chains/{symbol}). Prefer it over hand-building symbols. For more on tastytrade symbology (/ futures, ./ future options, OCC options, crypto pairs), see /reference.
Account streamer
The account streamer is a one-directional WebSocket that publishes notifications when your account data changes. Instead of re-fetching an order to learn it went from Routed to Filled, you receive a push message as the status changes.
Hosts
| Environment | WebSocket host |
|---|---|
| Sandbox | wss://streamer.cert.tastyworks.com |
| Production | wss://streamer.tastyworks.com |
These pair with the REST hosts https://api.cert.tastyworks.com (sandbox) and https://api.tastyworks.com (production).
Authentication
Every message includes an auth-token — your tastytrade OAuth access token, the same value you send in the Authorization header for REST calls. Access tokens last 15 minutes (mint a fresh one with POST /oauth/token — see Get started for the auth flow), so refresh and re-authenticate over the long-lived socket as needed.
Connect, then heartbeat
Perform these steps in order — if you heartbeat before connecting you may get a not implemented error:
- Open the WebSocket connection.
- Send a
connectmessage to subscribe to account updates. - Send
heartbeatmessages on a 2s–1m interval.
{
"action": "connect",
"value": ["5WT00000", "5WT00001"],
"auth-token": "<access token>",
"request-id": 2
}
{ "action": "heartbeat", "auth-token": "<access token>", "request-id": 1 }
The optional request-id is echoed back in the server's response so you can correlate messages. Other actions include public-watchlists-subscribe and quote-alerts-subscribe (note: quote alerts are scoped to the user, not an account).
Receiving notifications
Notifications use the same JSON object representations as the REST API and always contain the full object — never a partial or differential update. Each message carries a type and a data payload:
{
"type": "Order",
"data": {
"id": 1,
"account-number": "5WT00000",
"status": "Live",
"underlying-symbol": "AAPL",
"ext-client-order-id": "67890"
},
"timestamp": 1688595114405
}
Note the dasherized JSON keys (account-number, ext-client-order-id), consistent with the rest of the API.
Streaming and orders
The account streamer reports order state — it does not place orders. Submit orders over REST, dry-run first (validate against POST /accounts/{account_number}/orders/dry-run), then send the live order with your own unique external-identifier so you can recognize an order you already submitted — the server does not deduplicate retries, and there is no idempotency-key header. On an uncertain outcome (timeout, 5xx), check GET /accounts/{account_number}/orders (or /orders/live) for that identifier before resubmitting. See Idempotency & retries. You then watch the streamer for Order notifications as the status advances.
Operational notes
- Always send the
User-Agent: product/versionheader on the REST calls that mint your tokens. - Reconnect with backoff on drops; respect the 60-second DXLink keepalive timeout and the account-streamer heartbeat window. See Rate limits & backoff.
- Token errors (
401/403) and other HTTP error codes (400/404/422/429/5xx) on the token endpoints are documented in /reference/errors. - For an end-to-end client that wraps both streamers, see MCP Server.
Machine-readable spec
Both streamers are described in an AsyncAPI 2.6 document — download it as JSON or YAML.