FlashAlpha Lab API Documentation: Options Exposure Analytics REST API
Lab API Overview & Auth

FlashAlpha Lab API: Real-Time Options Exposure Analytics

Real-time options exposure analytics. Live gamma (GEX), delta (DEX), vanna (VEX), and charm (CHEX) exposure data, key levels, dealer hedging estimates, and verbal narrative analysis.

Getting started? Free Options Data API walks through your first API call step by step. Also see: Python SDK Guide for SDK installation and examples.

Options Exposure REST API Overview

The FlashAlpha Lab API provides programmatic access to real-time options exposure analytics derived from live options flow. Build trading tools, dashboards, and research platforms on top of production-grade exposure data.

Base URL: https://lab.flashalpha.com

What you can build:

  • Real-time gamma exposure dashboards with support/resistance levels
  • Automated trading signals based on dealer positioning shifts
  • Options flow analytics platforms with narrative interpretation
  • Historical exposure trend analysis and regime detection

Symbols & coverage (incl. CME futures)

Per-symbol endpoints accept 6,000+ US equities and ETFs plus 21 CME futures contracts - equity index (ES=F, NQ=F, RTY=F, YM=F and the micros), metals (GC=F, SI=F), the Treasury curve (ZT=F through UB=F), grains (ZC=F, ZS=F, ZW=F, ZL=F, ZM=F) and crypto (BTC=F, ETH=F). Pass a futures symbol exactly as you would an equity, but URL-encode the = as %3D in the path:

# Gamma exposure on the E-mini S&P 500 future (note %3D for the '=')
curl -H "X-Api-Key: YOUR_API_KEY" \
  "https://lab.flashalpha.com/v1/exposure/gex/ES%3DF"

Options-on-futures are priced with Black-76 (forward-priced, carry q=r), and dollar exposure uses the CME contract multiplier - $50/point for ES, $20/point for NQ - not the 100× equity multiplier. The response schema is identical to equities, so existing code works by swapping the symbol. CME futures and flow analytics are Growth-tier. See the futures hub, the ES & NQ futures handbook, and the futures methodology.

Quick Start: Make Your First Options API Call

pip install flashalpha
GitHub

Make your first API call in under a minute. Create a free account to get your API key (no credit card required), then run:

# Free tier: single-expiry GEX on any US equity
curl -H "X-Api-Key: YOUR_API_KEY" \
  "https://lab.flashalpha.com/v1/exposure/gex/AAPL?expiration=2026-06-19"
import requests

API_KEY = "YOUR_API_KEY"
BASE    = "https://lab.flashalpha.com"
headers = {"X-Api-Key": API_KEY}

# 1. Single-expiry gamma exposure (Free tier, any equity)
gex = requests.get(
    f"{BASE}/v1/exposure/gex/AAPL?expiration=2026-06-19",
    headers=headers
).json()

print(f"Net GEX:    ${gex['net_gex']:,.0f}")
print(f"Gamma Flip: {gex['gamma_flip']}")

# 2. Key support/resistance levels (Free tier, any equity)
levels = requests.get(
    f"{BASE}/v1/exposure/levels/AAPL",
    headers=headers
).json()["levels"]

print(f"Call Wall:  {levels['call_wall']}")
print(f"Put Wall:   {levels['put_wall']}")

# Full regime + dealer-hedging summary is /v1/exposure/summary (Growth);
# ETF/index symbols and full-chain GEX start at Basic/Growth.
const API_KEY = "YOUR_API_KEY";
const BASE    = "https://lab.flashalpha.com";

const headers = { "X-Api-Key": API_KEY };

// 1. Single-expiry gamma exposure (Free tier, any equity)
const gex = await fetch(`${BASE}/v1/exposure/gex/AAPL?expiration=2026-06-19`, { headers })
  .then(r => r.json());

console.log(`Net GEX:    $${gex.net_gex.toLocaleString()}`);
console.log(`Gamma Flip: ${gex.gamma_flip}`);

// 2. Key support/resistance levels (Free tier, any equity)
const { levels } = await fetch(`${BASE}/v1/exposure/levels/AAPL`, { headers })
  .then(r => r.json());

