Skip to content

Docs/Realtime Option Trades API

API reference

Realtime Option Trades API

WebSocket API reference for OPRA-licensed U.S. equity option trades: filters, RAW vs AGGREGATED modes, fields, test mode, and connection limits.

Open productMarkdown
On this page

The Realtime Option Trades API provides streaming access to option trade data.

  • WebSocket URL: wss://ws.optiondata.io

Request

Parameters

NameExampleRequiredDescription
tokenYOUR_API_KEYRequiredGet your API key here: optiondata.io
aggregation_modeAGGREGATED or RAWOptional, default value is AGGREGATED if leave blankDetermine how OptionData groups trades. AGGREGATED combines prints executed simultaneously for the same option symbol. RAW emits one service record per received print without OptionData trade aggregation; it is not an OPRA-native audit feed or a completeness guarantee.
symbolsSPY,TSLA,SPX or AAPLOptional field, leave it blank means no filteringA list of symbols in upper case separated by comma. Using the * wildcard or don't provide it to subscribe to all symbols.
premium[100000,250000] or [100,null]Optional field, leave it blank means no filteringThe filter for minimum premium. The first value represents the minimum premium, while the second value indicates the maximum premium. If not provided, [0,null] will be the default value.
sentimentBULLISH,BEARISH,NEUTRAL or BULLISHOptional field, leave it blank means no filteringThe filter for sentiment includes potential values of BULLISH, BEARISH, and NEUTRAL. You can combine multiple selections by separating them with commas, such as NEUTRAL,BEARISH.
underlying_typeSTOCK,ETF,INDEX,ETN,REITOptional field, leave it blank means no filteringThe filter for underlying_type includes potential values of STOCK, ETF, INDEX, ETN, and REIT. You can combine multiple selections by separating them with commas, such as INDEX,STOCK.
delta[-0.9,0.98]Optional field, leave it blank means no filteringThe filter for delta has a default range of [-1, 1]. The first value represents the minimum delta, while the second value indicates the maximum delta. The range must remain within [-1, 1]
moneynessITM,OTM,ATMOptional field, leave it blank means no filteringThe filter for moneyness includes potential values of ITM, ATM, and OTM. You can combine multiple selections by separating them with commas, such as ITM,OTM.
expiry_days[15,20]Optional field, leave it blank means no filteringThe days remaining until the expiration date. The first number represents the minimum expiration days, with a default value of 0 if null is provided. The second number is the maximum expiration days; if null is provided, there is no limit.
is_recent_earning_onlytrueOptional field, leave it blank means no filteringOnly trades on stocks with upcoming earnings. AGGREGATED MODE only
put_callCALL or PUTOptional field, leave it blank means no filteringFilter to identify whether this option trade is PUT or CALL
sideASK,AASK,BID,BBID,MIDOptional field, leave it blank means no filteringPrice side of the trade. Values: AASK (above ask), ASK, MID, BID, BBID (below bid). Comma-separated for multiple.
strike[15.5,20]Optional field, leave it blank means no filteringIdentity the range of the Strike price of the option trade. The first number represents the minimum strike price, with a default value of 0 if null is provided. The second number is the maximum strike price. if null is provided, there is no limit at the maximium strike price. default value is [0,null] with minimum strike price is $0, and no limit on the maximum strike price.
oi[155,2000]Optional field, leave it blank means no filteringThe total number of this options contract that are still open. The first number represents the minimum OI, with a default value of 0 if null is provided. The second number is the maximum OI. if null is provided, there is no limit at the maximium OI. default value is [0,null] with minimum OI is 0, and no limit on the maximum OI.
iv[0.1,1.1]Optional field, leave it blank means no filteringImplied Volatility which reflects the market's expectations of the future volatility of the underlying asset's price. The first number represents the minimum IV, with a default value of 0 if null is provided. The second number is the maximum IV. if null is provided, there is no limit at the maximium IV. default value is [0,null] with minimum IV is 0, and no limit on the maximum IV.
option_activity_typeAUTOOptional field, leave it blank means no filteringOPRA trade condition code. Values: AUTO (AutoExecution), SLAN (SingleLegAuctionNonISO), MLET (MultiLegAutoEx), MLAT (MultiLegAuction), ISOI (IntermarketSweep), MESL (MultiLegAutoSingleLeg), TLET (StockOptionAutoEx), SLFT (SingleLegFloor), MLFT (MultiLegFloor), CBMO (MultiLegFloorPropProduct). AGGREGATED MODE only
trade_count[1,1] or [2,null]Optional field, leave it blank means no filteringNumber of trades involved in the sweep or trade. Use [1,1] to filter for block trades only, and [2,null] to filter for aggregated trades. In RAW mode, this is always 1.
is_opening_onlytrueOptional field, leave it blank means no filteringOnly opening trades (2 × size >= OI + daily volume). AGGREGATED MODE only
size[155,2000]Optional field, leave it blank means no filteringFilter for the total order size. The first number represents the minimum size, with a default value of 0 if null is provided. The second number is the maximum size if null is provided, there is no limit at the maximium size. default value is [0,null] with minimum size is 0, and no limit on the maximum size.
dex[100,500]Optional field, leave it blank means no filteringThe filter for Delta Exposure (DEX). The first value represents the minimum dex, while the second value indicates the maximum dex. If not provided, default is no filtering.
dei[0.1,0.5]Optional field, leave it blank means no filteringThe filter for Delta Impact (DEI). The first value represents the minimum dei, while the second value indicates the maximum dei. If not provided, default is no filtering.
test_modetrueOptionalEnable test snapshot mode: server sends pre-loaded sample data and then closes the connection. No authentication required. Use to validate filters and client parsing before going live.

