Security — entry 020 of 30

Microsoft Security Response Center (MSRC)

Verified Jul 2026

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.

vulnerabilitiescvecvrfsecurity-updatesmicrosoft
AuthenticationNone requiredCall it straight away — no key, no signup.
HTTPSSupportedTraffic is encrypted in transit.
CORSDisabledBrowser calls need a server-side proxy.
PricingFreeNo paid tier — free for the documented use case.
FormatsJSON, XMLResponses can be requested as JSON or XML.

GreatAPIs Score

Score94out of 100
Authentication25/25No authentication required
Pricing20/20Free to use
Docs14/20Documentation URL provided
Formats15/15Supports 2 response formats
Freshness20/20Verified within 6 months

Embed this badge

Scored 94 on greatapis.com
<a href="https://greatapis.com/api/microsoft-security-response-center-msrc/"><img src="https://greatapis.com/badge/microsoft-security-response-center-msrc.svg" alt="Scored 94 on greatapis.com"></a>

Auth quickstart

  1. No API key, signup, or auth header is required — both endpoints below are public, keyless reads, confirmed live.
  2. /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.
Stored keyNo key stored

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

200 application/json

{"@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

Base URLhttps://api.msrc.microsoft.com/cvrf/v3.0

Gotchas & 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.