console.log(`Call Wall:  ${levels.call_wall}`);
console.log(`Put Wall:   ${levels.put_wall}`);
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY");
var baseUrl = "https://lab.flashalpha.com";

// 1. Single-expiry gamma exposure (Free tier, any equity)
var gex = JsonDocument.Parse(
    await client.GetStringAsync($"{baseUrl}/v1/exposure/gex/AAPL?expiration=2026-06-19")
).RootElement;

Console.WriteLine($"Net GEX:    {gex.GetProperty("net_gex")}");
Console.WriteLine($"Gamma Flip: {gex.GetProperty("gamma_flip")}");

// 2. Key support/resistance levels (Free tier, any equity)
var levels = JsonDocument.Parse(
    await client.GetStringAsync($"{baseUrl}/v1/exposure/levels/AAPL")
).RootElement.GetProperty("levels");

Console.WriteLine($"Call Wall:  {levels.GetProperty("call_wall")}");
HttpClient client = HttpClient.newHttpClient();
String BASE = "https://lab.flashalpha.com";
String API_KEY = "YOUR_API_KEY";

// 1. Single-expiry gamma exposure (Free tier, any equity)
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create(BASE + "/v1/exposure/gex/AAPL?expiration=2026-06-19"))
    .header("X-Api-Key", API_KEY).build();

JsonObject gex = JsonParser.parseString(
    client.send(req, BodyHandlers.ofString()).body()
).getAsJsonObject();

System.out.println("Net GEX:    " + gex.get("net_gex"));
System.out.println("Gamma Flip: " + gex.get("gamma_flip"));
base := "https://lab.flashalpha.com"
apiKey := "YOUR_API_KEY"

// 1. Single-expiry gamma exposure (Free tier, any equity)
req, _ := http.NewRequest("GET", base+"/v1/exposure/gex/AAPL?expiration=2026-06-19", nil)
req.Header.Set("X-Api-Key", apiKey)
resp, _ := http.DefaultClient.Do(req)

var gex map[string]interface{}
json.NewDecoder(resp.Body).Decode(&gex)
resp.Body.Close()

fmt.Printf("Net GEX:    %v\n", gex["net_gex"])
fmt.Printf("Gamma Flip: %v\n", gex["gamma_flip"])

That's it. If you see JSON output with regime, net_gex, and gamma_flip fields, your API key is working. Explore the Exposure Analytics endpoints next.

API Key Authentication via X-Api-Key Header

Step 1: Create a free account. Sign up here - no credit card required. Your API key can be found on your profile page and will appear automatically in the examples below once you're logged in.

All endpoints except /health, /v1/surface, and unauthenticated (cached) /v1/stock/{symbol}/summary require an API key. Pass it in the X-Api-Key header (recommended):

curl -H "X-Api-Key: YOUR_API_KEY" https://lab.flashalpha.com/v1/symbols

Or as a query parameter (useful for quick browser testing):

https://lab.flashalpha.com/v1/symbols?apiKey=YOUR_API_KEY

Security tip: Always prefer the X-Api-Key header in production. Query parameters may be logged in server access logs, browser history, and proxy caches.

Here's a reusable setup for your projects:

import requests, os

API_KEY = os.environ["FLASHALPHA_API_KEY"]
BASE    = "https://lab.flashalpha.com"
HEADERS = {"X-Api-Key": API_KEY}

def fa_get(path, params=None):
    """Reusable helper for FlashAlpha API calls."""
    resp = requests.get(f"{BASE}{path}", headers=HEADERS, params=params)
    resp.raise_for_status()
    return resp.json()

# Usage
summary = fa_get("/v1/exposure/summary/SPY")
quote   = fa_get("/stockquote/SPY")
const API_KEY = process.env.FLASHALPHA_API_KEY;
const BASE    = "https://lab.flashalpha.com";
const HEADERS = { "X-Api-Key": API_KEY };

async function faGet(path, params = {}) {
  const url = new URL(`${BASE}${path}`);
  Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
  const resp = await fetch(url, { headers: HEADERS });
  if (!resp.ok) throw new Error(`${resp.status}: ${await resp.text()}`);
  return resp.json();
}

