Public API · MCP Server · Watchlist + Alerts + EDGAR

BullAlert API & MCP

Three read-only endpoints, served as REST + an MCP server. The momentum watchlist — our top picks, ranked by a proprietary algorithm on 5-min model cycles — alerts — the record of what we actually flagged, and when — and EDGAR — SEC-filed company financials (dilution, cash runway, insider, filings) for any US ticker. Build your own bot, spreadsheet, dashboard, or AI agent on the same data the BullAlert app runs on.

Watchlist & alerts serve our derived outputs — status, score, tier — never raw market data. EDGAR returns public-domain SEC filing data.

Endpoints

3

The /watchlist, the /alerts record + /edgar financials.

Cycle

~5 min

Watchlist recomputed on 5-min wall-clock boundaries; alerts served from the DB with a ~1-min cache.

Top N

10–100

Watchlist caller picks; default 10. Alerts have no limit (a few a day).

Auth

1 key

One key powers your bot, sheet, and AI agent.

Quick start

Generate a key from your dashboard, then pick how you want to plug it in. MCP is the easiest path if you already use Claude, Cursor, or ChatGPT — no code required. REST is the path for custom bots, spreadsheets, and dashboards.

One config block gives your AI agent three native tools — list_watchlist (the current ranked list), list_alerts (the published record), and get_company_financials (SEC-EDGAR fundamentals + dilution / runway / insider for any ticker). No glue code, no scraping, no prompt-engineering the JSON shape.

Claude Desktop / Claude Code

~/.config/claude/mcp.json · %APPDATA%\Claude\mcp.json
{
  "mcpServers": {
    "bullalert": {
      "type": "http",
      "url": "https://api.bullalert.ai/v1/mcp",
      "headers": { "x-ba-api-key": "ba_live_..." }
    }
  }
}

Cursor / Windsurf

Same JSON block, dropped into the IDE's MCP server settings. Restart the IDE and your agent can call list_watchlist, list_alerts, and get_company_financials natively.

ChatGPT (custom GPT)

Point a custom GPT's “Actions” at https://api.bullalert.ai/v1/mcp with the same header. Useful for non-developers who want a packaged bot inside ChatGPT.

Try it: “What's on the BullAlert watchlist right now?” · “Show me the top 25 after-hours picks.” · “What tickers did BullAlert alert today, and when?”

Three endpoints, one header, JSON in — no SDK, no setup. Same data the in-app dashboard runs on.

curl

curl -H "x-ba-api-key: ba_live_..." \
  "https://api.bullalert.ai/v1/watchlist?session=current&limit=10"

Python

import requests

r = requests.get(
    "https://api.bullalert.ai/v1/watchlist",
    headers={"x-ba-api-key": "ba_live_..."},
    params={"session": "current", "limit": 10},
)
for row in r.json()["data"]["watchlist"]:
    print(row["rank"], row["ticker"], row["score"])

JavaScript / Node

const res = await fetch(
  "https://api.bullalert.ai/v1/watchlist?session=current&limit=10",
  { headers: { "x-ba-api-key": "ba_live_..." } },
);
const { data } = await res.json();
console.table(data.watchlist);

Alerts — the published record (curl)

curl -H "x-ba-api-key: ba_live_..." \
  "https://api.bullalert.ai/v1/alerts"

The current session's published alerts, newest-first — each row is ticker + caught_at (the session is in meta.session). Add ?session=all for the whole day or ?as_of= for a past day.

EDGAR — company financials (curl)

curl -H "x-ba-api-key: ba_live_..." \
  "https://api.bullalert.ai/v1/edgar/DXST"

SEC-filed fundamentals + small-cap signals for any US ticker — dilution, cash runway, insider, and the latest material filing. Add ?detail=true for raw values + provenance.

Authentication

Every request requires an x-ba-api-key header. Generate one from your dashboard in a single click — no support ticket in the loop.

1. Create

Generate a key in one click from your dashboard. Copy it right away — we hash it on our side, so the full key is shown once and never again.

2. Use

Send x-ba-api-key on every request. Header only — never query-param in prod.

3. Manage

Everything lives in your dashboard — your active key, usage, and the controls to roll it if you ever need to. Changes take effect within ~5 min.

The endpoints

Three read-only endpoints, one key. Watchlist is the current ranked candidate list, recomputed on 5-min model cycles. Alerts is the record of what we actually published — which tickers we flagged, and when. EDGAR is SEC-filed company financials — dilution, cash runway, insider, and filings — for any US ticker.

