Live Options Screener & Scanner API — Real-Time Filter, Rank, and Score Across 250 Symbols | FlashAlpha

Live Options Screener & Scanner API — Real-Time Filter, Rank, and Score Across 250 Symbols

How the FlashAlpha Live Screener API works: one POST endpoint, a recursive filter tree, cascading expiry/strike/contract filters, custom formulas, and pre-computed strategy scores. Filter 250 symbols by GEX regime, VRP, skew, dealer flow risk, and contract-level greeks — with data refreshed every 5-10 seconds from an in-memory store. Complete guide with worked examples.

T
Tomasz Dobrowolski Quant Engineer
Apr 5, 2026
14 min read
OptionsScreener LiveScreener GEX VRP DealerFlowRisk API FlashAlpha OptionsAnalytics

If you’re building an options trading tool, screening is usually the first thing that gets nasty. You have a universe of tickers, a handful of interesting metrics (IV, GEX, VRP, skew, open interest), and you want to rank them in real time. Doing that yourself means millions of option contracts per day, IV solvers, surface fitting, a scheduler, a database — and then every time a trader says “can you also filter by dealer flow risk?”, you rebuild half of it.

The Live Screener API at POST /v1/screener gives you that entire layer as one request. You send a filter tree, a sort spec, and a list of fields you want back. The server has already pre-computed every metric for ~250 symbols and refreshes them every 5–10 seconds from an in-memory store. There’s no database query at request time and no warm-up lag.

This article explains the data model, the filter grammar, cascading semantics, and formulas — with enough worked examples that you can wire up a screener in an afternoon.

Table of contents

Why one POST endpoint is better than a dozen GETs

If you’ve ever integrated with an options API, the usual pattern is “one endpoint per ticker, loop over your watchlist, join in your client.” That works fine for 10 symbols, but it has two problems:

  • N round trips. You pay the network cost N times. For a 250-symbol scan, that’s 250 HTTP requests and whatever the p99 latency of each one happens to be.
  • You do the joining. You call /exposure/summary, /volatility, /vrp, merge them in memory, and only then can you rank. The filter logic lives in your client.

A POST screener inverts both. The server already has every symbol hydrated in memory, so there’s no per-symbol query. And because you send the filter tree as JSON, the server does the join, the filter, the sort, and the pagination. One request in, a ranked page out.

Concretely, a request looks like this:

{
  "filters":  { ... filter tree ... },
  "sort":     [ ... sort specs ... ],
  "select":   [ ... field names ... ],
  "formulas": [ ... custom computed fields ... ],
  "limit":    50,
  "offset":   0
}

Every key is optional. An empty body {} returns every symbol in your plan’s universe with a default field set.

The four-level data model

Every symbol is modelled as a nested tree, and the filter grammar lets you address each level directly.

Stock (1 per symbol)
 └─ Expiry aggregates (many per symbol)
     └─ Strike aggregates (many per expiry)
         └─ Contracts (2 per strike: C and P)

Each level has its own filterable fields, accessed via a dotted prefix:

  • regime, net_gex, atm_iv, vrp_20d — stock-level scalars (no prefix)
  • expiries.days_to_expiry, expiries.atm_iv — per-expiry
  • strikes.call_oi, strikes.net_gex — per-strike inside each expiry
  • contracts.type, contracts.delta, contracts.oi — individual calls/puts

This is important because it lets you express things like “positive-gamma symbols, only expiries within 14 days, only strikes with call_oi ≥ 2000, only the 30–50 delta call contracts” in a single query. Which brings us to cascading.

Filter grammar: AND, OR, leaves

Filters form a recursive tree. A node is either a leaf (a single condition) or a group (AND / OR with child conditions):

// Leaf
{ "field": "atm_iv", "operator": "gte", "value": 20 }

// Group
{
  "op": "and",
  "conditions": [
    { "field": "regime", "operator": "eq",  "value": "positive_gamma" },
    { "field": "atm_iv", "operator": "gte", "value": 15 }
  ]
}

Groups nest up to 3 levels deep, with a max of 20 total leaf conditions per query. That’s enough to express basically any reasonable scan, and the ceiling keeps pathological filters from turning the screener into a DDOS surface.