// Usage
const summary = await faGet("/v1/exposure/summary/SPY");
const quote   = await faGet("/stockquote/SPY");
using System.Text.Json;

var apiKey = Environment.GetEnvironmentVariable("FLASHALPHA_API_KEY");
var baseUrl = "https://lab.flashalpha.com";
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", apiKey);

async Task<JsonElement> FaGet(string path)
{
    var json = await client.GetStringAsync($"{baseUrl}{path}");
    return JsonDocument.Parse(json).RootElement;
}

// Usage
var summary = await FaGet("/v1/exposure/summary/SPY");
var quote   = await FaGet("/stockquote/SPY");
String API_KEY = System.getenv("FLASHALPHA_API_KEY");
String BASE = "https://lab.flashalpha.com";
HttpClient client = HttpClient.newHttpClient();

JsonObject faGet(String path) throws Exception {
    HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create(BASE + path))
        .header("X-Api-Key", API_KEY).build();
    String body = client.send(req, BodyHandlers.ofString()).body();
    return JsonParser.parseString(body).getAsJsonObject();
}

// Usage
JsonObject summary = faGet("/v1/exposure/summary/SPY");
JsonObject quote   = faGet("/stockquote/SPY");
apiKey := os.Getenv("FLASHALPHA_API_KEY")
base   := "https://lab.flashalpha.com"

func faGet(path string) (map[string]interface{}, error) {
    req, _ := http.NewRequest("GET", base+path, nil)
    req.Header.Set("X-Api-Key", apiKey)
    resp, err := http.DefaultClient.Do(req)
    if err != nil { return nil, err }
    defer resp.Body.Close()
    var result map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&result)
    return result, nil
}

// Usage
summary, _ := faGet("/v1/exposure/summary/SPY")
quote, _   := faGet("/stockquote/SPY")
# Set your API key
export FLASHALPHA_API_KEY="YOUR_API_KEY"

# Reusable function
fa_get() {
  curl -s -H "X-Api-Key: $FLASHALPHA_API_KEY" \
    "https://lab.flashalpha.com$1"
}

# Usage
fa_get /v1/exposure/summary/SPY | jq .
fa_get /stockquote/SPY | jq .

Need higher limits? The free tier includes 5 requests/day. Upgrade your plan for higher daily quotas and additional endpoints, or contact sales for enterprise access.

API Rate Limits and Throttling Policy

Every response includes rate limit headers:

Header Description
X-RateLimit-LimitMax requests per day (or unlimited)
X-RateLimit-RemainingRequests remaining today
X-RateLimit-ResetUnix timestamp when quota resets
Retry-AfterSeconds to wait (only on 429)

When you hit the daily quota, the API returns 429 with a Retry-After header. Here's how to handle it gracefully:

import time, requests

def fa_get_safe(path, params=None):
    resp = requests.get(f"{BASE}{path}", headers=HEADERS, params=params)

    # Check remaining quota
    remaining = resp.headers.get("X-RateLimit-Remaining")
    if remaining and int(remaining) < 10:
        print(f"Warning: only {remaining} requests remaining today")

    if resp.status_code == 429:
        retry_after = int(resp.headers.get("Retry-After", 60))
        print(f"Rate limited. Retrying in {retry_after}s...")
        time.sleep(retry_after)
        return fa_get_safe(path, params)  # retry once

    resp.raise_for_status()
    return resp.json()
