Security — entry 020 of 30
Microsoft Security Response Center (MSRC)
MSRC's CVRF API serves Microsoft's own machine-readable security-update bulletins: list every bulletin's ID and dates from `/updates`, then pull one full document by ID (e.g. "2026-Jan") from `/cvrf/{id}` -- as CVRF XML by default, or the same CVRF document serialised to JSON under an `Accept: application/json` header. It's a fully public, unauthenticated REST endpoint with no signup or key required, backing tools like the community MsrcSecurityUpdates PowerShell module. Two traps confirmed live: the OData-shaped response envelope is misleading (most `$filter`/`$top` syntax 500s, and there's no CVE-to-bulletin lookup at all), and the separately documented `/csaf/{id}` route rejected all 17 id formats probed with "Invalid ID format" -- so despite the CSAF branding, the JSON this API actually returns is CVRF, not CSAF. The old stored URL pointed at the unrelated Report Abuse API's developer page; the live developer portal for this security-updates API now redirects straight to its published OpenAPI/Swagger definition.
MSRC's CVRF API serves Microsoft's own security-update bulletins. `/updates` lists every bulletin's ID and dates; `/cvrf/{id}` (e.g. `2026-Jan`) fetches one full bulletin's complete vulnerability detail, as CVRF XML by default or the same CVRF document serialised to JSON under an `Accept: application/json` header. Despite the API's name, that JSON is not CSAF -- see the gotchas. The response also carries an `@odata.context` field that makes it look like a full OData service, but most OData query syntax is not actually implemented -- check the gotchas below before reaching for `$filter` or `$top` on anything but the two documented date fields.
GreatAPIs Score
Auth quickstart
- No API key, signup, or auth header is required -- both endpoints below are public, keyless reads, confirmed live.
- `/cvrf/{id}` defaults to XML, and the format is selected by the `Accept` header rather than by the URL. To get JSON instead, send an explicit `Accept: application/json` request header -- confirmed live in this run: a bare request returns `content-type: application/xml`, while adding that header on the same URL returns `content-type: application/json` with the same document reshaped. Query-parameter format selection does not work: `$format=json`, `%24format=json`, `format=json`, `$format=application/json`, and `api-version=v3.0&$format=json` were each probed on `/cvrf/1999-Sep` and all five were silently ignored, still returning `application/xml` with a byte-identical 18,699-byte body; a `.json` suffix on the id (`/cvrf/1999-Sep.json`) instead returns `404 text/plain` with the body `An exception was thrown.`
Your key is stored only in this browser (localStorage) and sent directly to the API — never to greatapis.
List bulletins filtered by initial release date
GEThttps://api.msrc.microsoft.com/cvrf/v3.0/updates?$filter=InitialReleaseDate gt 2026-01-01
{"@odata.context":"https://api.msrc.microsoft.com/$metadata#Updates","value":[{"ID":"2026-Apr","Alias":"2026-Apr","DocumentTitle":"April 2026 Security Updates","Severity":null,"InitialReleaseDate":"2026-04-14T07:00:00Z","CurrentReleaseDate":"2026-07-12T14:49:49Z","CvrfUrl":"https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/2026-Apr"},{"ID":"2026-Feb","Alias":"2026-Feb","DocumentTitle":"February 2026 Security Updates","Severity":null,"InitialReleaseDate":"2026-02-10T08:00:00Z","CurrentReleaseDate":"2026-07-12T15:02:20Z","CvrfUrl":"https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/2026-Feb"},{"ID":"2026-Jan","Alias":"2026-Jan","DocumentTitle":"January 2026 Security Updates","Severity":null,"InitialReleaseDate":"2026-01-13T08:00:00Z","CurrentReleaseDate":"2026-07-13T01:44:37Z","CvrfUrl":"https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/2026-Jan"},{"ID":"2026-Jul","Alias":"2026-Jul","DocumentTitle":"July 2026 Security Updates","Severity":null,"InitialReleaseDate":"2026-07-14T07:00:00Z","CurrentReleaseDate":"2026-07-25T00:29:54Z","CvrfUrl":"https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/2026-Jul"},{"ID":"2026-Jun","Alias":"2026-Jun","DocumentTitle":"June 2026 Security Updates","Severity":null,"InitialReleaseDate":"2026-06-09T07:00:00Z","CurrentReleaseDate":"2026-07-21T14:40:56Z","CvrfUrl":"https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/2026-Jun"},{"ID":"2026-Mar","Alias":"2026-Mar","DocumentTitle":"March 2026 Security Updates","Severity":null,"InitialReleaseDate":"2026-03-10T07:00:00Z","CurrentReleaseDate":"2026-07-12T14:41:49Z","CvrfUrl":"https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/2026-Mar"},{"ID":"2026-May","Alias":"2026-May","DocumentTitle":"May 2026 Security Updates","Severity":null,"InitialReleaseDate":"2026-05-12T07:00:00Z","CurrentReleaseDate":"2026-07-15T01:39:24Z","CvrfUrl":"https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/2026-May"}]}This exact $filter is the one OData query this run confirmed actually works (see gotchas) -- captured directly from a live request, unmodified. Without any filter, /updates returns every bulletin back to 1999 (48,099 bytes at probe time), which is why this example filters instead of truncating.
Developer reference
https://api.msrc.microsoft.com/cvrf/v3.0Gotchas & limits
- Despite the OData-shaped `@odata.context` envelope, most OData query syntax 500s on this API: confirmed live, `/updates?$top=1` returns `500 An exception was thrown.` and `/updates?$filter=ID eq '2026-Jan'` returns `500 {"message":"Internal server error"}`. Only `$filter` on the two fields the project's own docs mention -- `InitialReleaseDate` and `CurrentReleaseDate` -- actually works, as in the worked example above.
- Both endpoints content-negotiate on `Accept`, but their defaults are OPPOSITE -- this is the trap, not a missing format. All four cases probed live in this run on `/cvrf/1999-Sep` and `/updates`: `/cvrf/{id}` returns `application/xml` on a bare request (18,699 bytes) and `application/json` under `Accept: application/json` (12,940 bytes), while `/updates` returns `application/json` on a bare request (48,099 bytes) and `application/xml` under `Accept: application/xml` (66,040 bytes, an `<ArrayOfUpdate>` document). `Accept: text/xml` also works on `/updates`, returning `text/xml` with the same 66,040 bytes. Sending the `Accept` that matches an endpoint's own default is a no-op (probed both ways). So always set `Accept` explicitly rather than relying on a default that differs per endpoint.
- That JSON is CVRF serialised to JSON, NOT CSAF -- don't wire a CSAF parser to it. Confirmed live on `/cvrf/1999-Sep` under `Accept: application/json`, the document has exactly 7 PascalCase CVRF top-level keys: `DocumentTitle`, `DocumentType`, `DocumentPublisher`, `DocumentTracking`, `DocumentNotes`, `ProductTree`, and `Vulnerability`. CSAF 2.0's required top-level keys (`document`, `product_tree`, `vulnerabilities`, snake_case) are all absent -- checked explicitly, all three missing.
- An invalid document ID on `/cvrf/{id}` (confirmed live with `/cvrf/not-a-real-id`) returns a plain-text `404` body reading literally `FileNotFound`, not a JSON error object.
- A `/csaf/{id}` route IS documented -- it is the first of the 14 paths in the live swagger at `api.msrc.microsoft.com/cvrf/v3.0/swagger/v3/swagger.json`, carrying its own `Csaf` tag -- but every id format probed in this run returned `400 Invalid ID format`. The spec declares the id as a bare `type: string` with no `pattern`, `example`, or description, and 17 candidate formats were tried: `2026-Jan`, `2026-jan`, `2026Jan`, `Jan-2026`, `2026-01`, `2026`, `2026-Jan.json`, `2026-Jan-csaf`, `cvrf-2026-Jan`, `CVE-2026-21441`, `CVE-2021-44228`, `2026-21441`, `msrc_cvrf_2026-Jan`, `msrc_cve-2026-21441`, `msrc_cve-2026-21441.json`, `all`, and `latest`. All 17 returned the same rejection, so the route is documented but was not usable from here -- treat it as unusable unless you find a published id format, rather than assuming it is absent. The error body shape depends on the request: bare it is `400 text/plain` with a 17-byte `Invalid ID format`, while under `Accept: application/json` it is the same message JSON-quoted (19 bytes).
- Note that the copy of `swagger.json` committed in the GitHub repo behind this entry's `docsUrl` is STALE relative to the live spec: it lists only 9 paths and no `/csaf` at all, while the live swagger served by the API host lists 14 including `/csaf/{id}`. Enumerate the live spec, not the repo copy. The repo's README doesn't mention CSAF anywhere either (0 hits for `csaf`, 2 for `cvrf`, confirmed live).
- There is no CVE-to-bulletin resolution endpoint. `/cvrf/v3.0/cves/CVE-2021-44228` 404s with an empty body (confirmed live), and no `/cves` path appears among the live swagger's 14 paths -- which are `/csaf/{id}`, `/cvrf/{id}`, `/cvrf({id})`, `/updates`, `/Updates`, `/Updates/$count`, `/updates({key})`, `/updates/{key}`, `/Updates({key})`, `/Updates/{key}`, and the four metadata paths `/`, `/.`, `/$metadata`, `/./$metadata`. Don't build around a CVE lookup that isn't there.
- No CORS support: confirmed live, `/updates` returns no `access-control-allow-origin` header at all, whether the request is bare or carries an `Origin: https://example.com` header -- matches this entry's stored `cors: no`.