The operators are the ones you’d expect: eq, neq, gt, gte, lt, lte, between (for ranges), in (for lists), is_null, is_not_null. String comparisons are case-insensitive.

Cascading filters and why they matter

Here’s the feature that makes the screener pull its weight: when you combine stock-level + expiry-level + strike-level + contract-level filters inside an AND group, the filter cascades. Non-matching children get trimmed at each level, and symbols with zero survivors are dropped.

{
  "filters": {
    "op": "and",
    "conditions": [
      { "field": "regime",                   "operator": "eq",  "value": "positive_gamma" },
      { "field": "expiries.days_to_expiry",  "operator": "lte", "value": 14 },
      { "field": "strikes.call_oi",          "operator": "gte", "value": 2000 },
      { "field": "contracts.type",           "operator": "eq",  "value": "C" },
      { "field": "contracts.delta",          "operator": "gte", "value": 0.3 }
    ]
  },
  "select": ["*"],
  "limit": 20
}

That single query returns:

  • Only the symbols where regime = positive_gamma
  • Only their expiries where DTE ≤ 14
  • Only the strikes inside those expiries with call_oi ≥ 2000
  • Only the call contracts at those strikes with delta ≥ 0.3

Without cascading you’d pull every symbol, every expiry, every strike, every contract, and filter in your client. Cascading shifts the entire winnowing process server-side, and the response that comes back is already shaped like the answer.

OR groups behave differently. Nested filters inside an OR use Any() semantics — contracts.delta > 0.3 matches if any contract in the symbol clears the bar. OR does not cascade; the full data comes back for matching symbols.

Formulas: your own computed fields

If the field you want isn’t in the catalog, you can define it inline. Formulas are arithmetic expressions over numeric fields:

{
  "formulas": [
    { "alias": "vrp_ratio", "expression": "atm_iv / rv_20d" },
    { "alias": "risk_adj",  "expression": "harvest_score / (dealer_flow_risk + 1)" }
  ],
  "sort":   [{ "formula": "risk_adj", "direction": "desc" }],
  "select": ["symbol", "harvest_score", "dealer_flow_risk", "risk_adj"]
}

Supported: + - * /, parentheses, unary negation, numeric literals, any numeric field name. Max expression length 200 characters. Division by zero returns null, and null inputs propagate through the expression so you never get a surprise 0.

You can also use formulas inline without a formulas array:

{
  "filters": {
    "op": "and",
    "conditions": [
      { "field": "regime", "operator": "eq", "value": "positive_gamma" },
      { "formula": "atm_iv - rv_20d", "operator": "gt", "value": 6 }
    ]
  }
}

Formulas are Alpha-tier only. Growth tier requests with a formulas array throw validation_error.

Pre-computed strategy scores

This is the part that beats writing your own scoring. For every symbol, Alpha-tier responses include a handful of 0–100 strategy scores that are pre-computed every 5–10 seconds:

  • harvest_score — “is it safe to sell premium here, right now?”. High = rich VRP with friendly dealer regime.
  • net_harvest_score — harvest score penalized by dealer flow risk.
  • dealer_flow_risk — estimate of how aggressively dealers will hedge against you.
  • short_put_spread_score, short_strangle_score, iron_condor_score, calendar_spread_score — strategy-specific attractiveness.

These are the scoring signals that most in-house scanners bolt together manually — rolling z-scores, regime detectors, concentration metrics, skew slopes — and they’re available as fields you can filter and sort on directly.

Alongside the scores, the vrp_regime classifier tags each symbol as harvestable, toxic_short_vol, cheap_convexity, event_only, or surface_distorted — which lets you write scans like “only sell premium when the regime is harvestable, never when it’s toxic_short_vol.”

Five worked examples

1. Negative-gamma alert board

Every morning you want a short list of symbols where dealers are short gamma and already exposed:

{
  "filters": { "op": "and", "conditions": [
    { "field": "regime",           "operator": "eq",  "value": "negative_gamma" },
    { "field": "dealer_flow_risk", "operator": "gte", "value": 50 }
  ]},
  "sort":   [{ "field": "dealer_flow_risk", "direction": "desc" }],
  "select": ["symbol", "regime", "dealer_flow_risk", "gamma_flip", "net_gex"]
}