async function faGetSafe(path, params = {}) {
  const url = new URL(`${BASE}${path}`);
  Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
  const resp = await fetch(url, { headers: HEADERS });

  // Check remaining quota
  const remaining = resp.headers.get("X-RateLimit-Remaining");
  if (remaining && parseInt(remaining) < 10) {
    console.warn(`Warning: only ${remaining} requests remaining today`);
  }

  if (resp.status === 429) {
    const retryAfter = parseInt(resp.headers.get("Retry-After") || "60");
    console.log(`Rate limited. Retrying in ${retryAfter}s...`);
    await new Promise(r => setTimeout(r, retryAfter * 1000));
    return faGetSafe(path, params); // retry once
  }

  if (!resp.ok) throw new Error(`${resp.status}: ${await resp.text()}`);
  return resp.json();
}
async Task<JsonElement> FaGetSafe(string path)
{
    var resp = await client.GetAsync($"{baseUrl}{path}");

    // Check remaining quota
    if (resp.Headers.TryGetValues("X-RateLimit-Remaining", out var vals))
    {
        var remaining = int.Parse(vals.First());
        if (remaining < 10)
            Console.WriteLine($"Warning: only {remaining} requests remaining today");
    }

    if (resp.StatusCode == (HttpStatusCode)429)
    {
        var retryAfter = int.Parse(
            resp.Headers.GetValues("Retry-After").FirstOrDefault() ?? "60");
        Console.WriteLine($"Rate limited. Retrying in {retryAfter}s...");
        await Task.Delay(retryAfter * 1000);
        return await FaGetSafe(path); // retry once
    }

    resp.EnsureSuccessStatusCode();
    var json = await resp.Content.ReadAsStringAsync();
    return JsonDocument.Parse(json).RootElement;
}
JsonObject faGetSafe(String path) throws Exception {
    HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create(BASE + path))
        .header("X-Api-Key", API_KEY).build();
    HttpResponse<String> resp = client.send(req, BodyHandlers.ofString());

    // Check remaining quota
    resp.headers().firstValue("X-RateLimit-Remaining").ifPresent(r -> {
        if (Integer.parseInt(r) < 10)
            System.out.println("Warning: only " + r + " requests remaining today");
    });

    if (resp.statusCode() == 429) {
        int retryAfter = Integer.parseInt(
            resp.headers().firstValue("Retry-After").orElse("60"));
        System.out.println("Rate limited. Retrying in " + retryAfter + "s...");
        Thread.sleep(retryAfter * 1000L);
        return faGetSafe(path); // retry once
    }

    return JsonParser.parseString(resp.body()).getAsJsonObject();
}
func faGetSafe(path string) (map[string]interface{}, error) {
    req, _ := http.NewRequest("GET", base+path, nil)
    req.Header.Set("X-Api-Key", apiKey)
    resp, err := http.DefaultClient.Do(req)
    if err != nil { return nil, err }
    defer resp.Body.Close()

    // Check remaining quota
    if r := resp.Header.Get("X-RateLimit-Remaining"); r != "" {
        if remaining, _ := strconv.Atoi(r); remaining < 10 {
            fmt.Printf("Warning: only %d requests remaining today\n", remaining)
        }
    }

    if resp.StatusCode == 429 {
        retryAfter, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
        if retryAfter == 0 { retryAfter = 60 }
        fmt.Printf("Rate limited. Retrying in %ds...\n", retryAfter)
        time.Sleep(time.Duration(retryAfter) * time.Second)
        return faGetSafe(path) // retry once
    }

    var result map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&result)
    return result, nil
}
# Check rate limit headers with verbose output
curl -v -H "X-Api-Key: YOUR_API_KEY" \
  "https://lab.flashalpha.com/v1/exposure/gex/SPY" 2>&1 | \
  grep -i "x-ratelimit\|retry-after"

# Retry after rate limit (bash)
resp=$(curl -s -w "\n%{http_code}" -H "X-Api-Key: YOUR_API_KEY" \
  "https://lab.flashalpha.com/v1/exposure/gex/SPY")
code=$(echo "$resp" | tail -1)
if [ "$code" = "429" ]; then
  echo "Rate limited  -  waiting 60s..."
  sleep 60
fi

API Plans, Pricing Tiers, and Request Quotas

Higher tiers unlock more daily requests and additional endpoints. Free covers single-expiry GEX, exposure levels, BSM Greeks, IV solver, stock quotes, tickers, options meta, symbols, and vol surface - for individual US equities only (e.g. AAPL, MSFT, TSLA). Basic adds ETFs (SPY, QQQ, IWM...) and index symbols (SPX, VIX, RUT...) on every endpoint, plus DEX/VEX/CHEX exposure by strike, max pain analysis, and access to the Market Overview page. Growth adds exposure summary, narrative, option quotes, volatility analytics, 0DTE analytics, Kelly sizing, full-chain GEX, and the live screener. Alpha adds advanced volatility (SVI surfaces), VRP analytics, historical exposure data, and unlimited requests.

