Unusual Whales API - GEX + Greeks endpoints
Foundation reference for any MK3 strategy that reads dealer-positioning
data. Ten endpoints, three families: aggregated exposure (daily
snapshots), flow (intraday transactional Greeks), and spot
exposures (1-minute snapshots with intraday granularity). All under
/api/stock/{ticker}/.... All Bearer-auth.
This page is the source-of-truth for endpoint shapes, request params, response schemas, and how each endpoint maps to MK3 strategy primitives.
1. Base contract (applies to all 10)
| Property | Value |
|---|---|
| Base URL | https://api.unusualwhales.com |
| Auth | Authorization: Bearer <token> header |
| Date format | YYYY-MM-DD (ISO 8601 date) |
| Default date | Last trading date if date omitted |
| Numeric format | Strings (parse to float / Decimal) |
| Response envelope | { "data": [ ... ] } |
| Rate limits | Not in per-endpoint docs - check concepts/uw-api-overview (TBD) |
Reminder. UW returns numbers as JSON strings ("call_gex": "227549667.4651").
Every Python deserializer must coerce. Burned MK2 once.
2. Endpoint matrix
| # | Family | Path | Granularity | Key extras |
|---|---|---|---|---|
| 1 | Exposure | GET /api/stock/{ticker}/greek-exposure | Daily, time series | timeframe (1D-2D, 1W-2W, …, YTD, 1Y) |
| 2 | Exposure | GET /api/stock/{ticker}/greek-exposure/expiry | Daily, by expiry | DTE + expiry |
| 3 | Exposure | GET /api/stock/{ticker}/greek-exposure/strike | Daily, by strike | |
| 4 | Exposure | GET /api/stock/{ticker}/greek-exposure/strike-expiry | Daily, strike × expiry | Requires expiry query param |
| 5 | Flow | GET /api/stock/{ticker}/greek-flow | Intraday total flow | Transactional, not positional |
| 6 | Flow | GET /api/stock/{ticker}/greek-flow/{expiry} | Intraday flow by expiry | Path param expiry |
| 7 | Spot | GET /api/stock/{ticker}/spot-exposures | 1-minute | Time-series of intraday spot Greeks |
| 8 | Spot | GET /api/stock/{ticker}/spot-exposures/expiry-strike | Daily, strike × expiry (v2) | expirations[], pagination, DTE filters |
| 9 | Spot | GET /api/stock/{ticker}/spot-exposures/strike | Daily, by strike | Strike + pagination filters |
| 10 | Spot | GET /api/stock/{ticker}/spot-exposures/{expiry}/strike | DEPRECATED - use #8 | v1, do not build new code against this |
3. Family 1: Greek Exposure (positional, daily)
These return dealer positioning (where the book is right now). One data point per trading date. Use for regime gates, OPEX framing, daily context.
3.1 GET /api/stock/{ticker}/greek-exposure
Aggregate Greeks across all strikes + all expiries, one row per trading date over a timeframe.
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
ticker (path) | string | yes | - | AAPL, SPY, … |
date (query) | date | no | last trading date | |
timeframe (query) | string | no | 1Y | 1D, 2D, 1W, 2W, 1M, 2M, 1Y, 2Y, YTD |
Response fields per row:
| Field | Description |
|---|---|
date | Trading date |
call_charm | Aggregate call charm GEX |
call_delta | Aggregate call delta GEX |
call_gamma | Aggregate call gamma GEX |
call_vanna | Aggregate call vanna GEX |
put_charm | Aggregate put charm GEX |
put_delta | Aggregate put delta GEX |
put_gamma | Aggregate put gamma GEX |
put_vanna | Aggregate put vanna GEX |
Use in MK3: primary daily regime feature. net_gex = call_gamma - put_gamma
drives the negative-GEX trend gate (60.5% hit rate per
2026-05-22-uw-historical-findings).
3.2 GET /api/stock/{ticker}/greek-exposure/expiry
Same Greeks, broken by expiration date.
| Param | Type | Required | Default |
|---|---|---|---|
ticker (path) | string | yes | - |
date (query) | date | no | last trading date |
Response per row: expiry, dte, date, call_delta, call_gex,
call_vanna, call_charm, put_delta, put_gex, put_vanna, put_charm.
Use in MK3: OPEX-week regime modulation, term-structure of dealer positioning. The 0DTE row is the most informative for SPY hunter.
3.3 GET /api/stock/{ticker}/greek-exposure/strike
Same Greeks, broken by strike.
| Param | Type | Required | Default |
|---|---|---|---|
ticker (path) | string | yes | - |
date (query) | date | no | last trading date |
Response per row: strike, call_gex, call_delta, call_vanna,
call_charm, put_gex, put_delta, put_vanna, put_charm.
Use in MK3: locate gamma walls and pin levels. The strike with the
largest call_gex + |put_gex| cluster is the magnet level for 0DTE.
3.4 GET /api/stock/{ticker}/greek-exposure/strike-expiry
Cross of strike × single expiry. expiry is REQUIRED.
| Param | Type | Required | Default |
|---|---|---|---|
ticker (path) | string | yes | - |
expiry (query) | date | yes | - |
date (query) | date | no | last trading date |
Response per row: strike, expiry, call_delta, call_gex,
call_vanna, call_charm, put_delta, put_gex, put_vanna,
put_charm.
Use in MK3: the finest cut of dealer positioning. For 0DTE SPY, this is the table that tells you “the dealer book is short gamma below 580 and long gamma above 585” - exactly where pinning fails and trends start.
4. Family 2: Greek Flow (transactional, intraday)
Flow ≠ exposure. Flow is what the tape just executed (transactional delta/vega change). Exposure is where the book sits. Trade flow is what shifts exposure.
4.1 GET /api/stock/{ticker}/greek-flow
Aggregate intraday flow.
| Param | Type | Required | Default |
|---|---|---|---|
ticker (path) | string | yes | - |
date (query) | date | no | last trading date |
Response fields:
| Field | Description |
|---|---|
ticker | Symbol |
timestamp | ISO 8601 |
transactions | Trade count |
volume | Total volume |
total_delta_flow | Sum |
total_vega_flow | Sum |
dir_delta_flow | Directional (signed) delta flow |
dir_vega_flow | Directional (signed) vega flow |
otm_total_delta_flow | OTM-only total |
otm_total_vega_flow | OTM-only total |
otm_dir_delta_flow | OTM-only directional |
otm_dir_vega_flow | OTM-only directional |
Use in MK3: real-time aggressor confirmation. Crucially, per
2026-05-22-uw-historical-findings, strongly positive dir_delta_flow
on OTM calls is CONTRARIAN-bearish, not bullish - high call-buying
predicts SPY down -3.6 to -8.7 lift below baseline. Sign carefully.
4.2 GET /api/stock/{ticker}/greek-flow/{expiry}
Same fields plus expiry, filtered to one expiration.
| Param | Type | Required | Default |
|---|---|---|---|
ticker (path) | string | yes | - |
expiry (path) | date | yes | - |
date (query) | date | no | last trading date |
Use in MK3: 0DTE-isolated flow. For SPY hunter, query expiry = today’s date.
Example response (from docs):
{
"data": [
{
"ticker": "SPY",
"expiry": "2024-10-28",
"timestamp": "2024-10-28T18:46:00Z",
"transactions": 1188,
"volume": 12348,
"total_delta_flow": "-21257.36",
"total_vega_flow": "350944.58",
"dir_delta_flow": "-43593.96",
"dir_vega_flow": "31243.04",
"otm_total_delta_flow": "-28564.02",
"otm_total_vega_flow": "101745.64",
"otm_dir_delta_flow": "14947.51",
"otm_dir_vega_flow": "11421.03"
}
]
}5. Family 3: Spot Exposures (positional, 1-minute intraday)
The crown jewel for intraday work. Same Greeks as Family 1 (positional,
not flow) but refreshed every minute through the trading session.
The dimensional cuts also split exposure across vol, oi, dir, and
bid/ask bands, giving a much richer view than the daily summaries.
5.1 GET /api/stock/{ticker}/spot-exposures
Per-minute aggregate spot exposures.
| Param | Type | Required | Default |
|---|---|---|---|
ticker (path) | string | yes | - |
date (query) | date | no | last trading date |
Response per row:
| Field | Description |
|---|---|
time | ISO 8601 timestamp (per-minute) |
price | Underlying spot price at snapshot |
gamma_per_one_percent_move_vol | Gamma exposure per 1% move (volume-weighted) |
gamma_per_one_percent_move_oi | Gamma exposure per 1% move (open-interest weighted) |
gamma_per_one_percent_move_dir | Gamma exposure per 1% move (directional / signed) |
vanna_per_one_percent_move_vol | Vanna per 1% move (vol) |
vanna_per_one_percent_move_oi | Vanna per 1% move (OI) |
vanna_per_one_percent_move_dir | Vanna per 1% move (dir) |
charm_per_one_percent_move_vol | Charm per 1% move (vol) |
charm_per_one_percent_move_oi | Charm per 1% move (OI) |
charm_per_one_percent_move_dir | Charm per 1% move (dir) |
Use in MK3: the regime gate becomes intraday-dynamic. Instead of
“net GEX from yesterday’s close” you have “net GEX one minute ago”.
The _dir variants are signed (long vs short dealer position), so
the negative-GEX trend gate can be re-evaluated minute by minute.
5.2 GET /api/stock/{ticker}/spot-exposures/expiry-strike (v2)
Strike × expiry cut of spot exposures with full filter set. Use this, not the deprecated v1.
| Param | Type | Required | Default |
|---|---|---|---|
ticker (path) | string | yes | - |
expirations[] (query, repeatable) | date | yes | - |
date (query) | date | no | last trading date |
limit (query) | int | no | 500 (max 500) |
page (query) | int | no | 0 |
min_strike (query) | number | no | - |
max_strike (query) | number | no | - |
min_dte (query) | int | no | - |
max_dte (query) | int | no | - |
Response per row: call and put variants of:
{delta,gamma,vanna,charm}_{ask,bid,oi,vol} (16 fields per side, 32
total) plus price, time, strike, expiry.
That _ask/_bid/_oi/_vol split is unique to spot-exposures
(Family 3) - the aggregate daily Greek exposure endpoints (Family 1)
do not provide it. It separates book (_oi), demand-paid (_ask),
supply-paid (_bid), and traded (_vol) exposure.
5.3 GET /api/stock/{ticker}/spot-exposures/strike
Strike-only cut, no expiry filter. Same field set as 5.2 minus the expiry breakout.
| Param | Type | Required | Default |
|---|---|---|---|
ticker (path) | string | yes | - |
date (query) | date | no | last trading date |
min_strike (query) | number | no | - |
max_strike (query) | number | no | - |
limit (query) | int | no | 500 (max 500) |
page (query) | int | no | 0 |
5.4 GET /api/stock/{ticker}/spot-exposures/{expiry}/strike (DEPRECATED)
V1 of the strike+expiry cut, marked deprecated in docs. Use 5.2
(/expiry-strike) instead. Listed here only so any future Codex
session that finds it in legacy code knows to migrate, not extend.
6. MK3 strategy mapping
How each family lands in Setup Hunter:
| Need | Endpoint | Cadence |
|---|---|---|
| Daily regime gate (negative GEX trend day) | 3.1 /greek-exposure (or 5.1 /spot-exposures for intraday refresh) | Once at open + minute refresh |
| OPEX framing | 3.2 /greek-exposure/expiry | Once daily |
| Gamma wall + pin level for 0DTE | 3.3 /greek-exposure/strike or 5.3 /spot-exposures/strike | Once at open, refresh on big moves |
| Strike-expiry book (where dealers flip from short to long gamma) | 3.4 /greek-exposure/strike-expiry or 5.2 /spot-exposures/expiry-strike | Once at open + on regime change |
| Aggressor flow (contrarian gate) | 4.1 /greek-flow | Every minute |
| 0DTE-isolated flow | 4.2 /greek-flow/{expiry} | Every minute |
| Intraday dynamic GEX (the live regime signal) | 5.1 /spot-exposures | Every minute |
The Setup Hunter spine (2026-05-15-mk3-setup-hunter-architecture) gates trades on regime first; this API surface is the substrate for that gate. The contrarian-aggressor finding (2026-05-22-uw-historical-findings) lives in family 2.
7. Nautilus integration shape (design notes, not code)
The UW REST surface fits Nautilus’s Actor + custom-data model
cleanly:
-
Custom data classes (one per endpoint family, defined with
@customdataclass):UWGreekExposure(family 1)UWGreekFlow(family 2)UWSpotExposure(family 3)
Each carries
ts_eventfrom the responsetime/timestampandts_initfrom the receive time. Numeric strings get coerced in__post_init__. -
Polling Actor (
UWGreeksActor) - background task on the Nautilus message bus that:- At session start: pulls daily exposure family (1.1-1.4).
- Every minute: pulls family 3 spot exposures and family 2 flow.
- Publishes each row to the MessageBus as the matching custom-data type.
-
Subscribers (Strategy / signal computations) consume from the bus via
subscribe_data(UWSpotExposure)and compute derived features (net_gex,dir_delta_flow_zscore, etc.). -
Determinism: timestamps go through
self.clockfor replay. Don’t read wall-clock in derived feature math. -
Adapter authoring contract:
concepts/nautilus-dev-adapters.md(the Adapter Authoring Bible). Phase 1-7 of that doc is the gate for landing this in MK3. Codex-only territory.
8. Known gaps + follow-ups
- Rate limits not on per-endpoint docs. Need to file
concepts/uw-api-overviewfrom the root docs page (auth, rate limits, response envelope conventions). - No WebSocket here. This page only covers REST. The streaming
side (live flow + spot exposures push) is a separate set of docs;
file under
concepts/uw-api-streamingwhen those URLs arrive. - Account / portfolio endpoints absent. Not relevant to MK3 at current scope.
- No historical bulk endpoint visible. The 1Y Parquet bundle is a separate data-shop product, not these REST endpoints.
9. Source URLs (verbatim, for re-fetch)
https://api.unusualwhales.com/docs/operations/PublicApi.TickerController.greek_exposurehttps://api.unusualwhales.com/docs/operations/PublicApi.TickerController.greek_exposure_by_expiryhttps://api.unusualwhales.com/docs/operations/PublicApi.TickerController.greek_exposure_by_strikehttps://api.unusualwhales.com/docs/operations/PublicApi.TickerController.greek_exposure_by_strike_expiryhttps://api.unusualwhales.com/docs/operations/PublicApi.TickerController.greek_flowhttps://api.unusualwhales.com/docs/operations/PublicApi.TickerController.greek_flow_expiryhttps://api.unusualwhales.com/docs/operations/PublicApi.TickerController.spot_exposures_one_minutehttps://api.unusualwhales.com/docs/operations/PublicApi.TickerController.spot_exposures_by_strike_expiry_v2https://api.unusualwhales.com/docs/operations/PublicApi.TickerController.spot_exposures_by_strikehttps://api.unusualwhales.com/docs/operations/PublicApi.TickerController.spot_exposures_by_strike_expiry
2026-05-22-uw-historical-findings 2026-05-15-mk3-setup-hunter-architecture 2026-05-15-mk3-data-foundation-constraint nautilus-dev-adapters nautilus-custom-data