The current ranked candidate list, recomputed each 5-min cycle and projected to a minimal, stable shape.

GEThttps://api.bullalert.ai/v1/watchlist

Query parameters

Param Default Description
session current pre_market | market | after_hours | all | current (auto-resolves to the active ET session).
limit 10 Integer 1–100. Same cap on every plan.
status all momentum | watch | all. Filter by row state. momentum = only rows that cleared all admission gates this refresh (strict, actionable cohort). watch = only fill-back rows (on the radar, gates not all green this refresh). all (default) returns both, ranked momentum-first.
as_of UTC ISO8601 timestamp for historical replay (e.g. 2026-05-19T18:30:00Z). Returns the watchlist the way it looked at that moment. Earliest data: alerts back to 2026-02-19, full firehose back to 2026-04-20. Omit for current-cycle behavior.

// historical replay

Pass as_of to replay the watchlist at any past moment — same shape, same scoring, just bucketed by the timestamp's session window. Useful for backtests, post-mortem analysis, and reproducing what your agent saw at a given cycle.

curl -H "x-ba-api-key: ba_live_..." \
  "https://api.bullalert.ai/v1/watchlist?as_of=2026-05-19T18:30:00Z&limit=25"

Response

{
  "data": {
    "watchlist": [
      { "rank": 1, "ticker": "WOLF", "score": 95, "status": "momentum" },
      { "rank": 2, "ticker": "AMD",  "score": 72, "status": "momentum" },
      { "rank": 3, "ticker": "TSLA", "score": 64, "status": "watch" }
    ]
  },
  "meta": {
    "timestamp": "2026-05-28T18:05:32Z",
    "api_version": "v1",
    "algorithm_version": "v1.1.0",
    "session": "market",
    "count": 3,
    "momentum_count": 2,
    "data_freshness_seconds": 23,
    "mtf_enabled": true,
    "rate_limit_remaining": 59,
    "rate_limit_reset": "2026-05-28T18:06:00Z"
  }
}

v1 row shape is intentionally minimal — rank + ticker + score + status. Session-aware, recomputed each 5-min cycle. We give you the ranked tickers, the score, and the current state; for prices, % moves, charts, and news, plug in the market-data API of your choice.

Every row carries a status: momentum (met all admission criteria as of this 5-min refresh) or watch (on the universe pool, criteria not all met this refresh — informational fill-back). The list is sorted momentum-first, so every momentum row outranks every watch row regardless of raw score. Pass ?status=momentum to narrow the response to the strict cohort. meta.momentum_count tells you how many of meta.count rows are momentum without scanning the array.

Outside trading hours (weekends, holidays, overnight 8 PM – 4 AM ET) the response returns data.watchlist: [] plus meta.market_status: "closed" and a human-readable meta.message. Cached longer in that state.

Field reference

rank
1-based ranking. Rank 1 is the strongest pick in the response.
ticker
US small-cap symbol. Every ticker returned has cleared the full BullAlert pipeline — 20 hard gates plus a momentum check by our proprietary algorithm. One row per ticker per response (deduped across internal lists).
score
Composite on a normalized 0–100 scale, produced by our proprietary algorithm.
status
"momentum" (green) — the row met all admission criteria as of this 5-min refresh. "watch" (yellow) — on the universe pool but criteria not all met this refresh; informational. The list is sorted momentum-first, so every momentum row outranks every watch row regardless of raw score. Filter to one cohort with the ?status query param.
meta.timestamp
UTC ISO8601 timestamp the response was computed.
meta.algorithm_version
Version of the underlying algorithm. Bumps when we ship improvements.
meta.session
Resolved session — what "current" turned into, or what you asked for. Can also be "closed" when the market is shut.
meta.count
Number of rows actually returned (≤ limit).
meta.momentum_count
How many of the count rows have status="momentum". meta.count − meta.momentum_count = the number of watch rows. Useful for routing without scanning the array.
meta.data_freshness_seconds
Age in seconds of the freshest data sample used by the momentum eval. Typical calls are 30–90s; null when market is closed.
meta.mtf_enabled
True when the engine ran the 5-min multi-timeframe context primitives in addition to the 1-min baseline.
meta.rate_limit_remaining
Calls left in your current per-minute window (mirrors the X-RateLimit-Remaining header). Per-day and per-month budgets are reported on the X-RateLimit-Daily-* and X-RateLimit-Monthly-* response headers.
meta.rate_limit_reset
UTC ISO8601 timestamp at which the per-minute window resets. Mirrors X-RateLimit-Reset. After this point, your per-minute budget refills.
meta.market_status (optional)
Present only when the market is closed. Value: "closed".
meta.message (optional)
Human-readable explanation, present only on market-closed responses.

