tickstreamdocs

DOCS

Options data

Full OPRA options data over REST: 12 request types from live chains to trades joined with the NBBO of that millisecond, 12 years deep.

Every US listed option, from OPRA: every trade, every NBBO quote, greeks, open interest and the joins between them. 12 request types, all under /v1/options/<type>, all returning the same envelope.

this replaced the old options feed on 2026-08-02

The previous provider is gone. If you were reading the options WebSocket channel for chains and greeks, that still works and is described below — but everything historical, every trade and every quote now comes from these REST types instead, with twelve years of history behind them rather than a few months.

What each package unlocks

The tiers are not three sizes of the same thing. Each unlocks the first N request types in the canonical order below, and the order is the argument: the surface, then the tape, then the joins.

The surface — what the chain looks like

TypeEndpointWhat it returnsPackage
chain /v1/options/chain Live chain — bid/ask/last, open interest and volume per strike. Options Core
greeks /v1/options/greeks Live greeks and implied vol per strike, for one expiration. Options Core
eod /v1/options/eod Daily close per contract, twelve years back. Options Core

The tape — what actually printed

TypeEndpointWhat it returnsPackage
ohlc /v1/options/ohlc OHLC bars per contract, down to tick interval. Options Flow
oi /v1/options/oi Open interest per contract per day — the opening-vs-closing input. Options Flow
quote /v1/options/quote Every NBBO quote reported by OPRA, with size and exchange. Options Flow
trade /v1/options/trade Every option trade reported by OPRA, with size and condition. Options Flow

The joins — what a print meant

TypeEndpointWhat it returnsPackage
trade_quote /v1/options/trade_quote Each trade paired with the NBBO standing at that millisecond — the flow primitive. Options Pro
greeks_history /v1/options/greeks_history Historical greeks and IV as a time series. Options Pro
trade_greeks /v1/options/trade_greeks Every trade with the greeks as they stood at trade time. Options Pro
at_time /v1/options/at_time The exact trade or quote in force at a given timestamp. Options Pro
root /v1/options/root Whole-root bulk snapshot — every expiration in a single call. Options Pro
refused, not truncated

A request type above your package returns 403 with request_type_not_in_plan, the package that would unlock it, and the list your key does have. It never returns a thinner version of the answer — a silently reduced result set is the worst possible failure for a backtest.

Making a call

requiresOptions Core or above

curl "https://api.tick-stream.xyz/v1/options/trade_quote?symbol=SPY&exp=2026-08-21&strike=600&right=C&date=2026-07-31" \
  -H "Authorization: Bearer sk_live_…"
import requests

r = requests.get(
    "https://api.tick-stream.xyz/v1/options/trade_quote",
    params={"symbol": "SPY", "exp": "2026-08-21",
            "strike": 600, "right": "C", "date": "2026-07-31"},
    headers={"Authorization": "Bearer sk_live_…"},
).json()

# columns arrive by NAME in the header — never index by position
cols = {c: i for i, c in enumerate(r["header"]["format"])}
for row in r["response"][0]["ticks"]:
    aggressive = row[cols["price"]] >= row[cols["ask"]]
const u = new URL("https://api.tick-stream.xyz/v1/options/trade_quote");
u.searchParams.set("symbol", "SPY");
u.searchParams.set("exp", "2026-08-21");
u.searchParams.set("strike", "600");
u.searchParams.set("right", "C");
u.searchParams.set("date", "2026-07-31");

const r = await fetch(u, { headers: { Authorization: "Bearer sk_live_…" } });

Parameters

ParameterExampleNotes
symbolSPYThe root. underlying and root are accepted as aliases.
exp2026-08-21Expiration. expiration is accepted; dashes optional. On the history types, * means every expiration — see bulk history.
max_dte30With exp=*: only contracts within N calendar days of expiry, measured on each session in the window.
strike_range550,650With exp=*: clip the strike ladder instead of taking all of it.
strike600In dollars. We convert to the thousandths the vendor wants — you never see 600000 on the way in.
rightC / PCall or put.
date2026-07-31One session. Expands to start_date+end_date internally.
start_date, end_date2026-07-01A range instead of one session.
interval60000Bar interval in ms, for ohlc, quote, greeks and implied_volatility. Defaults to one minute.

The response envelope

