Docs
GET /v2/chain/{symbol}

The option chain for one underlying

chain_getChain

Every listed contract of one underlying from a single capture, with quotes, open interest, volume, implied volatility and greeks. Use it for a full or filtered chain snapshot, live or at a past time with at=.

Path parameters

symbol string required

Underlying symbol, e.g. SPY

Query parameters

at string epoch ms

Point in time to read instead of the live capture: epoch-ms, or a YYYY-MM-DD / YYYY-MM-DDTHH:MM[:SS] string interpreted in tz.

tz string

IANA timezone for a string at (default America/New_York).

expiry string ISO date

Keep one expiry, ISO YYYY-MM-DD.

strike_gte string 1/1000 $

Strike lower bound, integer thousandths (inclusive).

strike_lte string 1/1000 $

Strike upper bound, integer thousandths (inclusive).

Details

Every row is valued against the one spot of the capture, so rows are consistent with each other. Fields the capture did not report are omitted from the row. partial: true means the capture is known to be incomplete.

Units

  • strike is in integer thousandths of a dollar (765000 = $765.00); strike_gte / strike_lte use the same unit.
  • spot, bid, ask, last and fmv are dollars (option prices per share); theta is dollars per day.
  • iv is annualized, as a fraction (0.126 = 12.6%).
  • oi (open interest) and volume are contracts; shares_per_contract is 100 for standard contracts.
  • captured_at is epoch milliseconds.

Freshness

A live read (no at=) is served from a shared capture that is re-taken at most every 30 seconds per symbol; while a refresh runs, the previous capture is served for up to 5 minutes. captured_at says when the served capture was taken, and meta.cache is hit, stale, miss or coalesced. The first read of a symbol can take a few seconds (SPY has thousands of contracts); filters apply to the cached capture, so they cost nothing extra.

Limits

No per-operation limit applies to requests with an API key.

Empty results

A symbol with no listed options returns 200 with rows: [].

Example

http
GET /v2/chain/SPY?expiry=2026-10-02&strike_gte=700000&strike_lte=700000
json
{
  "data": {
    "spot": 772.05,
    "captured_at": 1790493741321,
    "partial": false,
    "rows": [
      {
        "contract": {
          "underlying": "SPY",
          "expiry": "2026-10-02",
          "right": "call",
          "strike": 700000
        },
        "oi": 507,
        "volume": 256,
        "bid": 71.57,
        "ask": 72.83,
        "last": 71.92,
        "fmv": 72.201,
        "iv": 0.4379,
        "delta": 0.9666,
        "gamma": 0.0018,
        "theta": -0.3226,
        "vega": 0.0924,
        "shares_per_contract": 100
      },
      {
        "contract": {
          "underlying": "SPY",
          "expiry": "2026-10-02",
          "right": "put",
          "strike": 700000
        },
        "oi": 10522,
        "volume": 358,
        "bid": 0.05,
        "ask": 0.06,
        "last": 0.06,
        "fmv": 0.055,
        "iv": 0.3144,
        "delta": -0.0054,
        "gamma": 0.0005,
        "theta": -0.0408,
        "vega": 0.0196,
        "shares_per_contract": 100
      }
    ]
  },
  "meta": { "cache": "miss", "row_count": 2, "underlying": "SPY" }
}

Response

Prices are dollars, timestamps epoch milliseconds, strikes integer thousandths of a dollar; null means unknown. Units →

200application/jsonapplication/x-protobuf

{spot, captured_at, partial, rows} in data; provenance in meta. Under Accept: application/x-protobuf the same capture is served as the public protobuf body (/v2/rest-protocol.proto); JSON stays the default for every other Accept.

object

A successful response: the payload consumers parse plus response metadata. data shapes are the platform types — never reshaped by the envelope.

data object required

The contract chain view: one spot for the whole capture, the capture instant, a partial flag, and the per-contract rows.

captured_at integer · int64 nullable epoch ms

Capture instant, epoch-ms.

partial boolean required

The capture is knowingly incomplete (partial ingest / convert drops).

rows array<ChainSnapshotRow> required
ask number · double nullable
bid number · double nullable
contract object required

A single listed option contract, identified the way the OSI does: root symbol, expiry date, right, strike.

underlying is the OSI root (e.g. SPXW for SPX weeklies), stored unpadded and uppercase.

expiry string · date required ISO date
right string required

Call or put.

One of callput
strike integer · int64 required 1/1000 $

Strike in integer thousandths of a dollar (exact OSI unit)

underlying string required
delta number · double nullable
fmv number · double nullable

Vendor fair-market/model value — kept as captured, provenance only.

gamma number · double nullable
iv number · double nullable

Vendor implied volatility.

last number · double nullable

Last trade price.

oi integer · int64 nullable

Open interest (vendor-daily; None = not reported).

shares_per_contract integer · int32

Contract multiplier; 100 for standard contracts (S5: carried per row because adjusted/mini contracts differ).

theta number · double nullable
vega number · double nullable
volume integer · int64 nullable

Day volume at capture.

spot number · double nullable

The one spot the whole capture is valued against.

meta object required

Response metadata.

caps object nullable

meta.caps — the entitlement caps that shaped this response. applied lists the caps that actually changed it (for example range_clamped).

applied array<string> required

Which caps actually shaped this response (e.g. range_clamped). Empty = nothing was clamped.

darkpool boolean nullable

Whether this account can use Off-Exchange in the site.

flow boolean nullable

Whether the OPRA option-flow capability (flow) is granted to this principal in the app: site Pro plus a CBOE non-professional attestation (never on an itm_ key). Emitted explicitly — true and false — by the account endpoints, so the app reads the grant instead of inferring it from tier and attestation; absent on routes that do not report it.

gexDepth string nullable

GEX data depth when the route reports it. Camel-cased to match the browser capability bundle; absent on unrelated routes.

tier string required

The tier the request was served under.

cursor string nullable

Opaque continuation cursor (pass back as ?cursor=); absent when there is nothing more to read.

plane string nullable

Which serving tier answered the read (meta.plane). Informational provenance: treat it as an opaque label, because new values may appear.

Send Accept: application/x-protobuf for a protobuf body instead of JSON — see Protobuf. Errors stay JSON.

Errors

Every error is the JSON error envelope; branch on error.code.

StatusMeaning
400

invalid_params — bad at/expiry/strike_*/plane

401

unauthenticated / token_expired

403

not_entitled_tier / attestation_required

502

internal — the selected source failed upstream

503

internal — the selected source is not configured here