Developer resources v1

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)

Options flow guide

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.

Account connections

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.

PermissionAccess
api:readSubscriber market data and GEX setup streams
alerts:readPersonal custom alerts, live alerts and historical replays
alerts:writeCreate custom alerts
offline_accessKeep the connection using rotating refresh tokens
openidIdentify the connected Bullflow account
emailRead the account's verified email address; requires openid
  1. 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 is https://api.bullflow.io.
  2. Register once with POST /oauth/register, sending JSON with client_name, redirect_uris, and token_endpoint_auth_method. Use none for a desktop or browser client, or client_secret_basic / client_secret_post for 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.
  3. Open GET /oauth/authorize with response_type=code, client_id, redirect_uri, scope, state, resource=https://api.bullflow.io, code_challenge and code_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.
  4. On the callback, verify state and iss=https://api.bullflow.io. Exchange the code within 60 seconds at POST /oauth/token, using form-encoded grant_type=authorization_code, code, redirect_uri, code_verifier, and your registered client authentication.
  5. Send the access token in Authorization: Bearer ACCESS_TOKEN on REST, SSE, and MCP requests. Omit the API key. Tokens expire after 15 minutes. OAuth connections on the same account share endpoint rate limits; honor Retry-After on HTTP 429.
  6. If you requested offline_access, refresh with form-encoded grant_type=refresh_token and refresh_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

MCP server

POST /mcp

Connect 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. GET and DELETE are 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.

Alerts

Live Options Flow Alerts

GET /v1/streaming/alerts

Streams 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 use event to identify the payload type.
  • event === "init" — provides mode (alerts) and startedAt (ISO timestamp).
  • event === "heartbeat" — emitted every 10 seconds to keep the connection alive; safe to ignore.
  • event === "alert" — includes the alert id and the alert payload in data.
  • Alert payloads include data.alertType, data.symbol, data.alertName, data.alertPremium, data.averageFillPrice, and data.timestamp.
  • data.alertType — either algo (Bullflow algo alerts such as Urgent Repeater, Sizable Sweep, etc.) or custom (alerts matching the custom alerts you set in the Bullflow dashboard).
  • Alert payloads also include data.latency, data.deliveryLatency, and data.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", ""),
                )
GEX

Live GEX setups

GET /v1/streaming/gexSetups

Streams 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 to false. When true, 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-level event field to route the payload.
  • event === "history" — sent first only when history=true. Its data object contains version, date, timezone, alertCount, and an oldest-first alerts array.
  • event === "init" — confirms the connection with mode (gex-setups) and startedAt.
  • event === "heartbeat" — keeps the connection alive and is safe to ignore.
  • event === "gex_setup" — contains one newly created setup in data.
  • Every setup includes id, type, ticker, slot, date, alertedAt, classification, severity, title, message, spotPrice, and previousSpotPrice.
  • Setup-specific fields may include thresholdPercent, expiration, nodeStrike, nodeGex, distancePercent, absoluteDistancePercent, oldNodeStrike, newNodeStrike, oldNodeExpiration, newNodeExpiration, oldNodeGex, newNodeGex, and severityScore.
  • Live setup frames also include an SSE id: line matching data.id, which clients can use for deduplication.
  • Reconnecting with history=true sends the current day's history again, so use each alert's id if 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

Backtesting replay

GET /v1/streaming/backtesting

Replays 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 in YYYY-MM-DD format. Must be on or after 2025-06-01 and cannot be in the future.
  • speed — optional replay speed multiplier. Passing 1 replays in real time.