Sample Code

TypeScript
import WebSocket from 'ws';

const url = "wss://ws.optiondata.io?symbols=AAPL,SPX&token=YOUR_API_KEY&premium=[300000,null]";

// Create a new WebSocket instance
const ws = new WebSocket(url);

// Event handler for connection open
ws.on('open', function open() {
  console.log('WebSocket connection opened');
});

// Event handler for incoming messages
ws.on('message', function incoming(data) {
  console.log('Received message:', data);
  // You can parse and handle the data here
  try {
    const receivedData = JSON.parse(data.toString());
    console.log('Parsed data:', receivedData);
    // Further processing of receivedData
  } catch (error) {
    console.error('Error parsing received data:', error);
  }
});

// Event handler for connection close
ws.on('close', function close() {
  console.log('WebSocket connection closed');
});

// Event handler for errors
ws.on('error', function error(err) {
  console.error('WebSocket error:', err);
});
Python
import asyncio
import websockets
import json
import datetime
from asyncio import Queue

# Set your WebSocket URL here
url = "wss://ws.optiondata.io?token=YOUR_API_KEY"

# If this runs in a managed notebook with an HTTP proxy, allow the WebSocket
# host through the proxy (for example: NO_PROXY=ws.optiondata.io,.optiondata.io).

# Create a global queue for demonstration
data_queue = Queue()