Monthly Yearly Save 20%
Free
$0
forever free
  • 5 requests/day
  • Core endpoints
  • GEX & key levels (individual stocks only)
  • Community support
Get Started
Basic
$79/mo
  • 100 requests/day
  • ETFs & indexes (SPY, QQQ, SPX, VIX...)
  • DEX, VEX, CHEX & max pain
  • Market Overview page
  • Email support
Growth
$299/mo
  • 2,500 requests/day
  • Core endpoints
  • Exposure endpoints
  • Advanced endpoints
  • 0DTE analytics
  • Priority email support
Alpha
$1,499/mo
  • Unlimited requests
  • All endpoints incl. Raw + SVI
  • No 15s cache - real-time
  • Priority support (Discord) + 99.9% uptime SLA

Billing: Payments are processed securely via Stripe. You can upgrade, downgrade, or cancel anytime from your account page. Check your current usage with the GET /v1/account endpoint.

Available REST API Endpoints for Options Data

Market Data

  • GET /stockquote/{ticker} - Live stock quote
  • GET /optionquote/{ticker} - Option quotes with greeks (Growth+)
  • GET /v1/options/{ticker} - Option chain metadata (expirations + strikes)
  • GET /v1/stock/{symbol}/summary - Comprehensive stock summary (price, vol, exposure, macro) (public cached / Free+ live)
  • GET /v1/surface/{symbol} - Vol surface grid (public, no auth)

Exposure Analytics (settled OI)

  • GET /v1/exposure/gex/{symbol} - Gamma exposure by strike (Free for individual stocks; Basic+ for ETFs & indexes)
  • GET /v1/exposure/dex/{symbol} - Delta exposure by strike (Basic+)
  • GET /v1/exposure/vex/{symbol} - Vanna exposure by strike (Basic+)
  • GET /v1/exposure/chex/{symbol} - Charm exposure by strike (Basic+)
  • GET /v1/maxpain/{symbol} - Max pain analysis (pain curve, pin probability, dealer alignment) (Basic+)
  • GET /v1/exposure/summary/{symbol} - Full exposure summary (Growth+)
  • GET /v1/exposure/levels/{symbol} - Key support/resistance levels (Free for individual stocks; Basic+ for ETFs & indexes)
  • GET /v1/exposure/narrative/{symbol} - Verbal narrative analysis (Growth+)
  • GET /v1/exposure/zero-dte/{symbol} - 0DTE analytics: pin risk, expected move, gamma acceleration (Growth+)

Flow Analytics (simulation-aware exposure on effective OI)

