Quant Data has a historical options data API. It returns past options trades as JSON over REST: one row per exchange print, or one row per sweep, block or split, each with its contract, time, price, size, premium, exchange, trade side and Greeks. History goes back up to 6 years, depending on the dataset.
Quant Data filters on the server by what the trade was, across 50+ fields: trade side, sweep, block or split, flags, premium, size, moneyness and Greeks. A script, or an AI assistant connected to the hosted MCP server, sends a past date and a rule, gets back the rows that match, and can narrow the rule and backtest further.
One key at $149.99 a month or $1,499.99 a year covers every endpoint, at 240 requests per 60 seconds with no monthly quota.
Checked on November 12, 2026. Every request on this page was sent that day, between 2:29 PM and 3:30 PM ET.
At a glance
Quant Data API | |
What it returns | Past options trades as JSON, for one session or a date range per request |
Granularity | Trade by trade: one row per print on one exchange (unconsolidated flow), or one row per sweep, block or split (consolidated flow) |
One record contains | Contract, trade time, price, size, premium, exchange, trade type, trade side, bid and ask, stock price, open interest, Greeks and flags |
History | Up to 6 years, depending on the dataset, when we tested on November 12, 2026 |
Filters | 56 fields on consolidated flow and 55 on unconsolidated flow, with AND/OR logic |
Delivery | REST (POST with a JSON body) and a hosted MCP server with 31 tools |
Price | $149.99 a month or $1,499.99 a year for every endpoint |
Rate limit | 240 requests per 60 seconds, plus a burst cap of 20 per second, with no monthly quota |
Coverage | 6,000+ tickers, index options such as SPX included; exchange-licensed data |
Key takeaways
Filtered before it is sent. The Quant Data API counted 1,376,613 SPY options prints for March 12, 2025. Three filter steps cut that to 48 rows on one page.
A year in one request. One Quant Data API request for all of 2025 returned the totals for SPY sweeps of $1,000,000 or more: 8,022 sweeps and $18,980,716,990 in premium.
Why Quant Data. Quant Data serves exchange-licensed data for 6,000+ tickers, over REST and a hosted MCP server for AI assistants, with no monthly quota.
Each filter runs on the server, so the 1,376,565 prints you didn’t ask for never leave it. Data: Quant Data API, session March 12, 2025, requested November 12, 2026.
Is there a historical options data API?
Yes. Quant Data has a historical options data API with two endpoints. Unconsolidated flow is the historical options trades API, one row per exchange print; consolidated flow has one row per sweep, block or split.
Endpoint | One row is | Rows per page |
Unconsolidated flow: POST /v1/options/tool/order-flow/unconsolidated | One print on one exchange, with its OSI contract symbol | Up to 1,000 |
Consolidated flow: POST /v1/options/tool/order-flow/consolidated | One sweep, block or split | Up to 100 |
Both are a POST to https://api.quantdata.us with a JSON body and your API key in an Authorization: Bearer header. For how the two kinds of flow differ, see Options Flow API: Consolidated and Unconsolidated Flow Data.
The examples here use SPY. Index options such as SPX are covered too: SPX requests for March 12, 2025 returned rows on both endpoints. Open interest, exposure (GEX), option prices and the other datasets further down have history too.
Before you send a request. Get a key (steps in the Quant Data API overview) and sign the market-data subscriber agreement; until you do, options requests return HTTP 403 with the signing link in the response. Then set the key as QD_API_KEY: export QD_API_KEY="<your key>" on macOS or Linux, $env:QD_API_KEY = "<your key>" in Windows PowerShell.
What does one historical options trade record contain?
One Quant Data historical options trade record carries the contract, the trade (time, price, size, premium, exchange), the trade side, the bid and ask, the Greeks and a set of flags. This is one real consolidated flow row, a SPY trade from March 12, 2025 (requested November 12, 2026), with its fields grouped:
Group | Fields on the row | In this row |
Contract |
| SPY, 2025-03-13, 563.0, CALL |
Trade |
| 1741810493018 (4:14:53 PM ET), 1.3, 223, 28990.0, MULTIPLE, AUTO, SWEEP |
Side and quote |
| One of five side codes; bid 1.3, ask 1.33, spread 0.03 |
Underlying and context |
| 558.26, 34154, 1242, 1.03, OUT_OF_THE_MONEY, NOT_APPLICABLE, NOT_APPLICABLE, true, false |
Greeks |
| delta 0.2763, gamma 0.0434, theta -1.2714, vega 0.0992 |
Flags |
| true or false on every row |
Row id |
| A unique id for the row |
The Greeks and dte are rounded here. Unconsolidated flow rows carry the same fields, with osi (the OSI contract symbol) in place of tradeConsolidationType and isGoldenSweep. Because each row carries the bid, the ask and a trade side code, a filter can use them without a separate quote request. These are the fields the API supports; older sessions can leave some of them empty.
Use includes to keep the fields you name and drop the rest. The full list is in the Field Reference.
How is this different from other historical options data?
The Quant Data API takes the rule in the request and filters on the server, by what the trade was as well as which contract it was in: trade side, sweep, block or split, the flags, premium, size, moneyness and Greeks. You get back the rows that pass.
Historical options data often comes raw: files or unfiltered ticks that you download, store and parse before you see a row. Where an API does filter, it is often by contract and date. Finding the call sweeps of $1,000,000 or more with trade side ASK, or the trades with 7 days or less to expiration, is then left to your code. A raw file still suits you if you want every tick on your own disk.
Illustration: for the SPY session below, the left side is 1,376,613 prints to store and parse, and the right side is the 48 rows that matched.
A real example: SPY historical options data for March 12, 2025
We asked the Quant Data API for SPY’s prints from March 12, 2025 four times, tightening the filter each time, and read the match count from each response’s statistics block.
Filter in the request | Prints matched |
| 1,376,613 |
plus | 1,536 |
plus | 244 |
plus | 48 |
Unfiltered, the session is about 1,377 pages of 1,000 rows. After three filter steps it is one request. Here is that request, sent to /v1/options/tool/order-flow/unconsolidated:
{
"sessionDate": "2025-03-12",
"size": 1000,
"includeStatistics": true,
"filter": {
"ticker": "SPY",
"premiumRange": {"min": 100000},
"contractTypes": ["CALL"],
"tradeSideCodes": ["ASK", "ABOVE_ASK"],
"sizeRange": {"min": 500}
},
"includes": ["ID", "CONTRACT_TYPE", "PREMIUM", "SIZE", "TRADE_SIDE", "TRADE_TIME"]
}
The same request in Python (pip install requests):
import os
import requests
URL = "https://api.quantdata.us/v1/options/tool/order-flow/unconsolidated"
body = {
"sessionDate": "2025-03-12",
"size": 1000,
"includeStatistics": True,
"filter": {
"ticker": "SPY",
"premiumRange": {"min": 100000},
"contractTypes": ["CALL"],
"tradeSideCodes": ["ASK", "ABOVE_ASK"],
"sizeRange": {"min": 500},
},
"includes": ["ID", "CONTRACT_TYPE", "PREMIUM", "SIZE", "TRADE_SIDE", "TRADE_TIME"],
}
resp = requests.post(URL, json=body, timeout=60,
headers={"Authorization": f"Bearer {os.environ['QD_API_KEY']}"})
resp.raise_for_status()
print(len(resp.json()["data"]), "rows")
Real output, run November 12, 2026
48 rows
Real response, session March 12, 2025 (requested November 12, 2026; rows 1 to 3 of 48)
{
"data": [
{
"contractType": "CALL",
"id": "f181031d-18ec-4a18-b52c-52fe03a4c6a4",
"premium": 135200.0,
"size": 520,
"tradeSideCode": "ASK",
"tradeTime": 1741809486498
},
{
"contractType": "CALL",
"id": "3b195c41-7be1-4769-84fb-edcb90849ccf",
"premium": 304800.0,
"size": 1200,
"tradeSideCode": "ASK",
"tradeTime": 1741806767771
},
{
"contractType": "CALL",
"id": "14a34080-ba7c-4a1e-a97e-9c8cbcb32d3f",
"premium": 2592000.0,
"size": 13500,
"tradeSideCode": "ASK",
"tradeTime": 1741805016884
},
...
],
"statistics": {
"CALL": {
"ABOVE_ASK": {"tradeCount": 7, "premium": 26068388.0, "volume": 17200},
"ASK": {"tradeCount": 41, "premium": 25304969.0, "volume": 61083}
}
}
}
All 48 rows arrived on one page. premium is in dollars, and tradeTime is epoch milliseconds: 1741809486498 is 3:58:06 PM ET on March 12, 2025. The statistics block counts across the whole session rather than the page: here 48 prints and $51,373,357 in premium, the same 48 rows the page holds.
A trade side code does not tell you whether a trade opened a position, or whether the trader was bullish or bearish.
To run this request yourself, get a key on the Quant Data API plans page.
What can I filter historical options trades by?
Quant Data filters historical options trades by contract (ticker, calls or puts, expiration, strike) and by what the trade was (premium, size, moneyness, trade side, sweep, block or split, trade type, exchange, flags and Greeks): 56 filterable fields on consolidated flow and 55 on unconsolidated flow. The common filters are flat keys inside filter. Different keys combine with AND; several values in one key combine with OR.
Per the docs, the same names work in sort. Data: Quant Data API, requested November 12, 2026.
Filter on | Key | Values |
Ticker |
| One symbol or a list |
Calls or puts |
| CALL, PUT |
Premium |
| A min, a max or both, in dollars |
Size |
| A min, a max or both, in contracts |
Expiration |
| Dates, or a start and end date |
Days to expiration |
| For example DTE <= 7 |
Strike |
| A list, or a min and max |
Moneyness |
| AT_THE_MONEY, IN_THE_MONEY, OUT_OF_THE_MONEY |
Trade side |
| ABOVE_ASK, ASK, MID_MARKET, BID, BELOW_BID |
Sweep, block or split |
| SWEEP, BLOCK, SPLIT |
Trade type |
| For example AUTO or ISO |
Exchange |
| An exchange code, as it appears in a row’s |
Flags |
| true or false |
One contract |
| An OSI symbol such as SPY250313P00558000 |
Sector, industry, ETF or index |
| For example TECHNOLOGY |
Option price, bid and ask |
| A min, a max or both |
Stock price, volume and open interest |
| A min, a max or both |
For what a sweep, a block, a split and each flag mean, see Options Flow: Blocks, Splits, Sweeps, and Unusual Activity.
Every field, including Greeks such as DELTA and GAMMA, can also go in a filterExpression: a tree of AND and OR groups using =, !=, <, <=, > and >=. A flat filter and an expression can share one request. This body, sent to /v1/options/tool/order-flow/consolidated, asks for SPY puts on March 12, 2025 with premium of $500,000 or more, or a size of 1,000 contracts or more:
{
"sessionDate": "2025-03-12",
"size": 100,
"filter": {"ticker": "SPY", "contractTypes": ["PUT"]},
"filterExpression": {
"conjunction": "OR",
"filters": [
{"field": "PREMIUM", "operation": ">=", "value": 500000},
{"field": "SIZE", "operation": ">=", "value": 1000}
]
}
}
It returned a full page of 100 rows, and every row passed that rule when we checked it in code.
sort orders the rows by a filterable field such as PREMIUM, SIZE or TRADE_TIME, includes trims each row to the fields you name, and includeStatistics adds call and put totals by trade side for the whole session or window, on page 1.
After you change a filter, compare the statistics count with what you expected. It is a quick check that the filter did what you meant.
Can an AI assistant query historical options data?
Yes. An AI assistant connected to the Quant Data MCP server asks for a past session the way a script does: a date and filters in, matching rows out. Every endpoint here is a tool on the hosted server at https://api.quantdata.us/mcp with 31 tools, the same API key and the same rate limit.
The SPY session above holds 1,376,613 prints, far more than fits in an assistant’s context. Ten filtered rows fit easily.
A question such as “What were the 10 largest SPY sweeps of $1,000,000 or more on November 12, 2025?” becomes one tool call. On November 12, 2026 we sent that call to qd_get_order_flow_consolidated from a script, without an AI client, with these arguments:
{
"sessionDate": "2025-11-12",
"tickers": ["SPY"],
"tradeConsolidationTypes": ["SWEEP"],
"premiumRange": {"min": 1000000},
"sortField": "PREMIUM",
"sortDirection": "DESCENDING",
"size": 10
}
The answer was CSV-shaped text of about 6,800 characters: 10 rows, the same 10 in the same order as the matching REST request. An assistant can read that, tighten a filter and ask again. MCP arguments are flat (tickers, premiumRange, sortField), where REST nests them under filter and sort.
The call sends seven flat arguments and gets back 10 rows in place of a session file. Data: Quant Data MCP server, session November 12, 2025, requested November 12, 2026.
Setup for Claude Code and other clients is in the MCP Server docs.
How far back does the historical options data go?
Quant Data’s historical options data goes back up to 6 years, depending on the dataset, when we tested on November 12, 2026. The early years are thinner: fewer rows per session and fewer filled fields.
Dataset | Good to know |
Options trades, one row per exchange print (unconsolidated flow) | The trade-by-trade history |
Sweeps, blocks and splits (consolidated flow) | One row per sweep, block or split |
Option price over time, per contract | One-minute bars |
Open interest, by strike, by expiration and over time | Calls and puts, one value per session |
Exposure (GEX) by strike and by expiration | |
Net drift and net flow | One-minute buckets |
Max pain | One strike per expiration |
Volatility skew and term structure | By expiration and strike |
Stock price over time | One-minute bars |
Equity prints and dark pool levels |
We tested the options and stock datasets on SPY, and the two flow endpoints on AAPL and CHWY as well.
A date with no data (before the start, a weekend, a market holiday) returns HTTP 200 with an empty data list on both flow endpoints. Check for an empty list in your code; no error is raised.
How do I request a past session or a date range?
On Quant Data’s two flow endpoints, add sessionDate, written YYYY-MM-DD, for one session. For several sessions in one request, send timeRange with a startTime and an endTime in place of sessionDate. Send both and the API answers HTTP 400. Send neither and it uses the most recent session.
This body covers the five sessions from March 10 to March 14, 2025:
{
"timeRange": {"startTime": "2025-03-10T13:30:00Z", "endTime": "2025-03-14T20:15:00Z"},
"size": 100,
"includeStatistics": true,
"filter": {
"ticker": "SPY",
"tradeConsolidationTypes": ["SWEEP"],
"premiumRange": {"min": 1000000}
},
"sort": {"field": "TRADE_TIME", "direction": "ASCENDING"},
"includes": ["ID", "CONTRACT_TYPE", "PREMIUM", "TRADE_TIME"]
}
The times are UTC: 13:30Z is 9:30 AM ET and 20:15Z is 4:15 PM ET on those dates. A timeRange can also cut inside one session: 13:30Z to 14:00Z on March 12, 2025 returned rows between 9:30 and 10:00 AM ET, and every row on the page we checked was inside that window.
The week request returned 260 SPY sweeps of $1,000,000 or more across three pages: 50, 69, 60, 52 and 29 for the five days. To page, pass each response’s nextSearchAfter back unchanged as searchAfter. When a response has none, you have every row; see Pagination.
With the same filter, a timeRange covering all of 2025 and a size of 1 returned the year’s statistics in one response: 8,022 sweeps and $18,980,716,990 in premium.
Can I backtest with historical options data in Python?
Yes. To use Quant Data’s historical options data for backtesting, loop over sessions: the same filter, one session after another. This script asks consolidated flow for SPY sweeps of $1,000,000 or more on every weekday of September 2025, counts calls and puts, sums their premium and writes a CSV. It reads your key from QD_API_KEY, waits on a 429 and stops after 80 requests no matter what (pip install requests).
"""SPY sweeps of $1,000,000 or more, counted per session for one past month.
Reads QD_API_KEY from the environment. Writes backtest_month.csv next to where you run it.
"""
import csv
import os
import time
from datetime import date, timedelta
import requests
API_KEY = os.environ["QD_API_KEY"]
URL = "https://api.quantdata.us/v1/options/tool/order-flow/consolidated"
YEAR, MONTH = 2025, 9
FILTER = {
"ticker": "SPY",
"tradeConsolidationTypes": ["SWEEP"],
"premiumRange": {"min": 1000000},
}
MAX_REQUESTS = 80 # hard stop, so a bad loop never runs forever
calls = 0
def post(body):
"""One request. Waits and retries on a 429."""
global calls
while True:
if calls >= MAX_REQUESTS:
raise RuntimeError("hard stop reached")
calls += 1
resp = requests.post(URL, json=body, timeout=60,
headers={"Authorization": f"Bearer {API_KEY}"})
if resp.status_code == 429:
time.sleep(float(resp.headers.get("Retry-After", "1")) + 0.5)
continue
resp.raise_for_status()
time.sleep(0.3)
return resp.json()
def one_session(day):
"""Every matching row for one session, plus the statistics block from page 1."""
body = {
"sessionDate": day,
"size": 100,
"includeStatistics": True,
"filter": FILTER,
"includes": ["ID", "CONTRACT_TYPE", "PREMIUM"],
}
rows, stats = {}, None
while True:
page = post(body)
stats = stats or page.get("statistics")
for row in page["data"]:
rows[row["id"]] = row # de-duplicate by id
cursor = page.get("nextSearchAfter")
if not cursor:
return list(rows.values()), stats or {}
body["searchAfter"] = cursor # pass it back unchanged
def total(rows, contract_type):
mine = [row for row in rows if row["contractType"] == contract_type]
return len(mine), sum(row["premium"] for row in mine)
def stat_count(stats, contract_type):
return sum(cell["tradeCount"] for cell in stats.get(contract_type, {}).values())
out = []
day = date(YEAR, MONTH, 1)
print("session calls call premium puts put premium statistics")
while day.month == MONTH:
if day.weekday() < 5: # Monday to Friday
rows, stats = one_session(day.isoformat())
if not rows:
print(f"{day} no rows")
else:
calls_n, calls_usd = total(rows, "CALL")
puts_n, puts_usd = total(rows, "PUT")
same = (calls_n, puts_n) == (stat_count(stats, "CALL"), stat_count(stats, "PUT"))
print(f"{day} {calls_n:6d} ${calls_usd:13,.0f} {puts_n:5d} ${puts_usd:13,.0f} "
f"{'match' if same else 'differ'}")
out.append([day.isoformat(), calls_n, round(calls_usd), puts_n, round(puts_usd)])
day += timedelta(days=1)
with open("backtest_month.csv", "w", newline="") as f:
w = csv.writer(f)
w.writerow(["session", "call_count", "call_premium", "put_count", "put_premium"])
w.writerows(out)
print(f"{len(out)} sessions, {calls} requests, saved backtest_month.csv")
Real output, run November 12, 2026 (the opening lines and the closing line; the full run printed 21 sessions)
session calls call premium puts put premium statistics
2025-09-01 no rows
2025-09-02 5 $ 6,864,075 32 $ 51,574,437 match
2025-09-03 4 $ 5,207,707 6 $ 10,806,018 match
2025-09-04 21 $ 47,319,278 14 $ 18,569,085 match
...
21 sessions, 23 requests, saved backtest_month.csv
The run took 11.4 seconds: 21 sessions in 23 requests, because September 26 needed a second page. September 1 was Labor Day, so it printed no rows. The last column compares the script’s row counts with the statistics block, and they matched on all 21 sessions.
September 24, 25 and 26 held 212 of the month’s 364 call sweeps. Data: Quant Data API, requested November 12, 2026.
Swap FILTER for any consolidated flow filter on this page and the loop stays the same.
To test a rule, join these rows to prices from the same key. Stock price over time returns one-minute bars for a past session (405 for SPY on June 10, 2025), and option price over time returns one-minute bars for one contract. Sort by TRADE_TIME ascending to read a session in the order it traded; by default rows come newest to oldest.
How much does it cost, and what are the limits?
The Quant Data API is $149.99 a month, or $1,499.99 a year, for every endpoint over REST and MCP, past sessions included.
The request limit is 240 per 60 seconds, plus a burst cap of 20 per second, with no monthly quota. Go over and you get HTTP 429 with a Retry-After header.
Options data needs a signed market-data subscriber agreement. Until you sign, options requests return HTTP 403 with the signing link in the response.
Personal use only. Not for external redistribution. Contact us for enterprise pricing.
Why use Quant Data for historical options data?
Quant Data serves past sessions from the same endpoints as the current one, so a filter written for today’s session runs on a past session once you add a date. The data is exchange-licensed and covers 6,000+ tickers, and one key reaches all 30 REST endpoints and the MCP server.
Filter before you fetch. Trade side, sweep, block or split, flags, premium, size, moneyness and Greeks are filters in the request, with AND/OR logic, sorting and field selection.
Check it before you buy. Every endpoint’s fields, filters and errors are in the public API docs.
FAQ
Is there a historical options trades API?
Yes. Quant Data’s unconsolidated flow endpoint is a historical options trades API: one row per exchange print, up to 1,000 rows a page, filtered on 55 fields. The Quant Data API costs $149.99 a month or $1,499.99 a year for every endpoint.
Can I get historical options flow from the Quant Data API?
Yes. Quant Data’s consolidated flow endpoint returns historical options flow as one row per sweep, block or split, with up to 6 years of history when we tested on November 12, 2026. The early years are thinner.
Does the Quant Data API have historical options tick data?
Yes, for trades. Quant Data’s unconsolidated flow is trade tick data: one row per print on one exchange, with tradeTime in epoch milliseconds. For bars, option price over time and stock price over time return one-minute bars.
Does the Quant Data API have historical option prices?
Yes. For historical option prices, Quant Data’s option price over time endpoint returns one-minute bars (open, high, low, close and volume) for one contract on a past session. For the SPY July 18, 2025 $590 call on June 10, 2025 it returned 41 one-minute bars between 9:38 AM and 3:58 PM ET.
Can I get a historical options chain from the Quant Data API?
The Quant Data API returns a past session by strike and expiration, one measure per endpoint: open interest, volatility skew, term structure and exposure (GEX). For SPY on June 10, 2025, open interest by strike returned 347 strikes and open interest by expiration returned 33 expirations.
Does the Quant Data API have implied volatility history?
Yes. Quant Data’s volatility skew endpoint returns implied volatility for calls and puts by expiration and strike for a past session, and its term structure endpoint returns delta and IV by expiration and strike.
Does the Quant Data API have historical open interest data?
Yes. One open interest over time request to the Quant Data API returned SPY call and put open interest for every session it holds, in one response. Open interest by strike and by expiration break one past session down further.
Can I download historical options data as a CSV?
The Quant Data API returns JSON over REST, not files. To keep a CSV, write the rows you asked for to disk, which takes a few lines of Python. Over MCP, tool results come back as CSV-shaped text.
Can I get historical data for expired options contracts?
Yes. Send the contract’s OSI symbol as filter.osi to Quant Data’s unconsolidated flow endpoint with a past sessionDate. SPY250313P00558000 expired on March 13, 2025; a request sent on November 12, 2026 for March 12, 2025 returned 200 of its prints on page 1, with more pages behind it.
Can I pull historical options data with Python?
Yes. The Quant Data API is plain HTTPS: POST a JSON body with the requests library and read the JSON that comes back. A short Python request for SPY on March 12, 2025 printed 48 rows, and a longer script counted SPY sweeps for September 2025 across 21 sessions in 23 requests and 11.4 seconds.
Where are the full API docs?
Quant Data API overview: API key setup and an overview of the API
API docs: every endpoint
order-flow/unconsolidated and order-flow/consolidated: the two flow endpoints
I have not signed up for the Quant Data API. How do I?
Click here to see the API plans and get your API key. Click on Learn More if you would like to learn more about what we offer.
Would you like further assistance?
If you have any questions or need further assistance, please don’t hesitate to reach out to the Quant Data team at support@quantdata.us or via our live chat located on the bottom right-hand corner of your screen. We are available on our live chat between 9:30 AM and 5:00 PM (eastern time), Monday through Friday.





