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.
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
Quick Start: Make Your First Options API Call
Make your first API call in under a minute. Create a free account to get your API key (no credit card required), then run:
# Fetch SPY exposure summary
curl -H "X-Api-Key: YOUR_API_KEY" \
https://lab.flashalpha.com/v1/exposure/summary/SPY
import requests
API_KEY = "YOUR_API_KEY"
BASE = "https://lab.flashalpha.com"
# 1. Check gamma regime
summary = requests.get(
f"{BASE}/v1/exposure/summary/SPY",
headers={"X-Api-Key": API_KEY}
).json()
print(f"Regime: {summary['regime']}")
print(f"Net GEX: ${summary['exposures']['net_gex']:,.0f}")
print(f"Gamma Flip: {summary['gamma_flip']}")
# 2. Get key support/resistance levels
levels = requests.get(
f"{BASE}/v1/exposure/levels/SPY",
headers={"X-Api-Key": API_KEY}
).json()["levels"]
print(f"Call Wall: {levels['call_wall']}")
print(f"Put Wall: {levels['put_wall']}")
print(f"0DTE Magnet: {levels['zero_dte_magnet']}")
const API_KEY = "YOUR_API_KEY";
const BASE = "https://lab.flashalpha.com";
const headers = { "X-Api-Key": API_KEY };
// 1. Check gamma regime
const summary = await fetch(`${BASE}/v1/exposure/summary/SPY`, { headers })
.then(r => r.json());
console.log(`Regime: ${summary.regime}`);
console.log(`Net GEX: $${summary.exposures.net_gex.toLocaleString()}`);
console.log(`Gamma Flip: ${summary.gamma_flip}`);
// 2. Get key support/resistance levels
const { levels } = await fetch(`${BASE}/v1/exposure/levels/SPY`, { 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. Check gamma regime
var summary = JsonDocument.Parse(
await client.GetStringAsync($"{baseUrl}/v1/exposure/summary/SPY")
).RootElement;
Console.WriteLine($"Regime: {summary.GetProperty("regime")}");
Console.WriteLine($"Net GEX: {summary.GetProperty("exposures").GetProperty("net_gex")}");
Console.WriteLine($"Gamma Flip: {summary.GetProperty("gamma_flip")}");
// 2. Get key support/resistance levels
var levels = JsonDocument.Parse(
await client.GetStringAsync($"{baseUrl}/v1/exposure/levels/SPY")
).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. Check gamma regime
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(BASE + "/v1/exposure/summary/SPY"))
.header("X-Api-Key", API_KEY).build();
JsonObject summary = JsonParser.parseString(
client.send(req, BodyHandlers.ofString()).body()
).getAsJsonObject();
System.out.println("Regime: " + summary.get("regime"));
System.out.println("Net GEX: " + summary.getAsJsonObject("exposures").get("net_gex"));
System.out.println("Gamma Flip: " + summary.get("gamma_flip"));
base := "https://lab.flashalpha.com"
apiKey := "YOUR_API_KEY"
// 1. Check gamma regime
req, _ := http.NewRequest("GET", base+"/v1/exposure/summary/SPY", nil)
req.Header.Set("X-Api-Key", apiKey)
resp, _ := http.DefaultClient.Do(req)
var summary map[string]interface{}
json.NewDecoder(resp.Body).Decode(&summary)
resp.Body.Close()
fmt.Printf("Regime: %v\n", summary["regime"])
exposures := summary["exposures"].(map[string]interface{})
fmt.Printf("Net GEX: %v\n", exposures["net_gex"])
fmt.Printf("Gamma Flip: %v\n", summary["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) 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-Limit | Max requests per day (or unlimited) |
X-RateLimit-Remaining | Requests remaining today |
X-RateLimit-Reset | Unix timestamp when quota resets |
Retry-After | Seconds 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.
- 5 requests/day
- Core endpoints
- GEX & key levels (individual stocks only)
- Community support
- 100 requests/day
- ETFs & indexes (SPY, QQQ, SPX, VIX...)
- DEX, VEX, CHEX & max pain
- Market Overview page
- Email support
- 2,500 requests/day
- Core endpoints
- Exposure endpoints
- Advanced endpoints
- 0DTE analytics
- Priority email support
- Unlimited requests
- All endpoints incl. Raw + SVI
- No 15s cache - real-time
- Dedicated support + 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 quoteGET /optionquote/{ticker}- Option quotes with greeks (Growth+)GET /v1/options/{ticker}- Option chain metadata (expirations + strikes)GET /v1/surface/{symbol}- Vol surface grid (public, no auth)
Exposure Analytics
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/history/{symbol}- Daily exposure history (Alpha+) coming soon
Volatility 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)
Account & System
GET /v1/account- Account infoGET /v1/symbols- Tracked symbolsGET /v1/tickers- Available tickersGET /health- Health check (public, no auth)
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_volfield (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 for 15 seconds (Alpha plan bypasses cache)
- 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.