- Live API Reference
- Trade Flow Data
- Options
- Contract Minute History
Option Contract Minute History API
Minute buckets of buy, sell and mid classified volume for every option contract (strike, right, expiry) on an underlying. Sum buys minus sells per strike for net customer flow.
Endpoint
X-Api-Key)
Rate Limited: Yes
Plan: Alpha+
Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
symbol |
path | string | required | - | Underlying symbol (case-insensitive) |
expiry |
query | date | - | Only contracts of one expiration, yyyy-MM-dd. Omit to return every expiry - a large payload on busy chains (SPY: about 7 MB). Invalid values return 400 invalid_expiry. An expiry with no data returns an empty result, not an error. |
Request
curl -H "X-Api-Key: YOUR_API_KEY" \
"https://lab.flashalpha.com/v1/flow/options/SPY/option-minute-history?expiry=2026-12-18"import requests
from collections import defaultdict
resp = requests.get(
"https://lab.flashalpha.com/v1/flow/options/SPY/option-minute-history",
params={"expiry": "2026-12-18"},
headers={"X-Api-Key": "YOUR_API_KEY"}
)
resp.raise_for_status()
data = resp.json()
# net customer flow per strike = sum of (buy - sell) over all minutes
net = defaultdict(int)
for c in data["contracts"]:
net[c["strike"]] += sum(b["buy"] - b["sell"] for b in c["buckets"])
for strike in sorted(net):
print(strike, net[strike])const resp = await fetch(
"https://lab.flashalpha.com/v1/flow/options/SPY/option-minute-history?expiry=2026-12-18",
{ headers: { "X-Api-Key": "YOUR_API_KEY" } }
);
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const data = await resp.json();
// net customer flow per strike = sum of (buy - sell) over all minutes
const net = {};
for (const c of data.contracts) {
for (const b of c.buckets) net[c.strike] = (net[c.strike] || 0) + b.buy - b.sell;
}
console.log(net);Try it live
Runs on live market data and uses 1 request from your daily quota.
Response
{
"symbol": "SPY",
"contracts": [
{
"strike": 300,
"right": "C",
"expiry": "2026-12-18",
"buckets": [
{
"t": "2026-10-29T13:55:00Z",
"buy": 0,
"sell": 0,
"mid": 1
},
{
"t": "2026-10-29T14:14:00Z",
"buy": 1,
"sell": 0,
"mid": 0
}
]
},
{
"strike": 375,
"right": "P",
"expiry": "2026-12-18",
"buckets": [
{
"t": "2026-10-29T15:36:00Z",
"buy": 2,
"sell": 0,
"mid": 0
}
]
}
],
"endpoint_version": "2026.09.30",
"data_as_of": {
"node": "fa5",
"equity_feed": "2026-10-30T14:15:29.104Z",
"equity_options_feed": "2026-10-30T14:15:28Z",
"index_feed": "2026-10-30T14:15:29.102Z",
"index_options_feed": "2026-10-30T14:15:27Z",
"futures_feed": "2026-10-30T14:15:28.643Z",
"futures_options_feed": "2026-10-30T14:15:27.966Z",
"flow_feed": "2026-10-30T14:15:00Z",
"oi_feed": "2026-10-29T20:00:00Z",
"macro_feed": "2026-10-29T21:00:20Z"
}
}Sending request…
See this endpoint on live data
Send this exact request and the real response shows up here, right next to the example.
Responses can exceed 380 KB.
Response Fields
| Field | Type | Description |
|---|---|---|
symbol | string | Uppercase underlying symbol |
contracts[] | array | One item per option contract that traded. Empty when there is no flow for the symbol or expiry (still 200). Not sorted. |
contracts[].strike | number | Strike price |
contracts[].right | string | C (call) or P (put) |
contracts[].expiry | string | Expiration date, yyyy-MM-dd |
contracts[].buckets[] | array | Minutes with trades only, oldest first |
contracts[].buckets[].t | string | Minute start (UTC) |
contracts[].buckets[].buy | number | Contracts bought at the ask in that minute |
contracts[].buckets[].sell | number | Contracts sold at the bid in that minute |
contracts[].buckets[].mid | number | Contracts traded at mid in that minute |
endpoint_version, data_as_of | string, object | Response envelope, see Response envelope. |
Errors
| Status | Error | When |
|---|---|---|
400 | invalid_expiry | expiry is not a valid yyyy-MM-dd date. |
401 | - | Missing or invalid X-Api-Key. The body is empty; check the status code before parsing JSON. |
403 | tier_restricted | Your plan is below Alpha. The body includes current_plan and required_plan. |
429 | Quota exceeded | Daily quota used up. Retry-After gives the seconds until the reset at 00:00 UTC. Rejected requests (including 403) count toward the quota. |
About
Returns the option tape of an underlying split by contract: for every strike, right and expiry that traded, one bucket per minute with the contracts bought, sold and traded at mid. Sum buy - sell per strike for net customer flow. The inferred dealer inventory change is its negation, sell - buy, accumulating from zero at the 09:30 ET open - a change over the session, never an absolute dealer position.
Without expiry the response covers the whole chain and can be large (SPY: about 4,500 contracts and 7 MB). Filter to one expiry when you only need one cycle. For totals per underlying instead of per contract, use Options Trade History.
Common Use Cases
- Net customer flow by strike - sum
buy - sellover every bucket of each strike to see where customers bought or sold options today - Dealer inventory change by strike - negate the customer flow (
sell - buy) to estimate how dealer positioning changed since the open - Strike x time heatmap - plot per-minute net flow for each strike to see when and where activity concentrated
- One expiry cycle - pass
expiryto follow flow into a single OPEX or weekly without downloading the full chain - Calls vs puts - split by
rightto compare call and put buying at the same strike
Related
Related reading
- Options flow API: recent trades, blocks & leaderboard - how the raw tape endpoints fit together end to end
Complementary endpoints
- Options Trade History - the same minute flow rolled up per underlying, with VWAP and high/low
- Options Recent Trades - the individual prints behind each bucket
- Flow GEX -
?polarity=flowsigns each strike by the dealer inventory change inferred from the tape - 0DTE Strike Flow - per-strike signed aggressor flow for the same-day expiry