The record of what BullAlert actually published on a trading day — straight from the database, newest-first. Where the watchlist is the current candidate list, alerts are the committed calls: which tickers we flagged, and when.

GEThttps://api.bullalert.ai/v1/alerts

Query parameters

Param Default Description
session current pre_market | market | after_hours | all | current. Default current = the active ET session (same as the watchlist); pass all for the full day across PM/RTH/AH. When the market is closed, current returns the full day's record.
as_of UTC ISO8601 timestamp for historical replay (e.g. 2026-05-19T18:30:00Z). Returns the alerts for that ET trading date, caught at or before the timestamp. Alerts back to 2026-02-19. Omit for today's record.

No limit — a day holds at most a handful of alerts (a 3/5/3 per-session cap). No status — alerts are already the committed set.

Response

{
  "data": {
    "alerts": [
      { "ticker": "EEIQ", "caught_at": "2026-05-29T16:27:26Z" },
      { "ticker": "CLIK", "caught_at": "2026-05-29T12:31:28Z" }
    ]
  },
  "meta": {
    "timestamp": "2026-05-29T20:10:00Z",
    "api_version": "v1",
    "algorithm_version": "v1.1.0",
    "session": "all",
    "scan_date": "2026-05-29",
    "count": 5,
    "rate_limit_remaining": 59,
    "rate_limit_reset": "2026-05-29T20:11:00Z"
  }
}

Each row is just ticker + caught_at — which ticker we flagged, and the moment we caught it. The session those alerts belong to is in meta.session (scope the request with ?session= to pin it). The endpoint is the timestamped record of what we published — not a price feed. All timestamps are UTC (ISO8601, …Z); only meta.scan_date is the ET trading day.

Field reference

ticker
US small-cap symbol. Every row cleared the full BullAlert promotion pipeline (20 hard gates + game-changer / fader / quality checks) to become a published alert.
caught_at
UTC ISO8601 timestamp we flagged the ticker (the catch moment), e.g. 2026-05-29T16:27:26.085Z — always UTC (Z), not local/ET.
meta.timestamp
UTC ISO8601 timestamp the response was computed.
meta.scan_date
ET (America/New_York) trading-calendar date the rows are from (YYYY-MM-DD, no time) — today on current-cycle calls, or the as_of date. The only ET field; every actual timestamp is UTC.
meta.session
The session these alerts belong to — what you asked for, what "current" resolved to, or "all". (The session lives here, not on each row.)
meta.algorithm_version
Promotion algorithm version (e.g. "v1.1.0"). Bumps when we ship improvements.
meta.count
Number of alerts returned for the scope.
meta.rate_limit_remaining / meta.rate_limit_reset
Same per-minute budget as the watchlist — one key, one shared pool (mirrors the X-RateLimit-* headers). meta.rate_limit_reset is a UTC ISO8601 timestamp.

Informational only. This endpoint republishes factual data from public-domain SEC filings for research purposes. It is not investment advice, not a recommendation to buy/sell/hold, and not an official SEC service. BullAlert is not affiliated with or endorsed by the SEC. Derived labels (e.g. a dilution level) describe what was filed, not a verdict. Always verify against the original filing on SEC.gov/EDGAR.

SEC-EDGAR company intelligence for any US filer — a distilled financial snapshot plus factual filing signals (share-count/dilution level, cash runway, insider activity, material filings), built straight from public-domain SEC filings. Lean by default; the facts, noise filtered.

GEThttps://api.bullalert.ai/v1/edgar/{ticker}

Parameters

Param Default Description
ticker required Path segment — the ticker of a US SEC filer, e.g. /v1/edgar/LASE. 1–8 chars [A-Z0-9.-]. Missing/invalid → 400; a non-filer (OTC/foreign) → 404 unresolved_ticker.
detail false ?detail=true adds a raw block — numeric values, XBRL source-tag provenance, and the full recent-filings list — on top of the lean view.
as_of Point-in-time. ?as_of=2026-01-01 (or an ISO8601 timestamp) reconstructs the company's SEC state as of that date — only filings + XBRL facts filed on or before it are visible (no-lookahead). Omit for the latest live state. meta.as_of echoes the resolved latest-filing date ≤ your value.

Response (lean default)