async def connect():
    """Connect to WebSocket and process messages."""
    while True:
        try:
            async with websockets.connect(url, ping_interval=10, ping_timeout=15) as websocket:
                print(f'[{datetime.datetime.utcnow()}]  WebSocket connection opened')

                try:
                    while True:
                        message = await websocket.recv()
                        print(f"[{datetime.datetime.utcnow()}] Message received: {message[:200]}{'...' if len(message) > 200 else ''}")
                        try:
                            received_data = json.loads(message)
                            option_symbol = received_data.get('option_symbol')

                            if received_data.get('daily_volume', 0) > 1000 and (
                                (received_data.get('price', 0) > 0.02 and option_symbol != "SPY")
                                or option_symbol == "SPY"
                            ):
                                if option_symbol:
                                    await data_queue.put(received_data)  # Push to queue
                                    print(f"[{datetime.datetime.utcnow()}] Queued data for option_symbol: {option_symbol}")

                        except json.JSONDecodeError as e:
                            print(f"[{datetime.datetime.utcnow()}]  Error parsing received data: {e}")

                except websockets.exceptions.ConnectionClosed as e:
                    print(f"[{datetime.datetime.utcnow()}] WebSocket connection closed: {e}, reconnecting immediately.")

        except websockets.exceptions.WebSocketException as e:
            print(f"[{datetime.datetime.utcnow()}] WebSocket error: {e}, reconnecting immediately.")
        except Exception as e:
            print(f"[{datetime.datetime.utcnow()}] Unexpected error: {e}, reconnecting immediately.")

        print(f"[{datetime.datetime.utcnow()}] Waiting 2 seconds before reconnecting...")
        await asyncio.sleep(2)  # Backoff before reconnecting to avoid hammering the server

if __name__ == "__main__":
    try:
        asyncio.run(connect())
    except KeyboardInterrupt:
        print("Shutting down...")
GO
package main

import (
	"fmt"
	"log"
	"os"
	"os/signal"

	"github.com/gorilla/websocket"
)

func main() {
	// Define the URL of the WebSocket server
	url := "wss://ws.optiondata.io?symbols=AAPL,SPX&token=YOUR_API_KEY&premium=[300000,null]"

	// Connect to the WebSocket server
	c, _, err := websocket.DefaultDialer.Dial(url, nil)
	if err != nil {
		log.Fatalf("Failed to connect to WebSocket server: %v", err)
	}
	defer c.Close()

	// Handle interrupt signals to terminate gracefully
	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt)

	fmt.Println("WebSocket connection opened")

	done := make(chan struct{})

	// Goroutine for reading messages
	go func() {
		defer close(done)
		for {
			_, message, err := c.ReadMessage()
			if err != nil {
				log.Println("Error reading message:", err)
				return
			}
			fmt.Printf("Received message: %s\n", message)
			// Ideally, you would parse and handle the JSON message here
		}
	}()

	// Main loop for handling program exit
	for {
		select {
		case <-done:
			return
		case <-interrupt:
			log.Println("Interrupt received, closing WebSocket connection...")
			// To cleanly close the connection, send a close message and wait for the server to close the connection
			err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
			if err != nil {
				log.Println("Error during closing WebSocket connection:", err)
				return
			}
			select {
			case <-done:
			}
			return
		}
	}
}

Response

Schema

NameTypeDescription
idstringunique id for this option trade record
symbolstringTicker Symbol (TSLA, MSFT, etc...)
datestringthe date the trade was executed, the date format is YYYY-MM-DD
timestringThe time the trade was created. Format is YYYY-MM-DD HH:MM:SS
put_callstringIndicates whether this trade is PUT or CALL
strikefloatStrike price of the option trade
expiration_datestringThe date on which the Option expires. The Option becomes invalid after this date and cannot be exercised ex: 2022-04-05
option_activity_typestringOPRA trade condition code (e.g., "AUTO", "SLAN"). AGGREGATED MODE only
underlying_typestringIndicates underlying is Common Stock, ETF, ETN, etc.
oiintegerOpen Interest. The total number of this options contract that are still open.
sizeintegerTotal order size (either of the 1 trade for the trade of SINGLE or REPEATED, or the sum of trade sizes for a AGGREGATED trade)
pricefloatLast price of a trade, or last price of last trade in a sweep.
underlying_pricefloatCurrent stock price of the underlying asset
bidfloatOption contract best bid
askfloatOption contract best ask
ivfloatImplied volatility (IV) is an estimate of the future volatility of the underlying stock based on options prices.
premiumfloatCost in dollar of the entire sweep or block option trade
sentimentstringBULLISH, BEARISH, or NEUTRAL The sentiment is estimated based on whether the trade was executed at the bid, ask, or spot price.
trade_countintegerNumber of trades involved in the sweep. In RAW mode, this is always 1.
expiry_daysintegerThe days left to expiration date.
sidestringPrice side: "AASK" (above ask), "ASK", "MID", "BID", "BBID" (below bid)
moneynessstring"ITM" (in the money), "ATM" (at the money), "OTM" (out of the money)
deltafloatDelta is a measure of the change in an option's price
gammafloatOption gamma
dexnumberDelta exposure -- equivalent shares the dealer must hedge
deinumberDelta exposure impact relative to daily stock volume
daily_volumeintegerDay volume for this option contract including this trade AGGREGATED MODE only
earning_datestringNext earning date for the underlying asset AGGREGATED MODE only
updated_timestampnumberUTC timestamp in milliseconds when this record was created
market_capnumberMarket capitalization in dollars (current share price × outstanding shares) AGGREGATED MODE only
option_symbolstringOption Contract Symbol, for example: TSLA240430P00508000, this refers to a put option contract with a strike price of $508 and an expiration date of April 30, 2024.

