Windows Patch Level (API v2)
API version 2.0 (2026-09-26). Implements the Windows Build and KB Data Contract v0.1.
1. Purpose
API v2 takes the Windows build and update facts you collect from each host and returns, per CVE, whether the host is confirmed open, cleared (patched) or needs review, using Microsoft's own fixed builds and update (KB) data. Without a build, BreachSpider can only say a CVE applies to the Windows release; with a build, it can say whether this host is still exposed.
- Cumulative Windows (Windows 10, Windows 11, Server 2016 and later): the host's
os_buildis compared with Microsoft's fixed build for that CVE on that product. At or above means cleared; below means confirmed open. - Legacy Windows (Windows 7, Server 2008 R2, Server 2012 R2): the host's
installed_kbsare checked against the fix KB. A later monthly rollup, or a KB that Microsoft lists as replacing the fix, also counts. For CVEs published after end of support,esu_enrolleddecides the outcome. - Build and KB list disagree: the result is needs review. It is never guessed.
- No
os_build, or a bare "Windows": the host is not patch-resolved. It stays product-level, as in API v1.
API v1, including POST /api/v1/assets/correlate-cves, is unchanged.
2. Authentication
Send an API key in the Authorization header:
Authorization: Bearer bs_live_...
Each key belongs to one BreachSpider organization and only sees that organization's environments.
| Action | Key scope needed |
|---|---|
Submit hosts (POST /api/v2/assets/correlate, POST /api/v2/assets/correlate-csv) | write |
Read stored results (GET /api/v2/assets/results) | read (a read-only key is enough) |
Rate limits, per key: 60 requests per minute and 5,000 hosts per minute. Each call accepts at most 200 hosts. For your initial bulk load, we can raise the limits on your key for a set period. The raised limits lapse automatically at the agreed end time.
Logging: we log each request's time, key, path, HTTP status, host count and duration. We never log request or response bodies.
3. Endpoints
| Method and path | Purpose |
|---|---|
POST /api/v2/assets/correlate | Submit up to 200 Windows hosts (JSON), plus optional installed software, and get results |
POST /api/v2/assets/correlate-csv | The same as a CSV upload (multipart form) |
GET /api/v2/assets/results | Read stored results for an environment, or for one asset_id |
Base URL: https://breachspider.com
4. Host fields
These are the contract fields, with the same names in JSON and CSV. An empty value means unknown.
| Field | Required | Format and meaning |
|---|---|---|
asset_id | yes | Your scrubbed asset identifier, up to 64 characters. It must not be a hostname, IP, MAC or serial. Example: A-0417 |
os_product | yes | Windows ProductName, exactly as the host reports it. Example: Windows Server 2019 Datacenter. Windows 11 reports "Windows 10", so we identify it by build 22000 or higher. |
edition_id | yes | EditionID. Examples: ServerDatacenter, ServerStandard, Enterprise, EnterpriseS (LTSC), Professional |
display_version | no | DisplayVersion, or ReleaseId on older builds. Example: 21H2 |
os_build | yes | Full build 10.0.<CurrentBuild>.<UBR>, digits and dots only. Example: 10.0.17763.6189 |
architecture | yes | x64, x86 or arm64 |
installation_type | for servers | Server, Server Core or Client |
installed_kbs | see note | KB numbers. JSON: a list (["KB5034439","KB5033371"]). CSV: semicolon-separated (KB5034439;KB5033371). Duplicates are ignored. |
kb_source | when installed_kbs is sent | How the KB list was collected (free text). Examples: DISM packages, WMI QuickFixEngineering |
esu_enrolled | no (legacy Windows) | yes or no |
collected_at | yes | Collection time, ISO 8601 UTC. Example: 2026-09-25T14:02:00Z |
KB list states: - installed_kbs has values: the KB list was collected. - installed_kbs is empty and kb_source is filled: collected, none found. - Both are empty, or both are omitted: not collected.
Legacy Windows hosts need a collected KB list to be patch-resolved.
Installed software (optional, stored only for now). Send it in the installed_software array (JSON) or a second CSV. Fields: asset_id, name, publisher, version. List each host in windows_hosts before sending its software.
Never send: hostnames, domain names, IP or MAC addresses, user names, serial numbers, license keys, install paths, or anything naming a site or customer. A field with one of these names is refused with an error, and nothing in that host record is stored.
5. Validation rules
We validate each host on its own. A refused host does not stop the other hosts in the call.
| Rule | Error code |
|---|---|
A field that identifies a host, person or site (hostname, ip_address, mac_address, domain, serial_number, user_name, license_key, install_path, site, customer, ...) | forbidden_field |
asset_id that looks like an IP, MAC or domain name, or is longer than 64 characters (the value is not echoed back) | forbidden_value |
| A field that is not in the contract | unknown_field |
A placeholder value (N/A, -, unknown, none, null, TBD, ...). Send an empty value instead. | placeholder_value |
| A required field is missing | missing_field |
os_product is only "Windows" and there is no os_build | bare_windows |
os_build is not digits and dots | invalid_build |
architecture is not x64, x86 or arm64 | invalid_architecture |
installation_type is not Server, Server Core or Client | invalid_installation_type |
A server edition without installation_type | missing_installation_type |
| A KB that is not "KB" followed by digits | invalid_kb |
installed_kbs without kb_source | missing_kb_source |
esu_enrolled is not yes or no | invalid_esu |
collected_at is not ISO 8601 with a time zone | invalid_collected_at |
Warning (the host is accepted):
| Condition | Warning code |
|---|---|
os_build has three parts (no revision). The host stays product-level and is labelled "needs revision" until the full build is sent. | needs_revision |
6. Results
For each accepted host:
| Field | Meaning |
|---|---|
asset_id | Your identifier |
microsoft_product | The Microsoft product the host was matched to, e.g. Windows Server 2019 |
candidate_products | Set only when the edition was not supplied and several Microsoft products fit the build |
patch_resolved | true when results are based on the build or KB list |
end_of_life | true when the Windows version is past Microsoft's end of support (and ESU, if enrolled) |
map_note | Why a host could not be matched, or which products fit a build-only host |
labels | Any of: product-level (no build), needs revision, product-level (not mapped), resolved by build; edition not supplied, end of life |
counts | Number of CVEs per status: confirmed_open, cleared, needs_review |
cves[] | One entry per CVE: cve_id, status (confirmed open, cleared (patched), needs review), fixed_build, kb, source (the Microsoft Security Update Guide document), severity (Microsoft's rating), known_exploited (confirmed open only), note (why, for needs review or build-only results), priority_rank (1 = first), priority_reason (plain-language reason), fix (available, action, source, derived), references (nvd_url, cve_org_url, vendor_advisories (Microsoft's own advisory pages), cisa_ics_advisories, other_references capped at 25, other_references_total; same rules as the v1 correlate references fields) |
fix_groups[] | One entry per fix action, ranked: fix (e.g. install KB5122876 (latest cumulative, build 10.0.17763.9245)), fix_type (kb, esu, none), kb, kbs_covered (the per-CVE KBs this action supersedes), cve_ids, counts (total, confirmed, known_exploited, exploit_available), highest_score, group_rank |
cves_page | Only when paging is requested: page, page_size, total (after filters), total_unfiltered, has_more |
warnings | For example needs_revision |
Set options.include_cleared (JSON) or include_cleared (CSV form) to false to leave cleared CVEs out of cves[]. They still appear in counts.
Ordering, filters and paging (JSON options, CSV form fields, or /results query parameters, all optional):
| Option | Meaning |
|---|---|
sort | priority (default): confirmed open → needs review → cleared; then known-exploited; then exploit or proof-of-concept available; then installable fix, then ESU-required, then no fix; then BCS, CVSS, EPSS. score: the previous order (confirmed open first, then CVE id). exploit: known-exploited, exploit/PoC, EPSS, CVSS. newest: published date. Any other value returns 422. |
confirmed_only, known_exploited_only, fix_available_only | Filters, off by default. ESU-required fixes count as available. |
cve_page, cve_page_size | Paging, off by default (every CVE is returned, as before). cve_page_size 1–1000. |
Sorting and filters are applied before paging; counts always reports every CVE.
Fix actions for Windows. - Cumulative-servicing Windows (Server 2016 and later, Windows 10/11): every open CVE with a fixed build is grouped under the product's latest cumulative update, whose build is at or above every fixed build, so one install clears them all. The per-CVE KBs it supersedes are listed in kbs_covered. - Legacy servicing (for example Server 2012 R2): Monthly Rollup and Security Only fixes are grouped under the newest monthly rollup the host can install; servicing-stack and standalone security updates keep their own KB. A KB already in the host's installed_kbs is never recommended. - End-of-life hosts not enrolled in ESU: fixes released after end of support are Extended Security Updates, grouped as ESU required (or upgrade the OS), with the ESU KB named in each CVE's priority_reason. They rank below directly installable fixes of the same exploit tier.
data.fix_summary[] lists the top 10 fix actions across every host in the call (rank, fix, product, asset_ids, counts, highest_score). meta.sort and meta.filters echo what was applied.
meta.microsoft_data_as_of is the revision date of the newest Microsoft document behind the results. Microsoft data is refreshed daily, and in full after each monthly release.
End of life. A host is labelled end of life when its Windows version or edition is past Microsoft's end of support. With esu_enrolled: yes, the label applies only after the Extended Security Updates period has ended too.
7. Errors
Errors use BreachSpider's standard error envelope. error.code is the general code (for example RATE_LIMITED). For the v2 errors below, error.detail holds the specific error code, a message and any extra fields such as retry_after. See the example in section 8.
| HTTP | error | When |
|---|---|---|
| 401 | API key missing or invalid | |
| 403 | insufficient_scope | Submitting with a key that lacks the write scope |
| 404 | environment_not_found | environment_id is not in your organization |
| 413 | batch_too_large | More than 200 hosts in one call |
| 422 | Malformed JSON body, a top-level field outside environment_id, windows_hosts, installed_software, options, or an invalid option (e.g. sort not one of priority, score, exploit, newest) | |
| 422 | (per host) | Every host in the call was refused. The response lists them in data.rejected with their error codes. |
| 429 | rate_limited | Per-key limit reached; retry_after gives the seconds to wait |
If some hosts are accepted and others refused, the call returns 200 with the refused hosts in data.rejected.
8. Example
Request: two hosts, plus a third that carries a hostname and is refused. Cleared CVEs are left out, and the list is cut to two CVEs per host here.
POST /api/v2/assets/correlate
Authorization: Bearer bs_live_...
Content-Type: application/json
{
"environment_id": 12,
"windows_hosts": [
{
"asset_id": "A-0417",
"os_product": "Windows Server 2019 Standard",
"edition_id": "ServerStandard",
"display_version": "1809",
"os_build": "10.0.17763.7792",
"architecture": "x64",
"installation_type": "Server",
"collected_at": "2026-09-25T14:02:00Z"
},
{
"asset_id": "A-0533",
"os_product": "Windows Server 2012 R2 Standard",
"edition_id": "ServerStandard",
"os_build": "6.3.9600.21620",
"architecture": "x64",
"installation_type": "Server",
"installed_kbs": [
"KB5031419"
],
"kb_source": "WMI QuickFixEngineering",
"esu_enrolled": "no",
"collected_at": "2026-09-25T14:02:00Z"
},
{
"asset_id": "A-0600",
"hostname": "plc-eng-ws",
"os_product": "Windows 10 Enterprise LTSC 2021",
"edition_id": "EnterpriseS",
"os_build": "10.0.19044.6332",
"architecture": "x64",
"installation_type": "Client",
"collected_at": "2026-09-25T14:02:00Z"
}
],
"options": {
"include_cleared": false
}
}
Response (200):
{
"data": {
"assets": [
{
"asset_id": "A-0417",
"microsoft_product": "Windows Server 2019",
"candidate_products": null,
"patch_resolved": true,
"end_of_life": false,
"labels": [],
"map_note": null,
"counts": {
"confirmed_open": 1653,
"cleared": 3949,
"needs_review": 3
},
"cves": [
{
"cve_id": "CVE-2016-9535",
"status": "confirmed open",
"fixed_build": "10.0.17763.7919",
"kb": "KB5066586",
"source": "https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/2025-Oct",
"severity": "Critical",
"known_exploited": false,
"note": null
},
{
"cve_id": "CVE-2020-17103",
"status": "confirmed open",
"fixed_build": "10.0.17763.8880",
"kb": "KB5094123",
"source": "https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/2020-Dec",
"severity": "Important",
"known_exploited": false,
"note": null
}
],
"warnings": []
},
{
"asset_id": "A-0533",
"microsoft_product": "Windows Server 2012 R2",
"candidate_products": null,
"patch_resolved": true,
"end_of_life": true,
"labels": [
"end of life"
],
"map_note": null,
"counts": {
"confirmed_open": 1937,
"cleared": 2074,
"needs_review": 9
},
"cves": [
{
"cve_id": "CVE-2016-9535",
"status": "confirmed open",
"fixed_build": "6.3.9600.22824",
"kb": "KB5066873",
"source": "https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/2025-Oct",
"severity": "Critical",
"known_exploited": false,
"note": null
},
{
"cve_id": "CVE-2022-0001",
"status": "confirmed open",
"fixed_build": "6.3.9600.21924",
"kb": "KB5036960",
"source": "https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/2024-Apr",
"severity": "Important",
"known_exploited": false,
"note": null
}
],
"warnings": []
}
],
"rejected": [
{
"asset_id": "A-0600",
"errors": [
{
"code": "forbidden_field",
"field": "hostname",
"message": "Field 'hostname' is not accepted: identifying data must not be sent. The field was not stored."
}
]
}
],
"software_stored": 0,
"software_rejected": []
},
"meta": {
"api_version": "2",
"microsoft_data_as_of": "2026-09-25T07:00:00+00:00",
"took_ms": 1793
}
}
A-0417 is a Server 2019 host about a year behind: 1,653 CVEs are confirmed open and 3,949 are cleared by its build. A-0533 is Server 2012 R2 without ESU, past end of support: CVEs Microsoft published after 10 October 2023 are open because no ESU fix can be installed, and older CVEs are checked against its October 2023 monthly rollup. A-0600 was refused and nothing from it was stored. The other hosts in the call were still processed.
Error example (429):
{
"api": {"version": "1.0.0", "request_id": "bs-req-...", "timestamp": "2026-09-26T02:45:49Z"},
"error": {
"code": "RATE_LIMITED",
"message": "Per-key limit is 60 requests and 5000 hosts per minute.",
"detail": {"error": "rate_limited", "message": "Per-key limit is 60 requests and 5000 hosts per minute.", "retry_after": 10}
}
}
9. CSV equivalent
POST /api/v2/assets/correlate-csv takes a multipart form with these parts:
| Part | Content |
|---|---|
environment_id | The environment to write to |
hosts | A UTF-8 CSV file with a header row, using the field names in section 4 as column names |
software | Optional. A CSV file with columns asset_id,name,publisher,version |
include_cleared | Optional. true (default) or false |
sort, confirmed_only, known_exploited_only, fix_available_only, cve_page, cve_page_size | Optional; the same meaning as in section 6 |
asset_id,os_product,edition_id,display_version,os_build,architecture,installation_type,installed_kbs,kb_source,esu_enrolled,collected_at
A-0417,Windows Server 2019 Standard,ServerStandard,1809,10.0.17763.7792,x64,Server,,,,2026-09-25T14:02:00Z
A-0533,Windows Server 2012 R2 Standard,ServerStandard,,6.3.9600.21620,x64,Server,KB5031419,WMI QuickFixEngineering,no,2026-09-25T14:02:00Z
curl -X POST https://breachspider.com/api/v2/assets/correlate-csv \
-H "Authorization: Bearer bs_live_..." \
-F environment_id=12 -F [email protected] -F [email protected]
The CSV upload runs the same validation and the same processing as the JSON call, and returns the same response. The same hosts give identical results either way; and our tests check this on every release.