{
  "data": {
    "ticker": "LASE",
    "company": "Laser Photonics Corp",
    "as_of": "2026-05-22",
    "snapshot": {
      "revenue_ttm": "$6.5M", "net_income_ttm": "-$9.7M", "eps": "-$1.02",
      "cash": "$3.6M", "debt": "$728K", "shares_out": "32.6M", "net_margin": "-149%"
    },
    "signals": {
      "share_growth_1y": "+128%", "runway": "14 mo",
      "insider": "no recent activity", "flags": ["equity_offering_filed", "listing_rule_notice"]
    },
    "latest_filing": {
      "form": "8-K", "filed": "2026-05-22",
      "headline": "notice of delisting or failure to satisfy a listing rule", "url": "https://www.sec.gov/Archives/..."
    }
  },
  "meta": {
    "api_version": "v1", "algorithm_version": "edgar-v1.0.0",
    "source": "SEC EDGAR (public domain)", "as_of": "2026-05-22",
    "cik": "0001807887", "filing_state_hash": "b96431efe6179382",
    "cache_hit": true, "stale": false, "detail": false, "as_of_requested": null
  }
}

Snapshot values are pre-formatted strings; flags lists only what's present (false flags are omitted). No price, P/E, or market cap — those need market data we don't republish; EDGAR gives you what companies filed. Add ?detail=true for raw numbers + XBRL provenance.

Field reference

snapshot
Financial snapshot from the latest 10-K/10-Q XBRL: revenue / net income (TTM), EPS, cash, debt, shares outstanding, net margin. Each is a pre-formatted string or null when the filer hasn’t disclosed it.
signals.share_growth_1y
Factual share-count change over ~1 year (e.g. "+128%"); negative ≈ a reverse split. We publish this fact rather than a "dilution: high/low" verdict — interpretation is left to you.
signals.runway
Months of cash at the trailing burn rate, or "cash-flow positive".
signals.insider
"net buying" | "net selling" | "neutral" | "no recent activity" — netted from recent Form 4s. Context only.
signals.flags
Array of present flags only — each names what was FILED (factual SEC item subjects, never a verdict): equity_offering_filed, listing_rule_notice (8-K 3.01), material_agreement (8-K 1.01), going_concern, bankruptcy_filing, non_reliance (8-K 4.02), acquisition_completed, change_of_control.
latest_filing
The single most-material recent filing (form, date, plain-English headline, SEC URL).
as_of
Date of the latest filing the response reflects (YYYY-MM-DD).
meta.source
Always "SEC EDGAR (public domain)" — raw filing data, informational, not investment advice.
meta.filing_state_hash
Fingerprint of the latest filing state; changes only when a new material filing lands.

Caching & freshness

Watchlist is recomputed by our proprietary algorithm on 5-minute wall-clock boundaries (15:30 / 15:35 / 15:40 …) and cached server-side for the full 5-min window. Polling faster than every 5 minutes returns the same cached payload; once per 5-min bucket is the sweet spot for current consumption.

Alerts are served with a tighter ~1-minute cache — a freshly published alert shows up within ~1 min. Polling once a minute is plenty; the day's record only changes a handful of times.

Rate limits

Every paid plan includes API access. Headers x-ratelimit-limit, x-ratelimit-remaining, and x-ratelimit-reset ship on every response.

Plan Per minute Per day Per month
Weekly / Monthly 30 5K 50K
Yearly 60 20K 500K
B2B / Custom 300+ 100K+ 2M+

Need higher? Talk to us — we build custom plans for partners and trading platforms.

Security & privacy

Every external request flows through the same controls we use ourselves.

  • Header-only authentication. Keys travel in the x-ba-api-key header — never as a query parameter, never in URLs. Server-side only, never embed in client code.
  • One active key per account. Manage everything from your dashboard. Changes take effect within ~5 min.
  • Read-only. No public endpoint mutates BullAlert state. Your key can't affect other members or the underlying data.
  • Minimal surface. The response is exactly the fields documented above — nothing else.
  • TLS + HSTS in production. Plain HTTP is upgraded; certificates rotated automatically.

Build your own agent on the BullAlert watchlist

The same primitive that powers our in-app chatbot is public. Wire it into your own trading bot, spreadsheet, agent, or dashboard. No exclusivity, no rev-share — one API key per account, included with every paid plan.

Three persona starters

Same one-tool API, three different personas. Click through for the full sample prompt for each.

BullAlert is a data and information tool. The API delivers data, not financial advice. Past performance is not indicative of future results. Trading involves risk. You make the final decisions.