Message format

When the WebSocket connects, the server sends a single connection success message. After that, each trade record is sent as an individual JSON message: the message body is the trade object itself (no status or data wrapper). Parse each incoming message as JSON; if it has a status field, treat it as control (e.g. SUCCESS); otherwise treat it as a trade record.

Sample response

When WebSocket connected (one-time):

JSON
{
  "status": "SUCCESS",
  "msg": "Connection established with id: 159b4576-70ec-476f-8a1d-b061fdca0fb8",
  "data": {
    "connection_id": "1707123456789-a1b2c3d4e5f6"
  }
}

When streaming data is received (each message is a plain trade object):

JSON
{
  "id": "13k7t4e",
  "date": "2025-05-02",
  "time": "2025-05-02 09:53:51",
  "symbol": "TSLA",
  "put_call": "CALL",
  "strike": 280,
  "expiration_date": "2025-05-02",
  "size": 7,
  "price": 4.44,
  "premium": 3109,
  "bid": 4.38,
  "ask": 4.5,
  "underlying_price": 281.77,
  "side": "MID",
  "sentiment": "NEUTRAL",
  "moneyness": "ITM",
  "expiry_days": 0,
  "option_symbol": "TSLA250502C00280000",
  "trade_count": 2,
  "dex": 413,
  "dei": 0.00239,
  "iv": 0.588,
  "delta": 0.589,
  "gamma": 0.0449,
  "oi": 14410,
  "daily_volume": 8352,
  "underlying_type": "STOCK",
  "option_activity_type": "SLAN",
  "earning_date": "2025-07-21",
  "market_cap": 900485935542,
  "updated_timestamp": 1746194031262
}

Response Error

401 Unauthorized (missing or invalid token, before WebSocket upgrade)

JSON
{
  "status": "ERROR",
  "code": 401,
  "msg": "Unauthorized, please login to https://optiondata.io get a valid API subscription plan first"
}

500 Internal Error

JSON
{
  "status": "ERROR",
  "code": 500,
  "msg": "Internal Error"
}

Heartbeat

The server sends a ping message every 20 seconds to keep the connection alive:

JSON
{ "type": "ping", "ts": 1707123456789 }

Clients can optionally respond with a pong: { "type": "pong", "ts": 1707123456789 }. If the client sends {"type": "ping"}, the server responds with {"type": "pong", "ts": ...}.

Connection limits

Each API token is limited to 5 concurrent WebSocket connections. When the limit is reached, a new WebSocket/SSE handshake is rejected with HTTP 429 and a Retry-After header; existing connections remain open. Respect the retry delay and use exponential backoff rather than opening another connection immediately.

Test mode

Use test_mode=true to validate your integration without live market data. No token is required.

  • URL example: wss://ws.optiondata.io?test_mode=true&aggregation_mode=AGGREGATED&symbols=TSLA
  • After sending the connection success message, the server streams pre-loaded sample trade records (with your filters applied), then closes the connection with code 1000 and reason "test_snapshot_complete".
  • No heartbeat pings are sent in test mode. Connection limits are not enforced.