Event payloads

  • Each SSE data: frame contains JSON. Parse that JSON and use event to identify the payload type.
  • event === "init" — includes mode (alerts-backtesting), replayDate, and connected.
  • event === "status" — a single initialization message: Initializing trades, custom alerts and algo alerts... This can take up to a minute.
  • event === "ready" — includes replayDate, speed, totalTrades, totalCustomAlerts, processedTrades, customAlerts, algoAlerts, and totalReplayAlerts.
  • event === "heartbeat" — emitted during replay gaps and includes playbackTime such as 10:42:14 EST.
  • event === "alert" — includes sequence, id, and a data object with alertType, symbol, alertName, alertPremium, tradePrice, timestamp, and estTimestamp.
  • data.alertType — either algo or custom.
  • event === "complete" — includes alertsSent, totalTrades, customAlerts, algoAlerts, totalReplayAlerts, and elapsedSeconds.
  • 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")
            break
Alerts

Create alert

POST /v1/alerts/create-alert

Creates 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: tickerBlocklist excludes tickers, tickerAllowlist limits matches to specific tickers, and values are uppercased and deduped.
  • Quick filters: pass user-facing chip labels in quickFilters.
  • Sector filter: sectorCode defaults to 12.
  • Include switches: includeCalls, includePuts, includeAskSide, includeBidSide, includeMid, includeBullish, includeBearish, includeNeutral, includeSingles, includeSweeps, includeSplits, includeBlocks, includeMultiLeg, and includeExDividend.

Quick filter labels

Send labels in quickFilters. The tooltip text below describes what each quick filter means.

LabelTooltip
ETFsETFs
StocksStocks
SweepsTrades executed rapidly across multiple exchanges
CallsCall options
PutsPut options
BidTrades executed on the bid side
AskTrades executed on the ask side
AAAbove Ask. They really wanted these contracts!
UnusualTrade size exceeding open interest
UrgentRapid repeat trades matching Bullflow criteria
BullflowAggressive repeat trades matching Bullflow criteria.
Position BuildersConsistent repeat trades matching Bullflow criteria
SizableLarge Unusual Trades
GrenadeRisky short term trades
100k+Trades with $100,000+ value
Whales$1M+
Rising VolFirst trade that hits the scanner where volume exceeds OI
AM SpikeVolume exceeding 15% of the OI, 25k OI minimum, before 11am EST
LEAPSLong dated expirations
High SigSigScore 0.8+
Repeat FlowContracts that have 3 or more size>OI trades.
Large SizeSize > 5000
Earnings SoonEarnings within 2 days
Vol>OIVolume greater than open interest
WeekliesTrades expiring within a week
Penny StocksStock price < $5
OdditiesStocks not seen on Bullflow in a day
UnicornsStocks not seen on Bullflow in a week or longer
OTMOut of the money
Bullish FlowStocks with 80%+ bullish/bearish ratio
> AvgTotal 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());
Alerts

Custom Options Flow Alerts

GET /v1/alerts/custom-alerts

Returns 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 alert id.
  • 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());
Data

Peak return

GET /v1/data/peakReturn

Returns 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 example O: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())
Data

Net GEX

GET /v1/data/netgex

Returns 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 example SPY. sym is also accepted as an alias.

Behavior

  • Successful responses return a JSON object with ticker, spot_price, as_of, and strikes.
  • Each strike row includes expiration, strike, call_gex, put_gex, net_gex, call_gamma, and put_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())
Data

Net VEX

GET /v1/data/netvex

Returns 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 example SPY. sym is also accepted as an alias.

Behavior

  • Successful responses return a JSON object with ticker, spot_price, as_of, and strikes.
  • Each strike row includes expiration, strike, call_vex, put_vex, net_vex, call_vanna, and put_vanna.
  • The response intentionally omits vanna.
  • Net VEX uses spot_price * vanna * open_interest and calculates net_vex as 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())
Data

Net premium

GET /v1/data/netPremiumSeries

Returns chronological cumulative call and put net premium values for a ticker and inclusive date range.

