Docs / Cyber Threat Data API / Endpoints / List exploited vulnerabilities
List exploited vulnerabilities
One row per CVE as ONE PUBLISHED CATALOG listed it. The corpus keeps every catalog version CISA has released since this feed started running, so the endpoint resolves a single snapshot before it pages: with no `catalog_version` and no `date` you get the CURRENT catalog and nothing else, which is the ~1,716 rows CISA is serving today. `?date=2026-08-12` gives you the catalog that was in force that day — the question an audit asks, and one CISA's own feed cannot answer, because it is one document replaced in place. Sending both is a `400`: they can name different snapshots, and guessing which you meant is not an answer worth giving about a deadline. `required_action` and `due_date` are the two fields that make this a compliance record rather than a vulnerability list, and `?due_to=<today>` is how you ask what is past its deadline; `?cve=` takes a comma-separated list, which is the filter for “are any of my inventory's CVEs being exploited”. `known_ransomware_campaign_use` is CISA's own `Known` or `Unknown` and is NOT a boolean: `Unknown` means the agency has no evidence, not that there is none, and publishing it as `false` would put our reading out under their name. `cwes` keeps `[]` (classified, no weakness named) apart from `null` (the field was not carried) for the same reason. `order` takes `ingested_at` and nothing else, and it decides very little: a whole catalog arrives in one batch, so every row of one snapshot shares an `ingested_at` and the ordering falls to the id. Page with the cursor as usual, and pin the walk by reading `catalog_version` off page 1 and passing it back — one line, and it closes the one window where a new catalog could land mid-walk.
Query parameters
| Parameter | Type | Description |
|---|---|---|
catalog_version |
string | Serve one published catalog exactly, e.g. `2026.09.18`. Omit it and you get the CURRENT catalog. Read it off page 1 and pass it back to pin a multi-page walk to one snapshot. An unknown value answers an empty page rather than a 400 — the labels are CISA's and we do not assert a format for them. |
date |
date | The catalog as it stood on this day: the newest one released on or before it. This is how you ask what a `due_date` said at the time of an audit. Mutually exclusive with `catalog_version` — sending both is a 400, because they can name different snapshots and guessing which you meant is not an answer worth giving on a dataset that carries deadlines. |
cve |
string | CVE ids, comma-separated — `CVE-2025-39964,CVE-2002-0367`. Case is normalised, and a repeated `?cve=` is unioned with the list rather than overwriting it. This is the filter for "are any of my inventory's CVEs being exploited". |
vendor |
string | The vendor or project, exactly as the row carries it — `Microsoft`, `Linux`. Case-insensitive but not a substring match: `Apache` does NOT return `Apache Tomcat` rows. An unknown value answers an empty page rather than a 400, because this vocabulary is CISA's and grows whenever they catalogue a new publisher. |
ransomware |
string | `known` or `unknown`, CISA's own two values. Closed, unlike `vendor`, because a typo here would mean you believed you had filtered. **`unknown` means CISA has no evidence, not that there is none.** |
added_from |
date | Earliest `date_added`, inclusive. The sync watermark: it is a fact about the catalogue rather than about when we stored the row. |
added_to |
date | Latest `date_added`, inclusive. |
due_from |
date | Earliest `due_date`, inclusive. |
due_to |
date | Latest `due_date`, inclusive. `?due_to=<today>` is everything past its BOD deadline. |
limit |
integer | Items per page, 1–100. Defaults to 25. |
cursor |
string | Opaque cursor from a previous response's meta.next. Do not construct one. |
order |
string | Sort field: `date_released`, each optionally prefixed with `-` to reverse. Default is `-date_released`. Rows with no value for the sort column always sort LAST in either direction, so paging never leads with undated rows. |
source |
string | Restrict to ONE scraper source, by its stem — `abb`, not `ABB` or `abb-bank.az`. Not a list: `?source=a,b` and a repeated `?source=` do not select two sources. On `/v1/jobs` an unknown stem is a `400` naming it, and `source_type` is the way to select several sources at once. `GET /v1/sources` lists every stem. |
Request
curl -G https://api.softon.dev/v1/cyber/kev \
-H "Authorization: Bearer $SOFTON_KEY" \
-d catalog_version=2026.09.18 -d date=2026-08-12 -d cve=CVE-2002-0367
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.softon.dev/v1/cyber/kev", nil) req.Header.Set("Authorization", "Bearer "+os.Getenv("SOFTON_KEY")) q := req.URL.Query() q.Set("catalog_version", "2026.09.18") q.Set("date", "2026-08-12") q.Set("cve", "CVE-2002-0367") req.URL.RawQuery = q.Encode() res, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer res.Body.Close() var page struct { Data []Kev `json:"data"` Meta struct{ Next *string `json:"next"` } `json:"meta"` } if err := json.NewDecoder(res.Body).Decode(&page); err != nil { log.Fatal(err) } // page.Meta.Next → send it back as ?cursor= for the following page
import json, os, urllib.parse, urllib.request url = "https://api.softon.dev/v1/cyber/kev" + "?" + urllib.parse.urlencode({ "catalog_version": "2026.09.18", "date": "2026-08-12", "cve": "CVE-2002-0367", }) req = urllib.request.Request(url, headers={ "Authorization": "Bearer " + os.environ["SOFTON_KEY"], }) page = json.load(urllib.request.urlopen(req)) # page["meta"]["next"] → send it back as ?cursor= for the following page
const url = new URL("https://api.softon.dev/v1/cyber/kev"); url.searchParams.set("catalog_version", "2026.09.18"); url.searchParams.set("date", "2026-08-12"); url.searchParams.set("cve", "CVE-2002-0367"); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.SOFTON_KEY}` }, }); if (!res.ok) throw new Error(`${res.status} ${await res.text()}`); const { data, meta } = await res.json(); // meta.next → send it back as ?cursor= for the following page
Response
The envelope is identical on every softon.dev API: data, meta, error. Only the shape inside data changes per dataset — see the response envelope.
{
"data": [
{
"id": "cisa_kev:2026.09.18:CVE-2026-59310",
"source": "cisa_kev",
"catalog_version": "2026.09.18",
"date_released": "2026-09-18T19:00:05.097400Z",
"catalog_count": 1716,
"cve_id": "CVE-2026-59310",
"vendor_project": "Broadcom",
"product": "VMware vCenter",
"vulnerability_name": "Broadcom VMware vCenter Path Traversal Vulnerability",
"date_added": "2026-08-18",
"due_date": "2026-08-21",
"short_description": "Broadcom VMware vCenter contains a path traversal vulnerability which could allow a threat actor with network access to vCenter to execute arbitrary code.",
"required_action": "Apply mitigations in accordance with vendor instructions, ensuring compliance with CISA’s BOD 26-04 Prioritizing Security Updates Based on Risk (see URL in Notes) guidance and CISA’s “Forensics Triage Requirements” (see URL in Notes). Follow applicable BOD 26-04 guidance for cloud services or discontinue use of the product if mitigations are unavailable. Stakeholders are responsible for evaluating each asset's internet exposure and ensuring adherence to BOD 26-04 patching guidelines.",
"known_ransomware_campaign_use": "Known",
"forensic_triage": "Yes",
"notes": "https://support.broadcom.com/web/ecx/support-content-notification/-/external/content/SecurityAdvisories/0/38017 ; BOD 26-04: https://www.cisa.gov/news-events/directives/bod-26-04-prioritizing-security-updates-based-risk ; Forensics Triage Requirements: https://www.cisa.gov/news-events/directives/bod-26-04-implementation-guidance-prioritizing-security-updates-based-risk ; https://nvd.nist.gov/vuln/detail/CVE-2026-59310",
"cwes": [
"CWE-22"
],
"ingested_at": "2026-09-19T02:00:11.884215Z"
}
],
"meta": { "count": 1, "next": "eyJvIjoyfQ", "request_id": "req_9Fv3" },
"error": null
}
Fields of each item in data[]
| Field | Type | Description |
|---|---|---|
id |
string | Stable identifier, "<source>:<catalog version>:<CVE id>" — `cisa_kev:2026.09.18:CVE-2025-39964`. Opaque: treat the whole string as the id rather than parsing the parts out of it. **It names a SNAPSHOT of a CVE, not the CVE** — the same CVE has a different id in every catalog version that lists it, so an id you stored keeps resolving to the `due_date` you saw rather than silently moving to a newer one. |
source |
string | Which scraper delivered this catalog. `cisa_kev` is CISA's own published feed. |
catalog_version |
string | CISA's own version label for the published document — `2026.09.18`. Treat it as opaque rather than as a date: it is the publisher's rendering, and `date_released` is the fact. **Pass it back as `?catalog_version=` to pin a walk to one snapshot.** |
date_released |
timestamp | When that catalog was published. A real instant with a time of day in it, not a date — the measured release was 19:00:05 UTC. |
catalog_count |
integer | CISA's own count of the records in that document, **verbatim and never reconciled** with the number of rows served. It has agreed on every fetch measured; if it ever does not, that disagreement is a fact about the upstream document and replacing it with our arithmetic would delete the only evidence of it. |
cve_id |
string | The CVE identifier, `CVE-2025-39964`. **The year in it is the year the CVE was assigned, not the year CISA catalogued it** — `CVE-2002-0367` was added to this catalog in 2022. `date_added` is the other fact and neither is derivable from the other. |
vendor_project |
string · nullable | The upstream's own label for the vendor or project — `Microsoft`, `Linux`, `Cisco`. Free text rather than a taxonomy this platform maintains, so `?vendor=` is a case-insensitive EXACT match: `Apache` and `Apache Tomcat` are two entries here and this API will not collapse them for you. |
product |
string · nullable | The upstream's own label for the affected product. |
vulnerability_name |
string · nullable | CISA's short title for the vulnerability. |
date_added |
date · nullable | The day CISA added this CVE to the catalog. **This is the watermark to sync on** — "what has been catalogued since Tuesday" is a question about the catalogue, where `ingested_at` is a question about us. `?added_from=` takes it. |
due_date |
date · nullable | The Binding Operational Directive deadline for `required_action`. **`?due_to=<today>` is how you ask what is past it.** |
short_description |
string · nullable | CISA's own summary of the vulnerability. |
required_action |
string · nullable | The BOD instruction, verbatim and at length. It usually names the directive it comes from and points at `notes` for the URL. Together with `due_date` this is what makes a row a compliance instrument rather than a vulnerability description — and what makes a stale copy of one harmful rather than merely old. |
known_ransomware_campaign_use |
string | `Known` or `Unknown`, **CISA's own value and not a boolean**. `Unknown` is the agency saying it has no evidence of ransomware use — not that there is none — so publishing it as `false` would put our coercion out under their name. 360 of 1,716 were `Known` on the measured catalog. `?ransomware=known` takes the same vocabulary, lower-cased. |
forensic_triage |
string | `Yes` or `No`, CISA's own value. This one genuinely is a boolean and is still carried as published, so this collection has one rule rather than two. |
notes |
string · nullable | CISA's free-text note, usually carrying the vendor advisory URL and the BOD reference. |
cwes |
array of string · nullable | The upstream's CWE identifiers, `["CWE-362"]`. **An empty array and `null` mean different things and both occur**: `[]` is "CISA classified this and named no weakness" (175 of 1,716 on the measured catalog) and `null` is "CISA did not carry the field". They are not collapsed. |
ingested_at |
timestamp | When this platform stored this snapshot. A whole catalog arrives in one batch, so every row of one `catalog_version` shares this to the microsecond — it dates the snapshot, not the record. |
Try it
Send the request to see a response.