Bullflow API
Build with options flow, gamma exposure (GEX), dark pool trades, and streaming alerts through Bullflow’s JSON data endpoints and Server-Sent Events (SSE) streams. Explore endpoint parameters, response formats, code examples, and historical alert replay. Supply a valid Bullflow API key via the key query parameter where required.
Base URL: https://api.bullflow.io
Transports: HTTPS JSON and Server-Sent Events (Content-Type text/event-stream)
Reference revision 1.3, updated 2026-09-29. The authoritative public API reference is https://www.bullflow.io/api/docs. Use Download for AI agents on that page to obtain the latest bullflow-api-agent-docs.yaml. The download is a structured YAML reference, not an OpenAPI specification.
OAuth for agents and services
Subscribers can connect an agent or service without sharing their API key. The subscriber signs in to Bullflow, reviews the requested permissions, and chooses Allow access. An active API subscription and verified email are required.
For an OAuth-capable MCP client, add https://api.bullflow.io/mcp as a remote server and follow its connection prompt. Bullflow supports discovery and dynamic client registration.
In ChatGPT, configure the connection for OAuth with dynamic client registration. Tool discovery and API documentation are available before sign-in; subscriber tools request account connection when needed. Use the connected MCP tools to receive the sign-in prompt.
| Permission | Access |
|---|---|
api:read | Subscriber market data and GEX setup streams |
alerts:read | Personal custom alerts, live alerts and historical replays |
alerts:write | Create custom alerts |
offline_access | Keep the connection using rotating refresh tokens |
openid | Identify the connected Bullflow account |
email | Read the account's verified email address; requires openid |
- Discover endpoints at
https://api.bullflow.io/.well-known/oauth-authorization-server. Protected-resource metadata is available at/.well-known/oauth-protected-resource; the resource identifier ishttps://api.bullflow.io. - Register once with
POST /oauth/register, sending JSON withclient_name,redirect_uris, andtoken_endpoint_auth_method. Usenonefor a desktop or browser client, orclient_secret_basic/client_secret_postfor a service that can keep a secret. Callback URLs must use HTTPS or a local loopback address. Save the returned client ID and any client secret securely. - Open
GET /oauth/authorizewithresponse_type=code,client_id,redirect_uri,scope,state,resource=https://api.bullflow.io,code_challengeandcode_challenge_method=S256. Generate a fresh random PKCE verifier for each attempt. The challenge is its SHA-256 digest encoded as base64url without padding. PKCE is required for all clients. - On the callback, verify
stateandiss=https://api.bullflow.io. Exchange the code within 60 seconds atPOST /oauth/token, using form-encodedgrant_type=authorization_code,code,redirect_uri,code_verifier, and your registered client authentication. - Send the access token in
Authorization: Bearer ACCESS_TOKENon REST, SSE, and MCP requests. Omit the API key. Tokens expire after 15 minutes. OAuth connections on the same account share endpoint rate limits; honorRetry-Afteron HTTP 429. - If you requested
offline_access, refresh with form-encodedgrant_type=refresh_tokenandrefresh_token. Save both replacement tokens atomically and serialize refresh requests. Reusing a refresh token disconnects the connection. Connections expire after 30 days and can be renewed by connecting again.
OpenID Connect discovery is available at /.well-known/openid-configuration. Request openid email to support ChatGPT Enterprise workspace domain restrictions. Register these permissions for the client and obtain the subscriber's consent; existing grants do not gain permissions automatically.
With openid, token responses include a signed ID token. Validate its RS256 signature using /oauth/jwks, issuer, client ID audience, expiry, and the nonce sent with authorization. Call GET /oauth/userinfo with the access token in the bearer header to receive sub and, when email was granted, email and email_verified: true. ID tokens cannot be used for API access. UserInfo allows 120 requests per minute per IP.
curl 'https://api.bullflow.io/v1/data/netgex?ticker=SPY' \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
Disconnect a service in API dashboard → Connected services, or call POST /oauth/revoke with a form-encoded token and your client authentication. New requests stop working immediately after disconnection; open streams close within 15 seconds. Treat HTTP 401 as an expired or invalid connection and HTTP 403 with insufficient_scope as a request for a permission the subscriber has not granted.
MCP tool calls can instead return an error result with isError: true and _meta["mcp/www_authenticate"] to request sign-in or additional permissions. Check the tool result even when its HTTP status is 200.
Keep tokens out of URLs, chat messages, and tool results. API keys remain supported; examples using key can instead use the OAuth bearer header. Client registration allows 10 requests per hour per IP; authorization requests allow 60 per minute, and token and revocation requests each allow 120 per minute per IP.
MCP server
/mcpConnect coding agents directly to Bullflow through the MCP server. This endpoint exposes Bullflow public API docs, net GEX, net VEX, and net premium lookups, last trade price lookups, peak return lookups, custom alert creation, streaming endpoint metadata, and bounded backtesting replay samples over Streamable HTTP.
Connection
- Endpoint:
https://api.bullflow.io/mcp - Transport:
MCP Streamable HTTP - Supported method:
POST - Unsupported methods return an MCP error response.
GETandDELETEare not valid for this endpoint. - When using SSE endpoint metadata returned by MCP tools, it is recommended to implement auto reconnect functionality in your client in case the stream is disconnected for any reason.
Authentication
- OAuth:
Authorization: Bearer YOUR_ACCESS_TOKEN, or query string:?key=YOUR_API_KEY - Header:
X-API-Key: YOUR_API_KEY - Header:
Authorization: Bearer YOUR_API_KEY - Invalid or missing keys are returned as JSON-RPC errors from the MCP endpoint.
Available tools
bullflow_api_docs— returns API docs and MCP connection details.bullflow_peak_return— runs the peak return lookup with the current MCP connection's API key.bullflow_net_gex— runs the net GEX lookup with the current MCP connection's API key.bullflow_net_vex— runs the net VEX lookup with the current MCP connection's API key.bullflow_net_premium_series— returns cumulative call and put net premium values with the current MCP connection's API key.bullflow_options_top_tickers— returns leading option-flow tickers ranked by volume, premium, or directional net premium.bullflow_last_trade_price— runs the last trade price lookup with the current MCP connection's API key.bullflow_dark_pool_trades— runs the dark pool trades lookup with the current MCP connection's API key.bullflow_get_custom_alerts— returns saved custom alerts for the current MCP connection's API key account.bullflow_create_alert— creates a saved custom alert for the current MCP connection's API key account.bullflow_streaming_endpoint— returns connection details for live alerts or backtesting replay SSE.bullflow_backtesting_replay_sample— collects a bounded replay sample for agent workflows.
Resource
bullflow://api-docs— YAML docs resource exposed by the MCP server.
Example agent config
{
"mcpServers": {
"bullflow": {
"type": "streamable-http",
"url": "https://api.bullflow.io/mcp?key=YOUR_API_KEY"
}
}
}If your MCP client supports headers instead of query-string auth, use X-API-Key or Authorization: Bearer YOUR_API_KEY.
Live Options Flow Alerts
/v1/streaming/alertsStreams Bullflow algo alerts in real time plus any custom alerts that match the account associated with your API key. Only alerts created after the connection opens are delivered, so reconnect if you miss time on the stream.
Query params
key— required API key value generated in the dashboard.
Event payloads
- Each SSE
data:frame contains JSON. Parse that JSON and useeventto identify the payload type. event === "init"— providesmode(alerts) andstartedAt(ISO timestamp).event === "heartbeat"— emitted every 10 seconds to keep the connection alive; safe to ignore.event === "alert"— includes the alertidand the alert payload indata.- Alert payloads include
data.alertType,data.symbol,data.alertName,data.alertPremium,data.averageFillPrice, anddata.timestamp. data.alertType— eitheralgo(Bullflow algo alerts such as Urgent Repeater, Sizable Sweep, etc.) orcustom(alerts matching the custom alerts you set in the Bullflow dashboard).- Alert payloads also include
data.latency,data.deliveryLatency, anddata.receivedAt. - Authentication, subscription, and connection-throttle failures are returned as normal HTTP JSON errors before the SSE stream begins.
- Custom alert delivery is limited to 10 custom alerts per 60 seconds and 1000 custom alerts per 12 hours. API subscribers receive up to 60 custom alerts per 60 seconds and 5000 custom alerts per 12 hours.
- It is recommended to implement auto reconnect functionality in your SSE client in case the stream is disconnected for any reason. Live alert streams only deliver alerts created after the connection opens.
Example payload
{
"event": "alert",
"id": "1MPDp-Qm6_urgent",
"data": {
"alertType": "algo",
"symbol": "O:AMD251205P00205000",
"alertName": "Urgent Repeater",
"alertPremium": 16965.0,
"averageFillPrice": 1.31,
"timestamp": 1764708086.0,
"latency": 0.842,
"deliveryLatency": 0.091,
"receivedAt": 1764708086.751
}
}alertPremium is the premium of the trade that triggered the alert; averageFillPrice is the average fill price for that trade.
Python example code
import json
import requests
from types import SimpleNamespace
with requests.get(
"https://api.bullflow.io/v1/streaming/alerts",
params={"key": "YOUR_API_KEY"},
stream=True,
) as stream:
stream.raise_for_status()
for line in stream.iter_lines(decode_unicode=True):
if not line or not line.startswith("data: "):
continue
message = json.loads(line[6:], object_hook=lambda d: SimpleNamespace(**d))
event = getattr(message, "event", None)
if event == "init":
print("connected at", getattr(message, "startedAt", "unknown"))
if event == "heartbeat":
continue
if event == "alert":
alert = getattr(message, "data", None)
if alert:
print(
"⚡ alert",
getattr(message, "id", ""),
getattr(alert, "symbol", ""),
"premium",
getattr(alert, "alertPremium", ""),
"avg fill",
getattr(alert, "averageFillPrice", ""),
"type",
getattr(alert, "alertType", ""),
"name",
getattr(alert, "alertName", ""),
)
Live GEX setups
/v1/streaming/gexSetupsStreams newly created GEX setup alerts in real time. By default, the stream only delivers alerts created after the connection opens. Pass history=true to receive the current U.S. Eastern market date's alerts in the first SSE frame before live delivery begins.
Authentication
Requires an OAuth token with the relevant permission or an active Bullflow API key. For API keys, pass it as ?key=YOUR_API_KEY, an X-API-Key header, or an Authorization: Bearer YOUR_API_KEY header.
Query params
key— required when the API key is not supplied in a header.history— optional boolean, defaults tofalse. Whentrue, the first event contains all alerts from the current U.S. Eastern market date, ordered oldest-first.
Event payloads
- Each SSE
data:frame contains JSON. Use its top-leveleventfield to route the payload. event === "history"— sent first only whenhistory=true. Itsdataobject containsversion,date,timezone,alertCount, and an oldest-firstalertsarray.event === "init"— confirms the connection withmode(gex-setups) andstartedAt.event === "heartbeat"— keeps the connection alive and is safe to ignore.event === "gex_setup"— contains one newly created setup indata.- Every setup includes
id,type,ticker,slot,date,alertedAt,classification,severity,title,message,spotPrice, andpreviousSpotPrice. - Setup-specific fields may include
thresholdPercent,expiration,nodeStrike,nodeGex,distancePercent,absoluteDistancePercent,oldNodeStrike,newNodeStrike,oldNodeExpiration,newNodeExpiration,oldNodeGex,newNodeGex, andseverityScore. - Live setup frames also include an SSE
id:line matchingdata.id, which clients can use for deduplication. - Reconnecting with
history=truesends the current day's history again, so use each alert'sidif your client needs to deduplicate reconnects.
History first-frame example
{
"event": "history",
"data": {
"version": 1,
"date": "2026-08-25",
"timezone": "America/New_York",
"alertCount": 1,
"alerts": [
{
"id": 1841,
"type": "gex_node_proximity",
"ticker": "SPY",
"alertedAt": "2026-08-25T14:30:04+00:00",
"classification": "neutral",
"title": "GEX Node Proximity",
"spotPrice": 648.21,
"nodeStrike": 650
}
]
}
}Live setup example
{
"event": "gex_setup",
"data": {
"id": 1842,
"type": "largest_node_moved_higher",
"ticker": "NVDA",
"date": "2026-08-25",
"alertedAt": "2026-08-25T14:35:12+00:00",
"classification": "bullish",
"severity": "high",
"title": "Largest GEX Node Moved",
"message": "The largest GEX node moved higher.",
"spotPrice": 181.42,
"oldNodeStrike": 180,
"newNodeStrike": 185
}
}Python example code
import json
import requests
with requests.get(
"https://api.bullflow.io/v1/streaming/gexSetups",
params={"key": "YOUR_API_KEY", "history": "true"},
stream=True,
) as stream:
stream.raise_for_status()
for line in stream.iter_lines(decode_unicode=True):
if not line or not line.startswith("data: "):
continue
message = json.loads(line[6:])
event = message.get("event")
if event == "history":
history = message["data"]
print("history:", history["alertCount"], "alerts")
continue
if event == "heartbeat":
continue
if event == "gex_setup":
alert = message["data"]
print(alert["ticker"], alert["type"], alert["classification"])
Backtesting replay
/v1/streaming/backtestingReplays one historical trading day over SSE using your saved custom alerts plus Bullflow algo alerts from the regular session. The stream loads the day's trades, evaluates every trade against your current custom alert set, then emits matched custom alerts and algo alerts in replay order.
Query params
key— required API key value.date— required replay date inYYYY-MM-DDformat. Must be on or after2025-06-01and cannot be in the future.speed— optional replay speed multiplier. Passing1replays in real time.
Event payloads
- Each SSE
data:frame contains JSON. Parse that JSON and useeventto identify the payload type. event === "init"— includesmode(alerts-backtesting),replayDate, andconnected.event === "status"— a single initialization message:Initializing trades, custom alerts and algo alerts... This can take up to a minute.event === "ready"— includesreplayDate,speed,totalTrades,totalCustomAlerts,processedTrades,customAlerts,algoAlerts, andtotalReplayAlerts.event === "heartbeat"— emitted during replay gaps and includesplaybackTimesuch as10:42:14 EST.event === "alert"— includessequence,id, and adataobject withalertType,symbol,alertName,alertPremium,tradePrice,timestamp, andestTimestamp.data.alertType— eitheralgoorcustom.event === "complete"— includesalertsSent,totalTrades,customAlerts,algoAlerts,totalReplayAlerts, andelapsedSeconds.event === "error"— sent before the stream closes if the request cannot be loaded or replayed.- It is recommended to implement auto reconnect functionality in your SSE client in case the stream is disconnected for any reason.
Example payload
{
"event": "alert",
"id": "16",
"sequence": 16,
"data": {
"alertType": "custom",
"symbol": "O:SPY260409P00676000",
"alertName": "Ultra Repeater",
"alertPremium": 63022,
"tradePrice": 1.31,
"timestamp": 1775672380,
"estTimestamp": "2026-04-09 10:46:20 EST"
}
}Backtesting replays both matched custom alerts and algo alerts in timestamp order.
Python example code
import json
import requests
with requests.get(
"https://api.bullflow.io/v1/streaming/backtesting",
params={"key": "YOUR_API_KEY", "date": "2026-04-08", "speed": 60},
stream=True,
) as stream:
stream.raise_for_status()
for line in stream.iter_lines(decode_unicode=True):
if not line or not line.startswith("data: "):
continue
message = json.loads(line[6:])
event = message.get("event")
if event == "status":
print("status:", message.get("message"))
continue
if event == "heartbeat":
print("heartbeat:", message.get("playbackTime"))
continue
if event == "alert":
alert = message.get("data") or {}
print(
"alert",
message.get("sequence"),
alert.get("alertType"),
alert.get("symbol"),
alert.get("alertName"),
alert.get("tradePrice"),
alert.get("estTimestamp"),
)
continue
if event == "complete":
print("finished replay:", message.get("alertsSent"), "events sent")
breakCreate alert
/v1/alerts/create-alertCreates a saved custom alert for the account associated with your API key.
Authentication
- OAuth:
Authorization: Bearer YOUR_ACCESS_TOKEN, or query string:?key=YOUR_API_KEY - Header:
X-API-Key: YOUR_API_KEY - Header:
Authorization: Bearer YOUR_API_KEY
JSON body
name— required human-readable alert name.- Numeric range fields use clean min/max names:
premiumMin/premiumMax,dteMin/dteMax,otmPercentMin/otmPercentMax,scoreMin/scoreMax,strikeMin/strikeMax,impliedVolatilityMin/impliedVolatilityMax, and the other range fields shown in the YAML download. - Ticker filters:
tickerBlocklistexcludes tickers,tickerAllowlistlimits matches to specific tickers, and values are uppercased and deduped. - Quick filters: pass user-facing chip labels in
quickFilters. - Sector filter:
sectorCodedefaults to12. - Include switches:
includeCalls,includePuts,includeAskSide,includeBidSide,includeMid,includeBullish,includeBearish,includeNeutral,includeSingles,includeSweeps,includeSplits,includeBlocks,includeMultiLeg, andincludeExDividend.
Quick filter labels
Send labels in quickFilters. The tooltip text below describes what each quick filter means.
| Label | Tooltip |
|---|---|
| ETFs | ETFs |
| Stocks | Stocks |
| Sweeps | Trades executed rapidly across multiple exchanges |
| Calls | Call options |
| Puts | Put options |
| Bid | Trades executed on the bid side |
| Ask | Trades executed on the ask side |
| AA | Above Ask. They really wanted these contracts! |
| Unusual | Trade size exceeding open interest |
| Urgent | Rapid repeat trades matching Bullflow criteria |
| Bullflow | Aggressive repeat trades matching Bullflow criteria. |
| Position Builders | Consistent repeat trades matching Bullflow criteria |
| Sizable | Large Unusual Trades |
| Grenade | Risky short term trades |
| 100k+ | Trades with $100,000+ value |
| Whales | $1M+ |
| Rising Vol | First trade that hits the scanner where volume exceeds OI |
| AM Spike | Volume exceeding 15% of the OI, 25k OI minimum, before 11am EST |
| LEAPS | Long dated expirations |
| High Sig | SigScore 0.8+ |
| Repeat Flow | Contracts that have 3 or more size>OI trades. |
| Large Size | Size > 5000 |
| Earnings Soon | Earnings within 2 days |
| Vol>OI | Volume greater than open interest |
| Weeklies | Trades expiring within a week |
| Penny Stocks | Stock price < $5 |
| Oddities | Stocks not seen on Bullflow in a day |
| Unicorns | Stocks not seen on Bullflow in a week or longer |
| OTM | Out of the money |
| Bullish Flow | Stocks with 80%+ bullish/bearish ratio |
| > Avg | Total premium has exceeded the daily average for this stock. |
Validation
- Unknown fields are rejected. Numeric fields must be finite, and each min value must be less than or equal to its matching max value.
- Missing optional fields use broad defaults.
Example request
{
"name": "Large bullish SPY flow",
"premiumMin": 50000,
"tickerAllowlist": ["SPY"],
"includePuts": false,
"includeBearish": false,
"quickFilters": ["100k+"]
}Example response
{
"id": "abc123",
"status": "created",
"alertName": "Large bullish SPY flow"
}JavaScript example code
const response = await fetch(
"https://api.bullflow.io/v1/alerts/create-alert?key=YOUR_API_KEY",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "Large bullish SPY flow",
premiumMin: 50000,
tickerAllowlist: ["SPY"],
includePuts: false,
includeBearish: false,
quickFilters: ["100k+"]
})
}
);
if (!response.ok) {
throw new Error(await response.text());
}
console.log(await response.json());Custom Options Flow Alerts
/v1/alerts/custom-alertsReturns the saved custom alerts for the account associated with your API key.
Authentication
- OAuth:
Authorization: Bearer YOUR_ACCESS_TOKEN, or query string:?key=YOUR_API_KEY - Header:
X-API-Key: YOUR_API_KEY - Header:
Authorization: Bearer YOUR_API_KEY
Response
count— number of saved custom alerts returned.alerts— saved alerts, including each alertid.quickFilters— user-facing quick filter labels for the alert.
Example response
{
"count": 1,
"alerts": [
{
"id": "abc123",
"alertName": "Large bullish SPY flow",
"minPremium": 50000,
"maxPremium": 5000000000,
"quickFilters": ["100k+"],
"timestamp": "2026-05-13T18:24:00.000Z"
}
]
}JavaScript example code
const response = await fetch(
"https://api.bullflow.io/v1/alerts/custom-alerts?key=YOUR_API_KEY"
);
if (!response.ok) {
throw new Error(await response.text());
}
console.log(await response.json());Peak return
/v1/data/peakReturnReturns the highest percent return reached by an option contract after the trade timestamp you provide.
Query params
key— required API key value.sym— required option symbol, for exampleO:SPY260408C00520000.old_price— required entry price for the contract.trade_timestamp— required Unix timestamp for the trade.
Behavior
- Successful responses return a JSON object with the peak price and the peak percent return since the provided timestamp.
- Missing or invalid API keys return normal HTTP JSON errors before any lookup runs.
- This endpoint is rate-limited to 60 requests per minute per API key.
Example response
{
"peakPriceSinceTimestamp": "125.47",
"peakPercentReturnSinceTimestamp": 12.22
}Python example code
import requests
response = requests.get(
"https://api.bullflow.io/v1/data/peakReturn",
params={
"key": "YOUR_API_KEY",
"sym": "O:SPY260408C00520000",
"old_price": 1.35,
"trade_timestamp": 1775669400,
},
timeout=30,
)
response.raise_for_status()
print(response.json())Net GEX
/v1/data/netgexReturns net GEX by strike and expiration and removes the aggregate gamma field from every strike row.
Query params
key— required API key value.ticker— required underlying ticker symbol, for exampleSPY.symis also accepted as an alias.
Behavior
- Successful responses return a JSON object with
ticker,spot_price,as_of, andstrikes. - Each strike row includes
expiration,strike,call_gex,put_gex,net_gex,call_gamma, andput_gamma. - The response intentionally omits
gamma. - This endpoint is rate-limited to 25 requests per rolling 60 seconds per API key, per endpoint.
- GEX and VEX have separate limits: each API key may make 25 GEX requests and 25 VEX requests per rolling 60 seconds. All tickers on the same endpoint share its limit.
- There is no five-second minimum between requests. Bursts are allowed while fewer than 25 requests remain in the preceding 60 seconds; capacity returns as those requests age out of the window.
- When throttled, the endpoint returns HTTP 429 with Retry-After in seconds. Wait at least that long before retrying; requests admitted before a later data lookup failure still count toward the limit.
- For steady collection, space requests slightly more than 2.4 seconds apart per endpoint, coordinate all collectors using the same key, and honor Retry-After on HTTP 429.
Example response
{
"ticker": "SPY",
"spot_price": 719.245,
"as_of": "2026-04-30T19:24:26.236Z",
"strikes": [
{
"expiration": "20260430",
"strike": 659,
"call_gex": 63681.28,
"put_gex": 0,
"net_gex": 63681.28,
"call_gamma": 0.00123,
"put_gamma": 0
}
]
}Python example code
import requests
response = requests.get(
"https://api.bullflow.io/v1/data/netgex",
params={
"key": "YOUR_API_KEY",
"ticker": "SPY",
},
timeout=30,
)
response.raise_for_status()
print(response.json())Net VEX
/v1/data/netvexReturns net VEX by strike and expiration and removes the aggregate vanna field from every strike row.
Query params
key— required API key value.ticker— required underlying ticker symbol, for exampleSPY.symis also accepted as an alias.
Behavior
- Successful responses return a JSON object with
ticker,spot_price,as_of, andstrikes. - Each strike row includes
expiration,strike,call_vex,put_vex,net_vex,call_vanna, andput_vanna. - The response intentionally omits
vanna. - Net VEX uses
spot_price * vanna * open_interestand calculatesnet_vexas call VEX plus put VEX. - This endpoint is rate-limited to 25 requests per rolling 60 seconds per API key, per endpoint.
- GEX and VEX have separate limits: each API key may make 25 GEX requests and 25 VEX requests per rolling 60 seconds. All tickers on the same endpoint share its limit.
- There is no five-second minimum between requests. Bursts are allowed while fewer than 25 requests remain in the preceding 60 seconds; capacity returns as those requests age out of the window.
- When throttled, the endpoint returns HTTP 429 with Retry-After in seconds. Wait at least that long before retrying; requests admitted before a later data lookup failure still count toward the limit.
- For steady collection, space requests slightly more than 2.4 seconds apart per endpoint, coordinate all collectors using the same key, and honor Retry-After on HTTP 429.
Example response
{
"ticker": "SPY",
"spot_price": 719.245,
"as_of": "2026-04-30T19:24:26.236Z",
"strikes": [
{
"expiration": "20260430",
"strike": 659,
"call_vex": 1204.33,
"put_vex": -455.12,
"net_vex": 749.21,
"call_vanna": 1.6744,
"put_vanna": -0.6328
}
]
}Python example code
import requests
response = requests.get(
"https://api.bullflow.io/v1/data/netvex",
params={
"key": "YOUR_API_KEY",
"ticker": "SPY",
},
timeout=30,
)
response.raise_for_status()
print(response.json())Options top tickers
/v1/data/optionsTopTickersReturns the leading option-flow tickers ranked by volume, premium, or net premium for the current trading day, a selected date, or an inclusive date range.
Query params
key— required API key value.date— optional single trading date inYYYY-MM-DDformat. Cannot be combined withfromorto.from— optional start date for an inclusive range inYYYY-MM-DDformat. Must be supplied withto.to— optional end date for an inclusive range inYYYY-MM-DDformat. Must be supplied withfrom; the range cannot exceed 30 days.metric— optional ranking metric:volume,premium, ornet_premium. Defaults topremium.ticker_count— optional result count:10,20,30,40, or50. Defaults to20.exclude_etfs— optional boolean that excludes ETF option flow. Defaults tofalse.sweeps_only— optional boolean that limits results to sweep trades. Defaults tofalse.bullish_bearish— optional boolean fornet_premium. When enabled, returns a balanced selection of bullish and bearish leaders. Defaults tofalse.
Behavior
- When
date,from, andtoare omitted, the endpoint returns the current trading day's flow. - A supplied
datereturns that day's ranking. A suppliedfrom/topair aggregates all available trading days in the inclusive range. - Volume results include
totalVolume,callVolume, andputVolumefor each ticker. - Premium results include
totalPremium,callPremium, andputPremiumfor each ticker. - Net-premium results include directional call and put values plus ask-side and bid-side premium breakdowns.
totalNetPremiumis call net premium minus put net premium. - This endpoint is rate-limited to 10 requests every minute per API key.
Example response
{
"tradingDate": "2026-08-14",
"from": "2026-08-03",
"to": "2026-08-14",
"generatedAt": "2026-08-14T20:05:10.000Z",
"metric": "premium",
"tickerCount": 20,
"filters": {
"excludeEtfs": false,
"sweepsOnly": false,
"bullishBearish": false
},
"sourceTradeCount": 18420,
"matchedTradeCount": 16275,
"count": 2,
"rows": [
{
"ticker": "NVDA",
"totalPremium": 128450000,
"callPremium": 76400000,
"putPremium": 52050000
},
{
"ticker": "SPY",
"totalPremium": 113200000,
"callPremium": 58900000,
"putPremium": 54300000
}
]
}Python example code
import requests
response = requests.get(
"https://api.bullflow.io/v1/data/optionsTopTickers",
params={
"key": "YOUR_API_KEY",
"from": "2026-08-03",
"to": "2026-08-14",
"metric": "premium",
"ticker_count": 20,
"exclude_etfs": False,
"sweeps_only": False,
"bullish_bearish": False,
},
timeout=30,
)
response.raise_for_status()
print(response.json())Last trade price
/v1/data/lastTradePriceReturns the latest last trade price for a stock or supported index ticker.
Query params
key— required API key value.ticker— required stock or supported index ticker symbol, for exampleSPYorSPX.symis also accepted as an alias.
Behavior
- Successful responses return a JSON object with
lastTradePriceandtimestamp. When present,timestampis UTC and uses ISO 8601 format with a trailingZ. - Missing or invalid API keys return normal HTTP JSON errors before any lookup runs.
- This endpoint is rate-limited to 60 requests every minute per API key.
Example response
{
"lastTradePrice": 719.24,
"timestamp": "2026-04-30T19:24:26.236Z"
}Python example code
import requests
response = requests.get(
"https://api.bullflow.io/v1/data/lastTradePrice",
params={
"key": "YOUR_API_KEY",
"ticker": "SPY",
},
timeout=30,
)
response.raise_for_status()
print(response.json())Dark pool trades
/v1/data/darkPoolTradesReturns dark pool trades for an inclusive date range, optionally filtered by ticker.
Query params
key— required API key value.from— required start date inYYYY-MM-DDformat.to— required end date inYYYY-MM-DDformat.ticker— optional stock ticker symbol, for exampleSPY.limit— optional page size. Defaults to500and cannot exceed5000.cursor— optional pagination cursor from a previous response'snextCursorfield.minNotional— optional minimum trade notional value.
Behavior
- Date ranges are inclusive. Passing the same date for
fromandtoreturns trades for that day. - This endpoint returns trades reported through Nasdaq trade reporting data. Based on Nasdaq's public figures, FINRA/Nasdaq TRFs support more than 40% of total U.S. equities trading while off-exchange trading is over 45% of volume, making Nasdaq-reported off-exchange flow roughly 90% of U.S. off-exchange equity trading.
- The date range cannot exceed 30 days.
- Successful responses return
from,to,ticker,minNotional,count,limit,hasMore,nextCursor, androws. - When
hasMoreistrue, passnextCursorascursoron the next request to fetch the following page. - Condition codes can be cross-referenced against Nasdaq Trader's sale condition modifier definitions.
- This endpoint is rate-limited to 10 requests every minute per API key.
Example response
{
"from": "2026-04-01",
"to": "2026-04-01",
"ticker": "SPY",
"minNotional": 1000000,
"count": 1,
"limit": 500,
"hasMore": false,
"nextCursor": null,
"rows": [
{
"symbol": "SPY",
"tradeId": "123456789",
"sequence": 987654,
"exchange": "D",
"trfId": "N",
"price": 519.24,
"size": 10000,
"notional": 5192400,
"volume": 74231812,
"conditions": [37],
"sipTimestampMs": 1775067000000,
"trfTimestampMs": 1775067000123,
"pctDayVolume": 0.1348,
"volumePercent": 13.48,
"percent30DayVolume": 4.91,
"correctionOnlyUpdate": false,
"tradeDate": "2026-04-01"
}
]
}Python example code
import requests
response = requests.get(
"https://api.bullflow.io/v1/data/darkPoolTrades",
params={
"key": "YOUR_API_KEY",
"from": "2026-04-01",
"to": "2026-04-01",
"ticker": "SPY",
"limit": 500,
"minNotional": 1000000,
},
timeout=30,
)
response.raise_for_status()
print(response.json())