Query params

  • key — required API key value.
  • ticker — required underlying ticker symbol, for example SPY.
  • from — required inclusive start date in YYYY-MM-DD format.
  • to — required inclusive end date in YYYY-MM-DD format.
  • period — optional aggregation period: 1D, 7D, 1M, 3M, 1Y, or MAX. Defaults to 1D.
  • include_multileg — optional boolean. Defaults to false.
  • sweeps_only — optional boolean. Defaults to false.

Behavior

  • Each point contains a Unix timestamp, cumulative callsNetPremium, and cumulative putsNetPremium.
  • bucket_seconds reports the time bucket size. A value of 0 means individual time points.
  • Results are returned in chronological order.
  • This endpoint is rate-limited to 30 requests every minute per API key.

Example response

{
  "ticker": "SPY",
  "period": "1D",
  "from": "2026-07-24",
  "to": "2026-07-24",
  "include_multileg": false,
  "sweeps_only": false,
  "multilegs_filtered_count": 593,
  "sweeps_filtered_count": 0,
  "bucket_seconds": 0,
  "point_count": 2,
  "points": [
    {
      "timestamp": 1784899805,
      "callsNetPremium": -274992,
      "putsNetPremium": 0
    },
    {
      "timestamp": 1784899812,
      "callsNetPremium": -449529,
      "putsNetPremium": 125000
    }
  ]
}

Python example code

import requests

response = requests.get(
    "https://api.bullflow.io/v1/data/netPremiumSeries",
    params={
        "key": "YOUR_API_KEY",
        "ticker": "SPY",
        "from": "2026-07-24",
        "to": "2026-07-24",
        "period": "1D",
        "include_multileg": False,
        "sweeps_only": False,
    },
    timeout=30,
)
response.raise_for_status()
print(response.json())
Data

Options top tickers

GET /v1/data/optionsTopTickers

Returns 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 in YYYY-MM-DD format. Cannot be combined with from or to.
  • from — optional start date for an inclusive range in YYYY-MM-DD format. Must be supplied with to.
  • to — optional end date for an inclusive range in YYYY-MM-DD format. Must be supplied with from; the range cannot exceed 30 days.
  • metric — optional ranking metric: volume, premium, or net_premium. Defaults to premium.
  • ticker_count — optional result count: 10, 20, 30, 40, or 50. Defaults to 20.
  • exclude_etfs — optional boolean that excludes ETF option flow. Defaults to false.
  • sweeps_only — optional boolean that limits results to sweep trades. Defaults to false.
  • bullish_bearish — optional boolean for net_premium. When enabled, returns a balanced selection of bullish and bearish leaders. Defaults to false.

Behavior

  • When date, from, and to are omitted, the endpoint returns the current trading day's flow.
  • A supplied date returns that day's ranking. A supplied from/to pair aggregates all available trading days in the inclusive range.
  • Volume results include totalVolume, callVolume, and putVolume for each ticker.
  • Premium results include totalPremium, callPremium, and putPremium for each ticker.
  • Net-premium results include directional call and put values plus ask-side and bid-side premium breakdowns. totalNetPremium is 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())
Data

Last trade price

GET /v1/data/lastTradePrice

Returns 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 example SPY or SPX. sym is also accepted as an alias.

Behavior

  • Successful responses return a JSON object with lastTradePrice and timestamp. When present, timestamp is UTC and uses ISO 8601 format with a trailing Z.
  • 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())
Data

Dark pool trades

GET /v1/data/darkPoolTrades

Returns dark pool trades for an inclusive date range, optionally filtered by ticker.

Query params

  • key — required API key value.
  • from — required start date in YYYY-MM-DD format.
  • to — required end date in YYYY-MM-DD format.
  • ticker — optional stock ticker symbol, for example SPY.
  • limit — optional page size. Defaults to 500 and cannot exceed 5000.
  • cursor — optional pagination cursor from a previous response's nextCursor field.
  • minNotional — optional minimum trade notional value.

Behavior

  • Date ranges are inclusive. Passing the same date for from and to returns 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, and rows.
  • When hasMore is true, pass nextCursor as cursor on 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())