FAQ

What is an Aggregation Mode?

A: Real-time Option Trades in AGGREGATED mode consolidates trades with the same option symbol executed simultaneously (at the second level) into a single aggregate trade. This approach simplifies the identification of large trades that have been divided into multiple smaller transactions. If you want print-level service records without OptionData trade aggregation, use RAW mode. RAW is not an OPRA-native audit feed or a completeness guarantee. The major differences between the two modes are:

  • The trade_count for option trades in RAW mode is always 1. In AGGREGATED mode, the trade_count can be either 1 or greater than 1. If it is greater than 1, this indicates that the option trade record aggregates multiple original option trades with the same option symbol that was executed simultaneously.
  • The average daily volume for Real-time Option Trades in AGGREGATED mode is 4 million rows of records, while the average daily volume for RAW is around 7 million rows of records.

How to choose between AGGREGATED mode and RAW mode?

A: Institutional players often divide large trades into smaller ones and execute them simultaneously. They do this either to hide their intentions or due to a lack of liquidity or market depth at the moment of execution. This is the background for why we introduced AGGREGATED mode, for the case that you need to identify significant block trades in real-time, you can select AGGREGATED mode, allowing our aggregation services to combine these trades. If you prefer print-level service records without OptionData trade aggregation, choose RAW mode instead. By default, the API uses AGGREGATED mode unless you specify a different value for the aggregation_mode field. Please note that this is an assumption that trades executed simultaneously are sent by the same trader/institution, otherwise, you can choose RAW mode instead.

Which OPRA audit fields does RAW mode expose?

A: RAW means one service record per received print (trade_count = 1), without OptionData trade aggregation; it is not an OPRA-native audit feed. The portal payload does not expose the original OPRA trade condition, exchange, correction/cancellation status, or source sequence/message id. It exposes only the documented service fields, including a service-generated id and millisecond timestamps.

Does RAW mode guarantee every trade and a complete, lossless session?

A: No premium or size filter is applied when those parameters are omitted, and RAW records are not aggregated. However, OptionData does not warrant that every source print is delivered or that a session is complete, uninterrupted, or lossless. The stream has no source sequence field with which a client can independently certify completeness.

What connection, backlog, drop, and recovery telemetry is exposed?

A: Clients receive a connection-success control message, individual trade messages, heartbeat pings about every 20 seconds, transport close/errors, and HTTP 429 with Retry-After when the five-connection cap is exceeded. The API does not expose customer-facing backlog depth, drop counts, recovery counts, gap counters, or a complete-session marker. Track receive counts, time gaps, disconnects, and reconnects in your client.

Can I retain raw WebSocket frames or derived files after access ends?

A: Under the current Terms, when access or the subscription terminates you must stop using the Service and delete copies of raw Data. Derived files are permitted only when they cannot be reverse engineered to recreate the original Data or a reasonable substitute. Self-serve Pro is for individual, non-professional use; evaluation or use for a company, business, or professional purpose requires a separate commercial license from support@optiondata.io.

Can you give me some examples?

A: Let's say there are two records in real-time option trades with RAW mode like below:

Code
// RAW MODE
date        time      symbol  strike  put_call expiration_date premium size trade_count
2025-02-01  09:32:02  TSLA    330     CALL     2025-02-15       1000    2    1
2025-02-01  09:32:02  TSLA    330     CALL     2025-02-15       2000    3    1

These two trades will be aggregated into one trade since they are executed at the same time with the same option symbol.

Code
// AGGREGATED MODE
date        time      symbol  strike  put_call expiration_date premium size trade_count
2025-02-01  09:32:02  TSLA    330     CALL     2025-02-15       3000    5    2