Rows are arrays, and the column names are in header.format. Read them by name. Column order is the vendor's and is not part of our contract — code that indexes by position will break on a day you are not watching.

{
  "header": {
    "format": ["ms_of_day", "sequence", "size", "condition",
               "price", "bid_size", "bid", "ask_size", "ask", "date"],
    "next_page": "null"
  },
  "response": [
    {
      "contract": { "root": "SPY", "expiration": 20260821, "strike": 600000, "right": "C" },
      "ticks": [
        [51293846, 88213, 250, 18, 4.35, 88, 4.30, 120, 4.35, 20260731]
      ]
    }
  ]
}

Conventions worth knowing before your first parse

ThingWhat it actually is
ms_of_day + dateMilliseconds since midnight and YYYYMMDD, both in US/Eastern. We pass them through rather than converting to UTC, because converting loses the session boundary you almost always want.
strike in contractThousandths of a dollar — 600000 is $600.00. Only on the way out; the query takes dollars.
iv_error0.0 means the solver converged. 100.0 is the failed-solve sentinel — drop those rows rather than trusting the IV beside them.
PagingHandled for you. We follow next_page internally and concatenate, so a multi-page answer arrives as one response. You will never see an internal URL.
Empty resultAn empty ticks array, not an error. A contract that did not trade that session is a fact, not a failure.

trade_quote — the one worth the upgrade

requiresOptions Pro or above

Every trade paired with the NBBO that stood in that exact millisecond. Without the pairing a print is a number; with it, it is an aggressive buy or a passive fill:

price >= ask  →  lifted the offer
price <= bid  →  hit the bid
size  >  ask_size  →  took more than was displayed

That last line is why the join matters more than the trade feed alone. A 250-lot into a 120-lot offer is a different event from a 250-lot into a 5,000-lot offer, and the trade record on its own cannot tell you which one happened.

Whole-root snapshots

requiresOptions Pro or above

/v1/options/root returns every expiration of a root in one call, rather than one request per contract. It is the difference between a chain sweep that takes a second and one that takes four hundred requests.

Bulk history — the whole chain in one request

requiresOptions Core or above

Every history type accepts expiration=*: one request answers every contract of the root for the date window, instead of one request per expiration. This is the shape to use for backfills — a month of QQQ end-of-day (60,260 contracts) is a single call that returns in about twenty seconds:

/v1/options/eod?symbol=QQQ&expiration=*&start_date=2019-03-01&end_date=2019-03-31
/v1/options/oi?symbol=QQQ&expiration=*&start_date=2019-03-01&end_date=2019-03-31
/v1/options/trade_quote?symbol=QQQ&expiration=*&max_dte=7&date=2024-03-15

max_dte and strike_range clip the wildcard: the third example is the full front-week tape of a session — every trade with its NBBO — without pulling the LEAPS ladder along with it. Pulling seven years this way is a few hundred month-sized requests, not hundreds of thousands of per-contract ones.

eod and oi end yesterday, by design

The EOD report is generated at 17:15 ET and open interest arrives the next morning — the running session's report does not exist yet. If your window includes today, we clamp it to yesterday and say so in an x-end-clamped: running-day response header. Today's chain is the live side's job: chain, root or the WebSocket below.

Live chains over WebSocket

requiresOptions Core or above

The options channel still streams live chains with greeks by underlying — unchanged by the provider switch. Subscribe the same way as any other channel:

{ "op": "subscribe", "channel": "options", "symbols": ["SPY", "QQQ"] }
greeks at the edges

Deep OTM and deep ITM strikes routinely return zero greeks, and occasionally an absurd IV. That is the pricing model failing on an illiquid strike, not missing data, and we pass it through unchanged rather than inventing a value. Filter on iv_error and on quote size before you aggregate — those rows are exactly the ones that would otherwise dominate a GEX or vol-surface sum.

How far back

PackageHistoryTick level
Options Core4 years
Options Flow8 yearsyes
Options Pro12 yearsyes

The contract universe itself reaches back to 2012-06-01; a request older than your package's window is clamped to it rather than rejected. Verified at the far end on 2026-08-03: SPY 132C expiring 2012-06-16, session 2012-06-11 — 2,592 prints, 2,137 distinct millisecond stamps, full OPRA fields with the NBBO beside them.

professional display use

Viewing this data as a professional is an OPRA licence question, not a plan question, and no self-service package covers it. The arithmetic is public — work out your number.