Programmatic access
JSON API
The whole dataset, serialised to flat JSON at build time and served straight off the CDN. No authentication, no keys, no rate limits, no cold starts — because there's no server, just files.
Endpoints
/api/index.jsonEndpoint directory and dataset counts.
/api/stats.jsonDataset totals and currency date.
/api/actors.jsonAll actors in summary form, with attribution and counts.
/api/actors/{slug}.jsontry /api/actors/apt29.json ↗Full profile: bio, aliases, tools, techniques, CVEs, campaigns, and every citation.
/api/aliases.jsonFlat normalised alias → actor map. The endpoint to hit when automating designator resolution.
/api/crosswalk.jsonEvery actor's designators across every vendor taxonomy, with correlation quality.
/api/cves.jsonCVE reference table, each with the actors that exploited it.
/api/cves/{id}.jsontry /api/cves/CVE-2023-23397.json ↗Single CVE with per-actor usage detail and sources.
/api/campaigns.jsonAll documented campaigns with sectors, CVEs, and sources.
/api/feed.jsonActivity feed items.
/api/countries.jsonCountries of origin with their actor rosters.
/api/sectors.jsonTarget sectors with the actors that hit them.
/api/vendors.jsonVendor naming taxonomies and how each constructs names.
Resolving an unknown designator
The most common automation task: a report arrives using a taxonomy your pipeline doesn't know. Normalise the string the same way the index does — lowercase, collapse non-alphanumerics to single spaces, trim — then look it up.
const norm = s => s.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
const { aliases } = await fetch(
'https://analysisongoing.netlify.app/api/aliases.json'
).then(r => r.json());
const hits = aliases[norm('IRON RITUAL')];
// [{ slug: 'apt29', name: 'APT29', matched: 'IRON RITUAL',
// kind: 'alias', org: 'secureworks' }]
const profile = await fetch(
`https://analysisongoing.netlify.app/api/actors/${hits[0].slug}.json`
).then(r => r.json());Who exploited a given CVE
curl -s https://analysisongoing.netlify.app/api/cves/CVE-2021-44228.json \
| jq -r '.cve.exploitedBy[] | "\(.firstExploited) \(.name) (\(.country))"'
# 2021-12-01 APT40 (China)
# 2021-12-13 APT41 (China)
# 2022-01-01 MuddyWater (Iran)
# ...Building an ATT&CK Navigator layer
Each profile page offers a one-click Navigator layer download, but the raw technique list is in the API if you'd rather generate your own — for example to diff two actors' coverage.
curl -s https://analysisongoing.netlify.app/api/actors/sandworm.json \
| jq '[.actor.techniques[] | {techniqueID: .tCode, score: 1, comment: .note}]'Notes on use
Stability
Slugs and CVE IDs are stable identifiers and safe to key on. Actor display names occasionally change when a vendor retires a designator; slugs do not.
Currency
Every response carries datasetCurrentAsOf (currently 2025-09-01) and a generated timestamp. Check them — a stale dataset that looks live is worse than no dataset.
Caching
Files are served with a one-hour cache header. They only change when the site is rebuilt.
CORS
Access-Control-Allow-Origin is *, so browser-side fetches work without a proxy.
Attribution
If you build on this, cite the underlying primary sources rather than this site — every record carries them for exactly that reason.