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
{
"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-keyheader — 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.
Momentum-style bot →
Same-session continuation persona. Asks for the regular-hours watchlist, focuses on the top 10 by score, suggests study — never buys.
Warrior-style bot →
Scalper persona. Asks for the pre-market watchlist at the open, narrows to the highest-momentum names, pulls float / news context from elsewhere.
Fundamental DD bot →
Uses the watchlist as a candidate filter, then digs into filings + dilution via /v1/edgar/{ticker} and sector tools. Combines BullAlert's output with your own research stack.
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.