2. Harvestable VRP short list

{
  "filters": { "op": "and", "conditions": [
    { "field": "regime",           "operator": "eq",  "value": "positive_gamma" },
    { "field": "vrp_regime",       "operator": "eq",  "value": "harvestable" },
    { "field": "dealer_flow_risk", "operator": "lte", "value": 40 },
    { "field": "harvest_score",    "operator": "gte", "value": 65 }
  ]},
  "sort":   [{ "field": "harvest_score", "direction": "desc" }],
  "select": ["symbol", "price", "harvest_score", "dealer_flow_risk", "vrp_regime"]
}

3. Macro-conditioned regime scan

“Show me negative-gamma symbols but only when VIX is elevated.”

{
  "filters": { "op": "and", "conditions": [
    { "field": "regime", "operator": "eq",  "value": "negative_gamma" },
    { "field": "vix",    "operator": "gte", "value": 20 }
  ]},
  "select": ["symbol", "regime", "atm_iv", "vix"]
}

4. 0DTE call-seller setup (cascading)

{
  "filters": { "op": "and", "conditions": [
    { "field": "expiries.days_to_expiry", "operator": "eq",  "value": 0 },
    { "field": "contracts.type",          "operator": "eq",  "value": "C" },
    { "field": "contracts.delta",         "operator": "gte", "value": 0.3 },
    { "field": "contracts.oi",            "operator": "gte", "value": 1000 }
  ]},
  "select": ["*"]
}

You get back symbols where, for each symbol, only the 0DTE expiry is included, only the call contracts that clear the delta and OI thresholds are kept. No post-processing needed.

5. Risk-adjusted ranking (formula)

{
  "formulas": [
    { "alias": "risk_adj", "expression": "harvest_score / (dealer_flow_risk + 1)" }
  ],
  "filters": { "field": "harvest_score", "operator": "gte", "value": 50 },
  "sort":    [{ "formula": "risk_adj", "direction": "desc" }],
  "select":  ["symbol", "price", "harvest_score", "dealer_flow_risk", "risk_adj"],
  "limit":   20
}

The +1 keeps the denominator away from zero. This ranks symbols by “how much edge per unit of dealer risk” — a single, defensible ranking axis that combines the scoring and the risk penalty.

When to use the screener (vs the per-symbol endpoints)

The screener isn’t a replacement for the detail endpoints — each serves a different job:

  • Screener: “which symbols?” — ranking, filtering, regime detection across the universe.
  • Per-symbol endpoints (GEX, VRP, Narrative): “now tell me everything about this one” — per-strike data, surface parameters, narrative, historical context.

A typical workflow is: screener trims your universe of ~250 to 5–10 candidates, then you drill into each one with the detail endpoints for position sizing and entry timing.

Getting started

The screener is live at POST https://lab.flashalpha.com/v1/screener. Pass your API key in the X-Api-Key header:

curl -X POST "https://lab.flashalpha.com/v1/screener" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": { "field": "regime", "operator": "eq", "value": "negative_gamma" },
    "sort":    [{ "field": "atm_iv", "direction": "desc" }],
    "select":  ["symbol", "price", "regime", "atm_iv", "net_gex"],
    "limit":   10
  }'

For the complete field reference (every level, every tier, every operator), see the Live Screener docs and the Field Taxonomy. For copy-paste recipes, see the Screener Cookbook.

Ready to build with it?

The Live Screener is part of the FlashAlpha Lab API — Growth for a 20-symbol universe, Alpha for ~250 symbols with formulas and strategy scores. Start with the free tier (5 requests/day) and upgrade when you’re ready to scan.

Get an API key → Compare plans →

Live Market Pulse

Get tick-by-tick visibility into market shifts with full-chain analytics streaming in real time.

Intelligent Screening

Screen millions of option pairs per second using your custom EV rules, filters, and setups.

Execution-Ready

Instantly send structured orders to Interactive Brokers right from your scan results.

Join the Community

Discord

Engage in real time conversations with us!

Twitter / X

Follow us for real-time updates and insights!

GitHub

Explore our open-source SDK, examples, and analytics resources!