GET /api/hotspots-risk-events
Read-only access to the top hotspots from each repository analyzed on hotspots.dev: the highest-risk files from the analysis behind each post, with their risk score, tier and detected patterns. Public — it only exposes already-published analysis data. Want trend history and change alerts? Join the waitlist.
What's in the data
- Each analyzed repository contributes its top few hotspots (around 5), recorded once per analysis. Coverage is the set of repositories I have published analyses for, not arbitrary repos.
- Because these are each repo's top hotspots, nearly all rows are in the
criticaltier today. Filtering by lower tiers will usually return little or nothing. - Repositories are currently analyzed once, so there is no history yet:
deltaisnulluntil a repo is re-analyzed. This will start to populate as repos are revisited. - Rows identify a
file; a file can appear more than once when several of its functions rank. - Data is retained for 90 days.
Request
GET /api/hotspots-risk-events?repo=&since=&risk_tier=&limit=&cursor= | param | type | notes |
|---|---|---|
repo | string | e.g. vuejs/core. Required. |
since | string | ISO 8601 timestamp |
risk_tier | enum | low | moderate | high | critical |
limit | int | page size, 1–200 (default 50) |
cursor | string | opaque, from next_cursor |
Response
{
"events": [
{
"event_id": "evt_01h...",
"repo": "vuejs/core",
"file": "src/reactivity/effect.ts",
"commit_sha": "a1b2c3d",
"risk_score": 26.78,
"risk_tier": "high",
"delta": null,
"patterns": ["god_function"],
"collected_at": "2026-09-15T02:10:55Z",
"timestamp": "2026-09-15T02:11:00Z"
}
],
"next_cursor": "...",
"waitlist_url": "https://hotspots.dev/api-access",
"notice": "Trend history, change alerts and on-demand repos are coming — join the waitlist."
} waitlist_url— landing page for what is coming next (trend history, change alerts, on-demand repos). Safe to ignore.collected_at— when the analysis actually happened, as passed by the writer. Falls back totimestampfor rows written before this field existed.timestamp— write-time timestamp assigned by the backing store. Governs pagination (next_cursor), not meaningful as an analysis date for backfilled data.risk_score—LRSfromhotspots-core/src/risk.rs::calculate_lrs(unbounded float).risk_tier—RiskBandenum from the same module.delta— parent-relative ΔLRS + band transition (hotspots-core/src/delta.rs).nullwhen there's no prior snapshot to diff against. Shape once populated:{ "risk": 4.2, "band_transition": "moderate->high" }.patterns— static pattern-detector labels, e.g.god_function,cyclic_hub,exit_heavy.
Not yet included
author_type(ai/human/mixed) — not produced anywhere inhotspots-cli/hotspots-coretoday.detected_by— scoring is one fixed formula (LRS); there's no per-event method to report.
Rate limits
Public and unauthenticated — no signup required — but rate-limited in two tiers:
30 requests/minute per IP by default, or 300 requests/minute
per API key for a free account. Get a key with POST /api/signup
({ "email": "you@example.com" }) and send it back as
Authorization: Bearer <api_key>. An invalid or missing key falls
back to the per-IP tier rather than being rejected — this is a rate-limit tier, not
an access boundary, since the underlying data is public either way.
Requests are logged (endpoint, query parameters, country, user agent, and hashed key/IP identifiers) to monitor usage and abuse.
Errors
Validation failures return 400 with { "error": "invalid_parameter", "message", "field", "docs" }.
A misconfigured or unreachable query backend returns 502/503.
Exceeding the rate limit returns 429.
Questions: stephen@stephencollins.tech