Levels API
The same levels this site runs on, as an API: gamma walls, zero-gamma, pin candidates, expected moves and dark-pool levels for ES, NQ, SPX, NDX, SPY and QQQ. Futures levels are converted first-party from the index chains. JSON and CSV, refreshed after each US close and again pre-open.
Base URL https://api.tapelab.io · data last published 2026-06-13 00:20 UTC
Quick start
Authenticate with `X-API-Key` or `Authorization: Bearer` on every levels request. Keys are hand-issued during the beta — see keys & limits.
curl -s -H "X-API-Key: $TAPELAB_KEY" \ https://api.tapelab.io/v1/levels/ES | jq '.zeroGamma.price'
Endpoints
| Route | Auth | Returns |
|---|---|---|
GET /v1/health | none | Liveness + the timestamp of the last data publish. |
GET /v1/levels/{INSTRUMENT} | key | The instrument's full levels document (schema below). |
GET /v1/levels/{INSTRUMENT}.csv | key | The same levels as one flat CSV — one row per level. |
| Instrument | Kind | Derived from | Units |
|---|---|---|---|
| ES | futures | SPX chain + ES=F basis | ES points |
| NQ | futures | NDX chain + NQ=F basis | NQ points |
| SPX | cash index | SPX chain | index points |
| NDX | cash index | NDX chain | index points |
| SPY | ETF | SPY chain | dollars · + dark-pool levels |
| QQQ | ETF | QQQ chain | dollars · + dark-pool levels |
Response schema
Every level carries two values: `native` (the source chain's units) and `price` (the instrument's units — for ES/NQ that's native + basis). Levels are served unsnapped; snap to your instrument's tick on the client. `distPct` is distance from spot.
{
"version": 1,
"instrument": "ES",
"kind": "futures",
"units": "ES futures points",
"sourceSymbol": "SPX", // the option chain the levels derive from
"asOf": "2026-06-12", // market session
"generatedAt": "2026-06-12T22:41:24Z",
"freshness": "eod",
"conversion": { // futures only — null basis fields on cash/ETF
"method": "additive_basis",
"formula": "ES = SPX + basis",
"basis": 5.04, // same-session ES=F close − ^GSPC close
"basisDate": "2026-06-12",
"futuresSymbol": "ES=F",
"cashSymbol": "^GSPC"
},
"spot": { "native": 7431.46, "price": 7436.50 },
"zeroGamma": { "native": 7434.97, "price": 7440.01, "distPct": 0.05 },
"callWall": { "native": 7430.00, "price": 7435.04, "distPct": -0.02 },
"putWall": { "native": 7430.00, "price": 7435.04, "distPct": -0.02 },
"pinCandidates": [ // top strikes by |net GEX|, nearest expiry
{ "rank": 1, "native": 7430.0, "price": 7435.04, "netGexM": 312.4 }
],
"netGexM": 449.7, // $M, full chain
"netGexNearM": 197.2, // $M, nearest expiry
"gammaRegime": "long_gamma", // sign of net GEX: long_gamma | short_gamma
"gexHhi": 0.0175, // strike concentration (Herfindahl)
"expectedMove": { // ATM straddle, no haircut — each horizon:
"monthly": {
"expiry": "2026-06-18",
"straddleNative": 120.55,
"pct": 1.62,
"lower": { "native": 7310.91, "price": 7315.95 },
"upper": { "native": 7552.01, "price": 7557.05 }
}
// ... "nearest" and "quarterly" have the same shape
},
"darkpool": null // SPY/QQQ only: top-5 FINRA off-exchange
// volume-at-price levels since 2018
}The CSV twin flattens the same document — one row per level, `type` column keys the level kind:
instrument,as_of,type,rank,native,price,net_gex_m,dollars_b,date,pct ES,2026-06-12,basis,,,5.04,,,2026-06-12, ES,2026-06-12,spot,,7431.46,7436.5,,,, ES,2026-06-12,zero_gamma,,7434.97,7440.01,,,,0.05 ES,2026-06-12,call_wall,,7430.0,7435.04,,,,-0.02 ES,2026-06-12,put_wall,,7430.0,7435.04,,,,-0.02 ES,2026-06-12,em_monthly_lower,,7310.91,7315.95,,,2026-06-18,1.62 ES,2026-06-12,em_monthly_upper,,7552.01,7557.05,,,2026-06-18,1.62
Client snippets
Python:
import os, requests
KEY = os.environ["TAPELAB_KEY"]
r = requests.get(
"https://api.tapelab.io/v1/levels/NQ",
headers={"X-API-Key": KEY},
timeout=10,
)
r.raise_for_status()
levels = r.json()
print(levels["asOf"], levels["gammaRegime"])
print("zero-gamma:", levels["zeroGamma"]["price"]) # NQ futures points
print("call wall: ", levels["callWall"]["price"])
print("EM monthly:", levels["expectedMove"]["monthly"]["lower"]["price"],
"-", levels["expectedMove"]["monthly"]["upper"]["price"])NinjaTrader 8 (C#) — fetch once per session, not per tick:
// NinjaTrader 8 add-on / indicator (System.Net.Http)
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("X-API-Key",
Environment.GetEnvironmentVariable("TAPELAB_KEY"));
var json = await http.GetStringAsync(
"https://api.tapelab.io/v1/levels/ES");
// Newtonsoft ships with NT8:
var levels = Newtonsoft.Json.Linq.JObject.Parse(json);
double zeroGamma = (double)levels["zeroGamma"]["price"];
double callWall = (double)levels["callWall"]["price"];
// levels are UNSNAPPED — snap to the instrument's tick before drawing:
double tick = 0.25;
zeroGamma = Math.Round(zeroGamma / tick) * tick;Freshness & schedule
- Evening run— after the US close (published ~23:00–01:30 UTC): the session's full levels.
- Pre-open run — before the US open (published ~10:30–13:00 UTC): same session prices with overnight-settled open interest— the morning's fresh walls.
- Source data is CBOE delayed quotes (EOD cadence — this is not an intraday feed; a delayed-intraday tier is on the roadmap). Responses carry `asOf`, `generatedAt` and an
X-Data-Uploadedheader; the edge caches for 5 minutes.
Errors
All errors share one envelope: {"error": {"code": "...", "message": "..."}}
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_key / invalid_key | No key supplied, or the key is unknown. |
| 403 | key_revoked | The key exists but has been deactivated. |
| 404 | unknown_instrument | Not one of ES, NQ, SPX, NDX, SPY, QQQ. |
| 429 | rate_limited | Per-key request budget exceeded (default 60/min). |
| 503 | data_unavailable | The instrument's file isn't published yet — retry after the next pipeline run. |
Keys & limits
The API is in private beta: keys are hand-issued, free while the beta lasts, and rate-limited at 60 requests/minute (the data changes twice a day — you won't need more). Billing and self-serve keys come with the public release.