For the example above, the size of trade in the AGGREGATED mode is equal to the sum of the size of the trades in the RAW mode(5 = 3 + 2), the trade size of trade in the AGGREGATED mode is equal to the sum of the trade_size in the RAW mode. For other data fields in AGGREGATED mode (eg: delta, iv, gamma, theta) is the average value of the related data field in the RAW mode.

Is there a delay in the data?

A: Realtime WebSocket trades are live during market hours. We typically process around 20,000 records per minute; the open may lag under surge. Historical SQL (separate product) enforces a minimum 15-minute delay from each trade’s execution time.

Market open delay chart

What is the typical daily volume?

A: On the order of 10M+ prints/day. AGGREGATED mode averages about 4M records/day; RAW mode about 7M records/day.

What are connection and rate limits?

A: Each API token allows up to 5 concurrent WebSocket connections. A sixth handshake receives HTTP 429 with Retry-After; existing connections are not evicted. Heartbeats run about every 20 seconds. HTTP APIs (Historical SQL, Option Chain, Market Structure) default to about 60 requests per 60 seconds per key (HTTP 429 + Retry-After).

How to get started? Free trial length / extension?

A: Register at https://www.optiondata.io, complete qualification with an invitation code, then explicitly activate the 14-day no-card Pro trial when you are ready. Copy your API key and connect to wss://ws.optiondata.io. If you do not add a payment method, you are not charged and access does not continue as paid when the trial ends. Eligible trial users can receive 50% off the first year, with next steps shown inside the authenticated portal. Trials are not auto-extended.

What happens when the market closes?

A: During market closures, your authenticated live WebSocket connection stays open. Data streaming stops, but heartbeat pings continue every 20 seconds. When the feed resumes for the next applicable trading session, data resumes on the same connection; no reconnection is needed. If you connect outside market hours, authentication succeeds normally and you receive the connection success message, but no trade data is sent until the market opens. The portal offers separate Sample and Live modes. Sample mode returns a finite synthetic snapshot and closes. Live mode does not switch to sample based on the browser clock, weekends, or holidays. An open connection with no matching trades is a waiting state. For historical trade research, use the Historical SQL API.

What does the Pro plan include? Refunds?

A: One Pro subscription covers Realtime WebSocket, Historical SQL (trial SQL capped at 10 rows), Option Chain REST, and Market Structure API under one key. Eligible trial users can receive 50% off the first year, with next steps shown inside the authenticated portal. 30-day money-back guarantee; cancel anytime. Refunds: email support@optiondata.io from your login address within 30 days of purchase.

Ask ChatGPT or Claude Code

Copy this prompt, paste it into ChatGPT, Claude, Claude Code, Cursor, or Codex, then add your question. It tells the model to read our public docs first — no API key needed for that step.

You are helping me use OptionData (https://www.optiondata.io/), an OPRA-licensed U.S. equity options data API.

Before answering, fetch these public files (no login required) and treat them as the source of truth:
- https://www.optiondata.io/llms.txt — short product map (same content as https://www.optiondata.io/llm.txt)
- https://www.optiondata.io/llms-full.txt — full API reference
- https://www.optiondata.io/openapi.json — HTTP OpenAPI

Do not invent endpoints, fields, tables, or limits. Prefer `Authorization: Bearer apikey_…` for HTTP APIs. Realtime uses `wss://ws.optiondata.io` with a `token` query parameter.

Products:
- Realtime trades WebSocket: wss://ws.optiondata.io
- Historical SQL: POST https://www.optiondata.io/api/historical/sql
- Option chain: POST https://www.optiondata.io/api/option-chain
- Market structure: GET https://www.optiondata.io/api/v1/market-structure/{symbol}

I am asking about: Realtime Option Trades API
- Markdown: https://www.optiondata.io/md/realtime-option-trades-api/
- HTML docs: https://www.optiondata.io/docs/realtime-option-trades-api/

My question:

Search OptionData documentation

Search guides, API fields, examples, and troubleshooting steps.