CVE Lookup
Fetch detailed information about a single CVE, including data aggregated from multiple authoritative sources, nixpkgs security context, and cryptographic integrity hashes.
Endpoint
GET /api/v1/cve/:idPath Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | CVE identifier (e.g., CVE-2024-3094). Case-insensitive. |
Response
{
"success": true,
"data": {
"cve_id": "CVE-2021-44228",
"sources": {
"cve_list_v5": { /* same object as data.primary */ },
"nvd": { /* NVD record */ },
"github": { /* GitHub Security Advisory */ },
"osv": { /* OSV.dev record */ },
"nix": [ /* nixpkgs security issues */ ]
},
"primary": { /* normalized CVE List V5 record */ },
"nixChecked": { "partial": false },
"epss": { "epss": 0.99999, "percentile": 1, "date": "2026-10-15" },
"epssChecked": true,
"kev": { /* CISA KEV entry, only when listed */ },
"kevChecked": { "catalogVersion": "2026.10.15", "dateReleased": "2026-10-15T17:00:11.2795Z" },
"nixpkgsMapping": { /* candidate nixpkgs attributes */ },
"trackerVerdicts": { /* nixpkgs security review adjudications */ },
"nixFixStatus": { /* merged nixpkgs pull requests and per-channel status */ }
},
"sourceChanges": {
"cve_list_v5": { "changed": false, "hash": "c5450e40…", "previousHash": "c5450e40…" },
/* one entry per source in data.sources */
},
"status": null,
"quality": {
"score": 100,
"grade": "A",
"missingFields": [],
"strengths": ["Detailed description", "CVSS score available", /* ... */],
"detail": "Quality: A (100/100). Strengths: Detailed description, CVSS score available, ..."
},
"integrity": {
"sources": {
"cve_list_v5": "c5450e401ade845276033129aecc1a1e8328f149089b0dfbd78b8135620790f2",
/* one hash per source in data.sources */
},
"fetchedAt": "2026-10-15T19:51:20.650Z",
"primaryHash": "c5450e401ade845276033129aecc1a1e8328f149089b0dfbd78b8135620790f2",
"indexed": { "hash": "727082b8…", "lastUpdated": "2026-09-11T03:31:37.729Z" }
},
"sourceUrl": "https://raw.githubusercontent.com/CVEProject/cvelistV5/main/cves/2021/44xxx/CVE-2021-44228.json",
"timestamp": "2026-10-15T19:52:03.840Z"
}Response Fields
Envelope
| Field | Type | Description |
|---|---|---|
success | boolean | true for every 200 response |
data | object | The aggregated record (see Data Object) |
sourceChanges | object | null | Per-source change detection against the previously recorded payload. null when recording failed. |
status | object | null | Publication status flag. null means there is nothing to flag (the usual answer for a published CVE). |
quality | object | Completeness score for the record |
integrity | object | SHA-256 hashes of each source payload |
sourceUrl | string | The CVE List V5 JSON file for this id in the CVEProject repository; for a reserved or awaiting-publication id, the cve.org record page |
timestamp | string | ISO 8601 time the envelope was built |
Data Object
| Field | Type | Description |
|---|---|---|
cve_id | string | Normalized (uppercase) CVE id |
sources | object | Per-source payloads, keyed by source (see Sources). A source with no record is absent. |
primary | object | Normalized CVE List V5 record. Absent when CVE List V5 has no record and the CVE is known only to other sources. |
nixChecked | object | Present when the nixpkgs security issue set was read. An absent sources is a checked negative only when partial is not true. partial: true means the issue set read was incomplete and the absence of a matching issue is inconclusive. |
epss | object | EPSS score (only when FIRST.org has a score) |
epssChecked | boolean | true when FIRST.org answered, so an absent epss is FIRST.org's answer rather than a failed request |
kev | object | CISA KEV catalog entry (only when the CVE is listed) |
kevChecked | object | Present when the KEV catalog was consulted: catalogVersion and dateReleased of that catalog. With no kev, the CVE is not in it. |
nixpkgsMapping | object | Candidate nixpkgs attributes for the affected packages (see nixpkgsMapping) |
trackerVerdicts | object | Adjudications from the nixpkgs security review (see trackerVerdicts). Present when the CVE touches nixpkgs. |
nixFixStatus | object | Merged nixpkgs pull requests and per-channel fix status (see nixFixStatus). Present when the CVE touches nixpkgs. |
nixPatchEvidence | object | Present when at least one candidate was eligible for applied-patch checking: mode (compute or cached) and counts eligible, fromCache, computed, notChecked. |
failedSources | string[] | Sources that failed to answer (rate limit, timeout, upstream error). Present only on a partial record; see Partial record. |
upstreamCommitCheckFailed | boolean | true when an upstream commit comparison for a candidate could not be completed. Present only in that case; the record is cached for the shorter partial-record lifetime. |
Sources
| Key | Type | Source |
|---|---|---|
cve_list_v5 | object | CVE List V5 (the CVE Program), normalized. Identical to primary. |
nvd | object | NIST NVD: cveId, vulnStatus, nvdPublished, nvdLastModified, nvdDescription, cvss, cweIds, cweAssessor, cpeMatches, nvdReferences, tags, cachedAt. When NVD has no record or could not be reached this is { "error": " instead, with "not_found" meaning NVD answered that it holds no record. |
github | object | First GitHub Security Advisory for the CVE, as GitHub returns it (ghsa_id, severity, summary, cvss_severities, vulnerabilities, ...) |
osv | object | OSV.dev record, as OSV returns it (id, aliases, affected, severity, modified, ...) |
nix | array | nixpkgs security issues (GitHub issues in NixOS) naming this CVE |
nvd.cvss.severity is NVD's own uppercase value (for example "CRITICAL"). The normalized primary.severity is always lowercase.
Each nix entry carries id, number, state, title, url, package, packageSource, nixpkgsId, nixpkgsAttrs, channelVersions, cveIds, severity, currentVersion, fixedVersion, createdAt, closedAt, labels, maintainers, assignees, filedBy, dataSource ("nixpkgs-tracker"), isTracked and difficulty.
Primary Object
The normalized CVE List V5 record.
| Field | Type | Description |
|---|---|---|
id | string | CVE id |
state | string | CVE record state, e.g. "PUBLISHED" or "REJECTED" |
datePublished | string | null | When the CVE Program published the record |
datePublic | string | null | When the vulnerability was made public, if the CNA stated it |
dateUpdated | string | null | Last update to the record |
dateReserved | string | null | When the id was reserved |
assignerOrgId | string | UUID of the assigning CNA |
assignerShortName | string | Short name of the assigning CNA (e.g. "apache", "redhat") |
title | string | CNA-provided title |
description | string | English description |
severity | string | Lowercase: critical, high, medium, low, or unknown when no score is available |
cvssScore | number | CVSS base score (0 when none is available) |
cvssVersion | string | CVSS version of that score, e.g. "3 |
cvssVector | string | CVSS vector string |
severitySource | string | null | Which container supplied the score: cna, an ADP container name such as cisa_adp, or osv / github when the record had no score and one was taken from those sources |
ssvc | object | null | CISA SSVC decision points: exploitation, automatable, technicalImpact, timestamp |
adp | object | ADP container summary: cvss, cwes, ssvc, dateUpdated |
affected | array | Affected products: vendor, product, versions (each version, status, and optionally lessThan, versionType, changes), cpes, and where the CNA supplied them packageName, collectionURL, defaultStatus |
references | array | { url, tags } objects |
problemTypes | array | { cweId, description } objects |
capecs | array | CAPEC entries |
tags | array | Record tags |
source | string | Always "cve-list-v5" |
EPSS Object
| Field | Type | Description |
|---|---|---|
epss | number | Probability of exploitation in the next 30 days (0-1) |
percentile | number | Ranking among all scored CVEs (0-1) |
date | string | Score date (YYYY-MM-DD) |
KEV Object
Included when the CVE appears in the CISA Known Exploited Vulnerabilities catalog.
| Field | Type | Description |
|---|---|---|
vendorProject | string | Vendor or project name |
product | string | Affected product name |
vulnerabilityName | string | Vulnerability name |
dateAdded | string | Date added to the KEV catalog |
dueDate | string | Federal remediation deadline |
requiredAction | string | Required remediation action |
knownRansomwareCampaignUse | string | "Known" or "Unknown" |
nixpkgsMapping
Candidate nixpkgs attributes derived from the record's affected packages.
A candidate establishes likely applicability, not a proven vulnerability in a particular nixpkgs build. It does not establish whether a downstream patch, a build flag, a disabled component or a backport removes the vulnerability, beyond what patchEvidence and fixMerged can show from a merged pull request or a matched patch. identity is also a heuristic corroboration, not a confirmed project match. A same-domain basis (same_site) requires the record's own product name to be confirmed on the package's homepage before it counts as corroborated; a domain match with no such confirmation is the separate, explicitly uncorroborated same_site_unconfirmed instead, since a domain can host more than one project under one owner. Proving a specific build is or is not affected would need evaluating that build, which this mapping does not do.
| Field | Type | Description |
|---|---|---|
candidates | array | One entry per attribute and channel (may be empty) |
attempted | number | Affected packages a mapping was attempted for |
outcomes | object | Count of each resolution outcome, e.g. { "ok_fallback": 2 } or { "not_found_mirrored": 3, "empty_mirrored": 1 } |
mirroredAt | string | Present when an answer came from the mirrored package index: the time of that copy |
carried | array | Present when the mirror showed a package is carried in nixpkgs without a candidate row being derived |
Each candidate carries attr, channel, version, status, source, homepages, position, derivedFrom (name, ecosystem), identity (whether the attribute is the same project, with status and basis) and versionVerdict (the package version compared with the affected ranges, with source and status). A candidate can also carry fixMerged when merged fix evidence applies to its channel, patchEvidence from applied-patch checking, and tracker verdict fields when the nixpkgs security review has adjudicated the attribute. A candidate from CVE-2024-3094:
{
"attr": "xz",
"channel": "nixos-26.05",
"version": "5.8.4",
"status": null,
"source": "nixos-search",
"homepages": ["https://tukaani.org/xz/"],
"position": "pkgs/tools/compression/xz/default.nix:82",
"derivedFrom": { "name": "xz", "ecosystem": "tukaani" },
"identity": { "status": "same_site", "basis": "tukaani.org" },
"versionVerdict": { "source": "cve_record", "status": "outside_affected_range" }
}trackerVerdicts
The nixpkgs security review's suggestions for this CVE (tracker.security.nixos.org).
| Field | Type | Description |
|---|---|---|
verdicts | array | One entry per suggestion with a recognised status: suggestionId, status, verdict (adjudicated_affected, adjudicated_not_affected, or tracker_candidate), cveId, packages, url, and consideredAndSetAside when packages were set aside |
attempted | number | Requests made to the tracker |
outcomes | object | { "ok": 1 } when the tracker answered; { "exception": 1 } or { "http_<status>": 1 } when it did not |
byCve | boolean | true: the tracker was asked by CVE id |
An empty verdicts array with outcomes.ok is a checked negative. An empty array with any other outcome means the tracker was not reached.
nixFixStatus
Merged nixpkgs pull requests for this CVE and what they mean for each channel.
| Field | Type | Description |
|---|---|---|
cveId | string | CVE id |
fixPRs | array | Candidate pull requests: number, title, state, merged, mergedAt, url, createdAt, via (cve_search or tracker_issue), fixesChannel, and for merged ones mergeCommit and baseBranch |
channelStatus | object | Keyed by channel (unstable, 26, 25, ...): fixed (true, or null when no evidence was found), pr, basis (a plain-language reason), and where a fix was found channelRevision, channelObservedAt, carriedByChannel |
method | string | How the evidence was gathered |
trackerIssueLinks | object | nixpkgs security issues whose linked pull requests were asked for, read, and unread |
checkedAt | string | When the evidence was gathered |
fixed: null is not evidence a channel is unfixed; basis says why. When the evidence could not be gathered, nixFixStatus is { "error": "...", "checked": false }.
A channel entry from CVE-2021-44228:
"unstable": {
"fixed": true,
"pr": {
"number": 157025,
"title": "arduino: 1.8.16 -> 1.8.19",
"state": "closed",
"merged": true,
"mergedAt": "2022-02-01T12:28:45Z",
"url": "https://github.com/NixOS/nixpkgs/pull/157025",
"createdAt": "2022-01-27T17:01:26Z",
"via": "tracker_issue",
"trackerIssue": 150288,
"fixesChannel": ["unstable"],
"mergeCommit": "4174cd59c8435e405f91a6452cc7c2a9ff57463f",
"baseBranch": "master"
},
"basis": "A merged pull request based on master, which nixos-unstable builds from.",
"channelRevision": "319e4cebef10cbb636c6ea392137b52ad105336a",
"channelObservedAt": "2026-10-15T19:37:48.069Z",
"carriedByChannel": true
}Status Object
status is null for a published CVE with nothing to flag. Otherwise it is:
| Field | Type | Description |
|---|---|---|
type | string | REJECTED, RESERVED, DRAFT, or UNPUBLISHED |
label | string | Display label, e.g. "Rejected CVE", "Draft , "Awaiting NVD" |
note | string | Explanation |
confidence | string | high, medium, or low |
nvdPresent | boolean | Whether NVD holds a record |
Quality Object
| Field | Type | Description |
|---|---|---|
score | number | 0-100 |
grade | string | Letter grade (A-F) |
missingFields | string[] | What the record lacks, e.g. official_cve_record, nvd_enrichment, package_mapping, cvss_metrics |
strengths | string[] | What the record has |
detail | string | One-line summary |
sourceChanges Object
Keyed by source, one entry per source in data.sources.
| Field | Type | Description |
|---|---|---|
changed | boolean | Whether the payload differs from the last recorded one |
hash | string | SHA-256 of the payload as recorded |
previousHash | string | null | The previously recorded hash (null the first time a source is recorded) |
A changed source also carries entry (the change-log entry), regression and archived. A source that was not recorded carries skipped: true and a reason instead of hashes.
The nvd hash here excludes cachedAt, so it differs from integrity.sources.nvd, which hashes the payload as served.
Integrity Object
| Field | Type | Description |
|---|---|---|
sources | object | Source key to SHA-256 hash of that source's payload: cve_list_v5, nvd, github, osv, nix |
primaryHash | string | SHA-256 hash of primary |
fetchedAt | string | ISO 8601 time the sources were fetched |
indexed | object | Hash (hash) and lastUpdated of the archived copy, when one exists |
Data Provenance
Each hash is computed on the payload as it appears in data.sources. Compare hashes across requests made at different times to detect upstream changes; sourceChanges reports the same comparison against the last recorded payload.
Examples
Basic Request
curl "https://api.vulnpatch.dev/api/v1/cve/CVE-2021-44228"Response (trimmed)
{
"success": true,
"data": {
"cve_id": "CVE-2021-44228",
"sources": {
"cve_list_v5": { /* same object as data.primary */ },
"github": { "ghsa_id": "GHSA-jfh8-c2jp-5v3q", "severity": "critical", /* ... */ },
"nix": [
{
"id": "150288",
"number": 150288,
"state": "closed",
"title": "CVE-2021-44228 (Apache log4j) tracking issue",
"url": "https://github.com/NixOS/nixpkgs/issues/150288",
"cveIds": ["CVE-2021-44228"],
"dataSource": "nixpkgs-tracker",
/* ... */
}
/* ... */
],
"osv": { "id": "CVE-2021-44228", "aliases": ["GHSA-jfh8-c2jp-5v3q"], /* ... */ },
"nvd": {
"cveId": "CVE-2021-44228",
"vulnStatus": "Analyzed",
"nvdPublished": "2021-12-10T10:15:09.143",
"nvdLastModified": "2026-08-11T19:33:44.513",
"cvss": {
"version": "3.1",
"score": 10,
"severity": "CRITICAL",
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
"exploitability": 3.9,
"impact": 6,
"assessor": "nvd",
"source": "nvd@nist.gov"
},
"cweIds": ["CWE-917"],
/* ... */
}
},
"primary": {
"id": "CVE-2021-44228",
"state": "PUBLISHED",
"datePublished": "2021-12-10T00:00:00.000Z",
"datePublic": null,
"dateUpdated": "2025-10-21T23:25:23.121Z",
"dateReserved": "2021-11-26T00:00:00.000Z",
"tags": [],
"assignerOrgId": "f0158376-9dc2-43b6-827c-5f631a4d8d09",
"assignerShortName": "apache",
"ssvc": {
"exploitation": "active",
"automatable": "yes",
"technicalImpact": "total",
"timestamp": "2025-02-04T14:25:34.416117Z"
},
"adp": { "cvss": { /* ... */ }, "cwes": [ /* ... */ ], "ssvc": { /* ... */ }, "dateUpdated": "..." },
"title": "Apache Log4j2 JNDI features do not protect against attacker controlled LDAP and other JNDI related endpoints",
"description": "Apache Log4j2 2.0-beta9 through 2.15.0 (excluding security releases 2.12.2, 2.12.3, and 2.3.1) JNDI features ...",
"severity": "critical",
"cvssScore": 10,
"cvssVersion": "3.1",
"cvssVector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
"severitySource": "cisa_adp",
"affected": [
{
"vendor": "Apache Software Foundation",
"product": "Apache Log4j2",
"versions": [ { "version": "2.0-beta9", "status": "affected", "lessThan": "log4j-core*", "versionType": "custom", "changes": [ /* ... */ ] } ],
"cpes": []
}
],
"references": [
{ "url": "https://logging.apache.org/log4j/2.x/security.html", "tags": ["x_transferred"] }
/* ... */
],
"problemTypes": [
{ "cweId": "CWE-502", "description": "CWE-502 Deserialization of Untrusted Data" }
/* ... */
],
"capecs": [],
"source": "cve-list-v5"
},
"nixChecked": { "partial": false },
"epss": { "epss": 0.99999, "percentile": 1, "date": "2026-10-15" },
"epssChecked": true,
"kev": {
"vendorProject": "Apache",
"product": "Log4j2",
"vulnerabilityName": "Apache Log4j2 Remote Code Execution Vulnerability",
"dateAdded": "2021-12-10",
"dueDate": "2021-12-24",
"requiredAction": "For all affected software assets for which updates exist, the only acceptable remediation actions are: 1) Apply updates; OR 2) remove affected assets from agency networks. ...",
"knownRansomwareCampaignUse": "Known"
},
"kevChecked": { "catalogVersion": "2026.10.15", "dateReleased": "2026-10-15T17:00:11.2795Z" },
"nixpkgsMapping": {
"candidates": [],
"attempted": 4,
"outcomes": { "empty_mirrored": 1, "not_found_mirrored": 3 },
"mirroredAt": "2026-10-15T08:28:30.079Z"
},
"trackerVerdicts": { "verdicts": [], "attempted": 1, "outcomes": { "ok": 1 }, "byCve": true },
"nixFixStatus": {
"cveId": "CVE-2021-44228",
"channelStatus": {
"unstable": { "fixed": true, "pr": { "number": 157025, /* ... */ }, /* ... */ },
"26.05": {
"fixed": null,
"pr": null,
"basis": "No merged nixpkgs pull request naming this channel was found in a GitHub search for the CVE id. A channel branched after the fix merged carries it without one, so this is not evidence the channel is unfixed."
}
/* ... */
},
"fixPRs": [ /* ... */ ],
"method": "GitHub issue search for the CVE id, plus the pull requests cross-referenced from the CVE's nixpkgs security issues; a merged PR's real base branch (not its title or body) decides which channel it fixes",
"trackerIssueLinks": { "asked": [150288, 149982, 150153], "read": [150288, 149982, 150153], "unread": [] },
"checkedAt": "2026-10-15T19:52:03.378Z"
}
},
"sourceChanges": {
"github": { "changed": false, "hash": "4f11095a954995f4452832019258310d25c794e079b3371e061e8c69a1b6252b", "previousHash": "4f11095a954995f4452832019258310d25c794e079b3371e061e8c69a1b6252b" },
"nvd": { "changed": false, "hash": "5ec99217dab6b1d17ae07028fe7cef608c7d83cd81eb3f388355a64d40e9768b", "previousHash": "5ec99217dab6b1d17ae07028fe7cef608c7d83cd81eb3f388355a64d40e9768b" },
"cve_list_v5": { "changed": false, "hash": "c5450e401ade845276033129aecc1a1e8328f149089b0dfbd78b8135620790f2", "previousHash": "c5450e401ade845276033129aecc1a1e8328f149089b0dfbd78b8135620790f2" },
"osv": { "changed": false, "hash": "5d9f7bb092c44157c1255d297b699a8d8f24d1acd866544245f10d752f8ee607", "previousHash": "5d9f7bb092c44157c1255d297b699a8d8f24d1acd866544245f10d752f8ee607" },
"nix": { "changed": false, "hash": "e1d4ab939afd33ffda15aa56f3880b006a94fe1b6472ba936c30068667cbb3a8", "previousHash": "e1d4ab939afd33ffda15aa56f3880b006a94fe1b6472ba936c30068667cbb3a8" }
},
"status": null,
"quality": {
"score": 100,
"grade": "A",
"missingFields": [],
"strengths": ["Detailed description", "CVSS score available", "CVSS v3.1", "CWE classification", "Affected versions listed", "References available", "52 references", "Confirmed by 5 sources", "Sources agree on severity", "EPSS/KEV data available"],
"detail": "Quality: A (100/100). Strengths: Detailed description, CVSS score available, CVSS v3.1, CWE classification, Affected versions listed, References available, 52 references, Confirmed by 5 sources, Sources agree on severity, EPSS/KEV data available"
},
"integrity": {
"sources": {
"cve_list_v5": "c5450e401ade845276033129aecc1a1e8328f149089b0dfbd78b8135620790f2",
"nix": "e1d4ab939afd33ffda15aa56f3880b006a94fe1b6472ba936c30068667cbb3a8",
"nvd": "27b1891ba94f61ba7974cc03c165c90e00d15be9e3965d08307e7de645865ed1",
"osv": "5d9f7bb092c44157c1255d297b699a8d8f24d1acd866544245f10d752f8ee607",
"github": "4f11095a954995f4452832019258310d25c794e079b3371e061e8c69a1b6252b"
},
"fetchedAt": "2026-10-15T19:51:20.650Z",
"primaryHash": "c5450e401ade845276033129aecc1a1e8328f149089b0dfbd78b8135620790f2",
"indexed": { "hash": "727082b883df3ff0af8ecca6e16701baf76a766321ce00a3ab78723edcb8711e", "lastUpdated": "2026-09-11T03:31:37.729Z" }
},
"sourceUrl": "https://raw.githubusercontent.com/CVEProject/cvelistV5/main/cves/2021/44xxx/CVE-2021-44228.json",
"timestamp": "2026-10-15T19:52:03.840Z"
}Code Examples
async function lookupCVE(cveId) {
const response = await fetch(`https://api.vulnpatch.dev/api/v1/cve/${cveId}`);
const result = await response.json();
if (!result.success) {
// Branch on result.code: CVE_UNKNOWN (404) or CVE_LOOKUP_DEGRADED (503)
throw new Error(result.code || result.error);
}
const { data, integrity } = result;
if (data.failedSources) {
console.warn(`Partial record; unavailable: ${data.failedSources.join(', ')}`);
}
console.log(`CVE: ${data.cve_id}`);
console.log(`Severity: ${data.primary?.severity}`);
console.log(`CVSS: ${data.primary?.cvssScore}`);
// Verify data integrity
console.log('Integrity hashes:');
for (const [source, hash] of Object.entries(integrity.sources)) {
console.log(` ${source}: ${hash.slice(0, 16)}...`);
}
return result;
}
// Usage
lookupCVE('CVE-2024-3094');import requests
def lookup_cve(cve_id):
response = requests.get(f'https://api.vulnpatch.dev/api/v1/cve/{cve_id}')
result = response.json()
if not result.get('success'):
# Branch on result['code']: CVE_UNKNOWN (404) or CVE_LOOKUP_DEGRADED (503)
raise Exception(result.get('code') or result.get('error'))
data = result['data']
integrity = result['integrity']
if data.get('failedSources'):
print(f"Partial record; unavailable: {', '.join(data['failedSources'])}")
primary = data.get('primary') or {}
print(f"CVE: {data['cve_id']}")
print(f"Severity: {primary.get('severity', 'N/A')}")
print(f"CVSS: {primary.get('cvssScore', 'N/A')}")
# Verify data integrity
print('Integrity hashes:')
for source, hash_value in integrity['sources'].items():
print(f" {source}: {hash_value[:16]}...")
return result
# Usage
lookup_cve('CVE-2024-3094')# Basic lookup
curl -s "https://api.vulnpatch.dev/api/v1/cve/CVE-2024-3094" | jq .
# Extract just the integrity hashes
curl -s "https://api.vulnpatch.dev/api/v1/cve/CVE-2024-3094" | jq '.integrity.sources'
# Get primary severity and CVSS
curl -s "https://api.vulnpatch.dev/api/v1/cve/CVE-2024-3094" | \
jq '{severity: .data.primary.severity, cvssScore: .data.primary.cvssScore, cvssVersion: .data.primary.cvssVersion}'
# Per-channel nixpkgs fix status
curl -s "https://api.vulnpatch.dev/api/v1/cve/CVE-2021-44228" | \
jq '.data.nixFixStatus.channelStatus | map_values({fixed, basis})'Verifying Data Integrity
The integrity hashes let you detect when an upstream source changes its record.
Comparing Hashes
// Compare a stored hash with the current one
async function hasPrimaryChanged(cveId, storedPrimaryHash) {
const response = await fetch(`https://api.vulnpatch.dev/api/v1/cve/${cveId}`);
const result = await response.json();
return result.integrity.primaryHash !== storedPrimaryHash;
}Responses are cached (see Caching), so two requests made within the cache lifetime return the same envelope, including the same integrity.fetchedAt. Compare hashes across longer intervals, or read sourceChanges.
Availability Contract
This endpoint distinguishes these states:
| State | Condition | Status | Body |
|---|---|---|---|
| Published | At least one source holds a usable record and every source answered | 200 | Full envelope |
| Partial | At least one source holds a usable record, but one or more sources failed to answer | 200 | Full envelope with data |
| Reserved | The CVE Program's register lists the id as reserved and no source holds a record | 200 | Minimal envelope with status |
| Rejected (unheld) | The CVE Program's register lists the id as rejected and no source has ingested a record for it | 200 | Minimal envelope with status |
| Awaiting publication | No source holds a record, but the id is listed in the CVE List V5 delta feed, or the CVE Program's register confirms the id is published | 200 | Minimal envelope with status |
| Degraded | No source holds a usable record and the sources that could have held one failed to answer, or the CVE Program's register could not be reached, or answered with a state that cannot be placed | 503 | Structured error, code: "CVE_LOOKUP_DEGRADED" |
| Unknown | Every source answered with no record and the register does not list the id | 404 | Structured error, code: "CVE_UNKNOWN" |
A 503 never means the CVE does not exist; it means the API could not determine whether it does.
Partial record (200 with failedSources)
The record is served with whatever the healthy sources returned, and data.failedSources names the ones that did not answer (any of cve_list_v5, github, nix, osv, nvd). An absent source in a partial record means "not checked", not "no record". A partial record is built with a shorter cache lifetime: the response that builds it carries Cache-Control: public, max-age=60, s-maxage=300, and the server-side copy expires after five minutes, so a later request rebuilds it.
{
"success": true,
"data": {
"cve_id": "CVE-2024-3094",
"sources": { "cve_list_v5": { /* ... */ }, "osv": { /* ... */ }, "nvd": { /* ... */ } },
"primary": { /* ... */ },
/* ... */
"failedSources": ["github", "nix"] /* illustrative: whichever sources failed */
},
/* ... remaining envelope fields ... */
}Reserved (200)
{
"success": true,
"data": { "cve_id": "CVE-2026-110500", "sources": {}, "primary": null },
"status": {
"type": "RESERVED",
"label": "Reserved CVE",
"note": "The CVE Program has reserved this id for a CNA, and no record has been published for it yet.",
"confidence": "high",
"nvdPresent": false
},
"quality": {
"score": 0,
"grade": "F",
"missingFields": ["official_cve_record", "nvd_enrichment", "package_mapping", "cvss_metrics"],
"strengths": [],
"detail": "No authoritative source has published data for this CVE yet."
},
"integrity": { "sources": {}, "fetchedAt": "2026-10-15T19:53:12.719Z" },
"sourceUrl": "https://www.cve.org/CVERecord?id=CVE-2026-110500",
"timestamp": "2026-10-15T19:53:12.719Z"
}Served with Cache-Control: public, max-age=300.
Awaiting publication (200)
The same minimal envelope as a reserved id, with this status instead, served with Cache-Control: public, max-age=60:
"status": {
"type": "UNPUBLISHED",
"label": "Awaiting publication",
"note": "CVE id is listed in the MITRE delta feed but no authoritative record has been published or ingested yet.",
"confidence": "low",
"nvdPresent": false
}This same status.type also covers a higher-confidence case: the CVE Program's own register directly confirms the id is PUBLISHED, but no source has ingested a record for it yet (rather than the id merely appearing in MITRE's recent-delta feed). That case carries a different label, note and confidence, still served with Cache-Control: public, max-age=60:
"status": {
"type": "UNPUBLISHED",
"label": "Published, pending ingest",
"note": "The CVE Program's register confirms this id is published, but no source has ingested a record for it yet. Request ?fresh=true or retry shortly.",
"confidence": "high",
"nvdPresent": false
}Clients should render reserved and awaiting-publication ids as such, not as "Not found". Agents should treat either status.type as a signal to retry later, or to request a refresh via the dossier endpoint with ?fresh=true.
Rejected, no record ingested (200)
The same minimal envelope, served when the CVE Program's own register says the id is REJECTED but no source has ingested a record for it yet. Cache-Control: public, max-age=300:
"status": {
"type": "REJECTED",
"label": "Rejected CVE",
"note": "This CVE has been rejected by the assigning CNA. It may have been a duplicate, invalid, or withdrawn.",
"confidence": "high",
"nvdPresent": false
}This differs from a reserved or awaiting-publication id: clients and agents should not treat a rejected id as pending, and should not use it for remediation decisions.
Degraded (503)
The shape, with an illustrative failedSources:
{
"success": false,
"error": "lookup_degraded",
"code": "CVE_LOOKUP_DEGRADED",
"failedSources": ["cve_list_v5", "github", "osv", "nvd"],
"hint": "Upstream sources (cve_list_v5, github, osv, nvd) were unavailable, so the existence of CVE-2024-3094 could not be determined. Retry later."
}Served with Retry-After: 120 and Cache-Control: no-store. failedSources lists the sources that failed; when the CVE Program's register could not be reached it is ["cve_services"], and when the register has no record for the id but the recent-delta cache used to check for one could not be read, it is ["mitre_recent"]. A failure of the nixpkgs security issue source alone never produces a 503, since that source does not decide whether a CVE exists.
Unknown (404)
{
"success": false,
"error": "not_found",
"code": "CVE_UNKNOWN",
"hint": "No authority has allocated CVE-2099-99999: no source holds a record for it and the CVE Program's register does not list it. Verify the ID follows the CVE-YYYY-NNNNN+ format and check for typos."
}Served with Cache-Control: no-store. Branch on code, not the free-text error. The code field is stable.
Invalid CVE ID (400)
{
"success": false,
"error": "Invalid CVE ID format. Expected: CVE-YYYY-NNNNN"
}Server error (500)
{
"success": false,
"error": "CVE lookup failed"
}Caching
- A complete record is cached for two hours and served with
Cache-Control: public, max-age=300, s-maxage=7200. X-Cache: HITmeans the response was served from that cache;X-Cache: MISSmeans it was built for this request. A cached response returns the envelope as it was built, including itstimestamp,integrity.fetchedAtand hashes.- Partial records, reserved ids and awaiting-publication ids are cached for shorter periods, as described above.
404and503responses are not cached.
Notes
- CVE IDs are case-insensitive (
cve-2024-3094works) - Not all sources have data for every CVE; a source with no record is absent from
data.sourcesandintegrity.sources - EPSS scores are served from cache when available, with a live FIRST.org fallback for uncached CVEs
- KEV data is checked against a locally held copy of the CISA catalog;
kevCheckedidentifies the catalog version consulted - Use the Bulk CVE Lookup endpoint for CVE List V5 records of multiple CVEs
- Use the Exploitability endpoint for bulk EPSS/KEV data
Batch Status
Rendering a list of CVEs usually only calls for the publication status of each row, not the full record. Requesting GET /api/v1/cve/:id once per row is wasteful and will exhaust the per-IP rate limit on a moderately sized page. Ask for the whole list at once instead:
POST /api/v1/cve/status{ "cves": ["CVE-2024-3094", "CVE-2026-110500", "CVE-2099-99999", "bogus"] }{
"success": true,
"data": {
"CVE-2024-3094": null,
"CVE-2026-110500": {
"type": "RESERVED",
"label": "Reserved CVE",
"note": "The CVE Program has reserved this id for a CNA, and no record has been published for it yet.",
"confidence": "high",
"nvdPresent": false
},
"CVE-2099-99999": {
"type": "UNPUBLISHED",
"label": "Unknown CVE",
"note": "No authority has a record of this CVE ID. It may be a typo, or an ID that was later withdrawn or renumbered.",
"confidence": "high",
"nvdPresent": false
}
},
"publishedAt": {
"CVE-2024-3094": "2024-03-29T16:51:12.588Z"
},
"invalidIds": ["bogus"],
"pendingIds": [],
"unknownIds": ["CVE-2099-99999"],
"summary": { "requested": 3, "cached": 2, "attempted": 1, "pending": 0 },
"timestamp": "2026-10-15T19:52:47.628Z"
}| Field | Type | Description |
|---|---|---|
data | object | CVE id to status object (see Status Object) or null. Carries the same RESERVED/REJECTED/UNPUBLISHED status types GET uses -- a reserved, rejected, or awaiting-publication id (including one the register confirms is published but no source has ingested) is never folded into unknownIds |
publishedAt | object | CVE id to CVE List V5 datePublished, when known |
invalidIds | array | Entries that are not well-formed CVE ids, as sent |
pendingIds | string[] | Ids with no answer yet, including one whose register check, or recent-delta cache check, itself could not complete; their data entry is null |
unknownIds | string[] | Ids for which every source answered with no record and the register does not list the id at all -- not used for a reserved, rejected, or register-confirmed-published id, or when the register or recent-delta cache could not be reached |
summary | object | requested (valid, deduplicated ids), cached, attempted (live lookups that produced an answer), pending. cached + attempted + pending . |
timestamp | string | ISO 8601 time of the response |
- Maximum 50 IDs per request. IDs are normalized and deduplicated.
- A malformed ID is returned in
invalidIdsrather than failing the batch. - A
nullentry means there is nothing to flag (the usual answer for a published CVE), unless the id is inpendingIds. publishedAtcarries CVE List v5 publication times when known. Missing entries are unknown, not GitHub advisory publication times.- Answers come from the same cache as
GET /api/v1/cve/:id. A bounded number of cache misses are resolved live per request; the rest, and any lookup that failed or was degraded, are listed inpendingIdsand resolved in the background. Distinguishnullfrom pending usingpendingIds, not by the value itself; request the pending IDs again to get their status. An id the register could not be reached for also lands inpendingIdsrather than being guessed as unknown. - Served with
Cache-Control: private, max-age=60. - Errors:
400for a missing or emptycvesarray (idsis accepted as an alias), more than 50 entries, or no valid ids;405for a method other thanPOST;500with"error": "CVE status lookup failed".