Same metrics as /v1/exposure/* but recomputed on the OI simulator's effective open interest (settled + intraday-flow estimate). Every endpoint accepts an optional ?expiry=YYYY-MM-DD filter.

  • GET /v1/flow/levels/{symbol} - Live gamma flip + call/put walls + max pain (Growth+)
  • GET /v1/flow/pin-risk/{symbol} - Live pin score with OI/proximity/time/gamma sub-scores (weighted 30/25/25/20) (Growth+)
  • GET /v1/flow/summary/{symbol} - Watchlist-cheap headline: direction, intraday OI delta, live GEX, % shift (Growth+)
  • GET /v1/flow/gex/{symbol} - Live net GEX + per-strike profile on effective OI (Growth+)
  • GET /v1/flow/dex/{symbol} - Live net DEX + per-strike profile on effective OI (Growth+)
  • GET /v1/flow/dealer-risk/{symbol} - Settled vs live dealer GEX/DEX delta with direction classifier (Growth+)
  • GET /v1/flow/oi/{symbol} - Raw OI simulator state (model input) (Alpha+)
  • GET /v1/flow/live/{symbol} - Headline flow bundle in one call (Alpha+)
  • GET /v1/flow/signals/{symbol} - Scored, classified unusual-flow feed (sweep/block, opening bias, intent) (Alpha+)
  • GET /v1/flow/signals/{symbol}/summary - Net bullish/bearish + opening/closing premium roll-up (Alpha+)

Raw Flow Data (trade tape proxy, camelCase response fields, Alpha+)

  • GET /v1/flow/options/{symbol}/{recent,summary,blocks,history,cumulative} - Per-symbol option trade flow: recent trades, totals, blocks, minute history, cumulative net (Alpha+)
  • GET /v1/flow/stocks/{symbol}/{recent,summary,blocks,history,cumulative} - Per-symbol stock trade flow (same shape as options) (Alpha+)
  • GET /v1/flow/options/{leaderboard,outliers} - Cross-symbol option-flow ranking and outlier scan (cached 30s) (Alpha+)
  • GET /v1/flow/stocks/{leaderboard,outliers} - Cross-symbol stock-flow ranking and outlier scan (Alpha+)

Volatility & VRP Analytics

  • GET /v1/volatility/{symbol} - Comprehensive volatility analysis (realized vol, skew, hedging scenarios, liquidity) (Growth+)
  • GET /v1/adv_volatility/{symbol} - Advanced: SVI parameters, variance surface, arbitrage detection, greeks surfaces, variance swap pricing (Alpha)
  • GET /v1/vrp/{symbol} - VRP dashboard: z-score, percentile, directional VRP, GEX-conditioned regime, strategy scores, dealer risk, macro context (Alpha)
  • GET /v1/vrp/{symbol}/history - Daily VRP time series for charting and backtesting (Alpha)

Pricing & Sizing

  • GET /v1/pricing/greeks - Full BSM greeks from inputs (first, second, third order)
  • GET /v1/pricing/iv - Implied volatility solver (Newton-Raphson)
  • GET /v1/pricing/kelly - Kelly criterion position sizing for options (Growth+)

Account & System

  • GET /v1/account - Account info
  • GET /v1/symbols - Tracked symbols
  • GET /v1/tickers - Available tickers
  • GET /health - Health check (public, no auth)

Response Envelope: endpoint_version and data_as_of

Every successful JSON response carries two fields, added by the API rather than by each endpoint. endpoint_version identifies the deployment that produced the response. data_as_of reports when each upstream market-data feed last delivered to the node that answered you.

data_as_of is a fixed object: every key is present on every endpoint, and a key is null when that node has not received anything on that feed since it started.

{
  "symbol": "SPY",
  "underlying_price": 764.23,
  "as_of": "2026-08-25T18:49:00.697Z",
  "endpoint_version": "2026.08.25",
  "data_as_of": {
    "node": "fa2",
    "equity_feed": "2026-08-25T18:49:00.512Z",
    "equity_options_feed": "2026-08-25T18:48:58.204Z",
    "index_feed": null,
    "index_options_feed": null,
    "futures_feed": null,
    "futures_options_feed": null,
    "flow_feed": "2026-08-25T18:49:00.220Z",
    "oi_feed": "2026-08-21T20:00:00.000Z",
    "macro_feed": "2026-08-24T18:42:11.000Z"
  }
}

What each key reports

KeyFeedExpected cadence
nodeWhich node answeredNodes hydrate independently, so their feeds can differ
equity_feedEquity and ETF spot quotesSeconds, during market hours
equity_options_feedEquity and ETF option quotesSeconds, during market hours
index_feedIndex spot (SPX, NDX, RUT, VIX)Seconds, during market hours
index_options_feedIndex option quotesSeconds, during market hours
futures_feedFutures pricesSeconds, during the futures session
futures_options_feedFutures option quotesSeconds, during the futures session
flow_feedClassified options and stock trade tapeSeconds, during market hours
oi_feedSettled open interestDaily, dated to the prior 16:00 ET close
macro_feedVIX, VVIX, SKEW, MOVE, SPX, Fear & GreedMinutes. Reports its OLDEST component, so one lagging series stays visible

How to read it

  • Check the feeds your query depends on. A GEX call on SPY is answered from equity_feed, equity_options_feed and oi_feed. futures_feed being null in that response says nothing about the answer.
  • Compare against the cadence, not the clock. oi_feed at the previous session's close is correct - settled open interest is published once per session. In the example above it sits three days behind as_of because the request ran on a Monday and Friday was the last session to settle; that is right, not stale. equity_options_feed an hour behind during market hours, by contrast, is not.
  • null means "not seen on this node", not "broken". A node that has never been asked for a futures symbol has never opened that feed.
  • Spot and options are separate on purpose. They arrive over different pipes and can fail independently, so an index chain can be current while index spot is not.
  • It reports feed activity, not per-contract freshness. A timestamp shows the feed delivered recently. It does not assert that every contract in a chain is equally current - an illiquid strike may not have quoted for hours while the feed itself is healthy.
  • data_as_of is not as_of. as_of, where an endpoint returns it, is when the response was generated or when the newest contract in it last ticked. data_as_of describes the feeds behind it.

Endpoints that return a bare JSON array carry the same information in the X-Endpoint-Version and X-Data-As-Of response headers, the latter as compact JSON.

On the Historical API, data_as_of means the same thing - live feed state - and every slot is null, because a historical node reads the archive rather than any live feed. The vintage of the archive rows replayed for your at is published alongside as archive_as_of, in the same shape.

HTTP Error Codes and Error Response Format

The API returns consistent JSON error responses. Errors include a machine-readable error field and a human-readable message or detail.

401 Unauthorized

{
  "title": "Unauthorized",
  "status": 401,
  "detail": "Invalid API key."
}

404 Not Found

{
  "error": "symbol_not_found",
  "message": "No data for XYZ."
}

403 Forbidden (Tier Restricted)

{
  "status": "ERROR",
  "error": "tier_restricted",
  "message": "This endpoint requires a Growth plan or higher.",
  "current_plan": "Free",
  "required_plan": "Growth"
}

429 Too Many Requests

{
  "status": "ERROR",
  "error": "Quota exceeded",
  "message": "You have exceeded your daily API quota of 5 requests on the Free plan.",
  "current_plan": "Free",
  "limit": 5,
  "upgrade_to": "Basic",
  "reset_at": "2026-03-06T00:00:00Z"
}

Handling Errors in Code

resp = requests.get(f"{BASE}/v1/exposure/gex/INVALID", headers=HEADERS)

if resp.status_code == 200:
    data = resp.json()
elif resp.status_code == 401:
    print("Bad API key  -  check your X-Api-Key header")
elif resp.status_code == 403:
    err = resp.json()
    print(f"Tier restricted: {err['message']}")
    print(f"Current plan: {err['current_plan']}, required: {err['required_plan']}")
elif resp.status_code == 404:
    err = resp.json()
    print(f"Not found: {err['message']}")  # "No data for INVALID."
elif resp.status_code == 429:
    err = resp.json()
    print(f"Quota exceeded on {err['current_plan']} plan")
    print(f"Resets at: {err['reset_at']}")
else:
    print(f"Unexpected error: {resp.status_code}")
const resp = await fetch(`${BASE}/v1/exposure/gex/INVALID`, { headers: HEADERS });

switch (resp.status) {
  case 200:
    const data = await resp.json();
    break;
  case 401:
    console.error("Bad API key  -  check your X-Api-Key header");
    break;
  case 403:
    const err403 = await resp.json();
    console.error(`Tier restricted: ${err403.message}`);
    console.error(`Current: ${err403.current_plan}, required: ${err403.required_plan}`);
    break;
  case 404:
    const err404 = await resp.json();
    console.error(`Not found: ${err404.message}`);
    break;
  case 429:
    const err429 = await resp.json();
    console.error(`Quota exceeded on ${err429.current_plan} plan`);
    console.error(`Resets at: ${err429.reset_at}`);
    break;
  default:
    console.error(`Unexpected error: ${resp.status}`);
}
var resp = await client.GetAsync($"{baseUrl}/v1/exposure/gex/INVALID");
var body = await resp.Content.ReadAsStringAsync();

switch ((int)resp.StatusCode)
{
    case 200:
        var data = JsonDocument.Parse(body).RootElement;
        break;
    case 401:
        Console.WriteLine("Bad API key  -  check your X-Api-Key header");
        break;
    case 403:
        var err403 = JsonDocument.Parse(body).RootElement;
        Console.WriteLine($"Tier restricted: {err403.GetProperty("message")}");
        Console.WriteLine($"Current: {err403.GetProperty("current_plan")}, required: {err403.GetProperty("required_plan")}");
        break;
    case 404:
        var err404 = JsonDocument.Parse(body).RootElement;
        Console.WriteLine($"Not found: {err404.GetProperty("message")}");
        break;
    case 429:
        var err429 = JsonDocument.Parse(body).RootElement;
        Console.WriteLine($"Quota exceeded on {err429.GetProperty("current_plan")} plan");
        Console.WriteLine($"Resets at: {err429.GetProperty("reset_at")}");
        break;
    default:
        Console.WriteLine($"Unexpected error: {(int)resp.StatusCode}");
        break;
}
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create(BASE + "/v1/exposure/gex/INVALID"))
    .header("X-Api-Key", API_KEY).build();
HttpResponse<String> resp = client.send(req, BodyHandlers.ofString());

switch (resp.statusCode()) {
    case 200:
        JsonObject data = JsonParser.parseString(resp.body()).getAsJsonObject();
        break;
    case 401:
        System.out.println("Bad API key  -  check your X-Api-Key header");
        break;
    case 403:
        JsonObject err403 = JsonParser.parseString(resp.body()).getAsJsonObject();
        System.out.println("Tier restricted: " + err403.get("message"));
        break;
    case 404:
        JsonObject err404 = JsonParser.parseString(resp.body()).getAsJsonObject();
        System.out.println("Not found: " + err404.get("message"));
        break;
    case 429:
        JsonObject err429 = JsonParser.parseString(resp.body()).getAsJsonObject();
        System.out.println("Quota exceeded on " + err429.get("current_plan") + " plan");
        break;
    default:
        System.out.println("Unexpected error: " + resp.statusCode());
}
req, _ := http.NewRequest("GET", base+"/v1/exposure/gex/INVALID", nil)
req.Header.Set("X-Api-Key", apiKey)
resp, _ := http.DefaultClient.Do(req)
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()

switch resp.StatusCode {
case 200:
    var data map[string]interface{}
    json.Unmarshal(body, &data)
case 401:
    fmt.Println("Bad API key  -  check your X-Api-Key header")
case 403:
    var err map[string]interface{}
    json.Unmarshal(body, &err)
    fmt.Printf("Tier restricted: %v\n", err["message"])
case 404:
    var err map[string]interface{}
    json.Unmarshal(body, &err)
    fmt.Printf("Not found: %v\n", err["message"])
case 429:
    var err map[string]interface{}
    json.Unmarshal(body, &err)
    fmt.Printf("Quota exceeded on %v plan\n", err["current_plan"])
default:
    fmt.Printf("Unexpected error: %d\n", resp.StatusCode)
}
# Check HTTP status code with curl
resp=$(curl -s -o /dev/null -w "%{http_code}" \
  -H "X-Api-Key: YOUR_API_KEY" \
  "https://lab.flashalpha.com/v1/exposure/gex/INVALID")

echo "Status: $resp"

# Get full error body
curl -s -H "X-Api-Key: YOUR_API_KEY" \
  "https://lab.flashalpha.com/v1/exposure/gex/INVALID" | jq .

API Conventions: JSON Format, Dates, and Pagination

  • All timestamps are UTC in ISO 8601 format
  • All exposure values are in USD notional
  • GEX formula: gamma × OI × 100 × spot² × 0.01
  • Greeks are calculated via Black-Scholes-Merton (BSM) - not sourced from vendor
  • Implied volatility is derived from BSM inversion. The svi_vol field (SVI-smoothed IV) requires the Alpha plan - non-Alpha plans receive "REQUIRES_ALPHA_TIER".
  • Any US equity is supported on Free; ETFs (SPY, QQQ, IWM...) and index symbols (SPX, VIX, RUT...) require Basic+
  • Responses are cached per plan: 15 minutes on Free, 15 seconds on Basic/Growth, near-real-time (~1s) on Alpha
  • Dealer position is the opposite of net exposure (dealers are counterparty)
  • OI changes are day-over-day deltas

Ready to build?

Get your free API key and start pulling live options data in 30 seconds.