Skip to content

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/:id

Path Parameters ​

ParameterTypeRequiredDescription
idstringYesCVE identifier (e.g., CVE-2024-3094). Case-insensitive.

Response ​

json
{
  "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 ​

FieldTypeDescription
successbooleantrue for every 200 response
dataobjectThe aggregated record (see Data Object)
sourceChangesobject | nullPer-source change detection against the previously recorded payload. null when recording failed.
statusobject | nullPublication status flag. null means there is nothing to flag (the usual answer for a published CVE).
qualityobjectCompleteness score for the record
integrityobjectSHA-256 hashes of each source payload
sourceUrlstringThe CVE List V5 JSON file for this id in the CVEProject/cvelistV5 repository; for a reserved or awaiting-publication id, the cve.org record page
timestampstringISO 8601 time the envelope was built

Data Object ​

FieldTypeDescription
cve_idstringNormalized (uppercase) CVE id
sourcesobjectPer-source payloads, keyed by source (see Sources). A source with no record is absent.
primaryobjectNormalized CVE List V5 record. Absent when CVE List V5 has no record and the CVE is known only to other sources.
nixCheckedobjectPresent when the nixpkgs security issue set was read. An absent sources.nix 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.
epssobjectEPSS score (only when FIRST.org has a score)
epssCheckedbooleantrue when FIRST.org answered, so an absent epss is FIRST.org's answer rather than a failed request
kevobjectCISA KEV catalog entry (only when the CVE is listed)
kevCheckedobjectPresent when the KEV catalog was consulted: catalogVersion and dateReleased of that catalog. With no kev, the CVE is not in it.
nixpkgsMappingobjectCandidate nixpkgs attributes for the affected packages (see nixpkgsMapping)
trackerVerdictsobjectAdjudications from the nixpkgs security review (see trackerVerdicts). Present when the CVE touches nixpkgs.
nixFixStatusobjectMerged nixpkgs pull requests and per-channel fix status (see nixFixStatus). Present when the CVE touches nixpkgs.
nixPatchEvidenceobjectPresent when at least one candidate was eligible for applied-patch checking: mode (compute or cached) and counts eligible, fromCache, computed, notChecked.
failedSourcesstring[]Sources that failed to answer (rate limit, timeout, upstream error). Present only on a partial record; see Partial record.
upstreamCommitCheckFailedbooleantrue 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 ​

KeyTypeSource
cve_list_v5objectCVE List V5 (the CVE Program), normalized. Identical to primary.
nvdobjectNIST 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.
githubobjectFirst GitHub Security Advisory for the CVE, as GitHub returns it (ghsa_id, severity, summary, cvss_severities, vulnerabilities, ...)
osvobjectOSV.dev record, as OSV returns it (id, aliases, affected, severity, modified, ...)
nixarraynixpkgs security issues (GitHub issues in NixOS/nixpkgs) 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.

FieldTypeDescription
idstringCVE id
statestringCVE record state, e.g. "PUBLISHED" or "REJECTED"
datePublishedstring | nullWhen the CVE Program published the record
datePublicstring | nullWhen the vulnerability was made public, if the CNA stated it
dateUpdatedstring | nullLast update to the record
dateReservedstring | nullWhen the id was reserved
assignerOrgIdstringUUID of the assigning CNA
assignerShortNamestringShort name of the assigning CNA (e.g. "apache", "redhat")
titlestringCNA-provided title
descriptionstringEnglish description
severitystringLowercase: critical, high, medium, low, or unknown when no score is available
cvssScorenumberCVSS base score (0 when none is available)
cvssVersionstringCVSS version of that score, e.g. "3.1"
cvssVectorstringCVSS vector string
severitySourcestring | nullWhich 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
ssvcobject | nullCISA SSVC decision points: exploitation, automatable, technicalImpact, timestamp
adpobjectADP container summary: cvss, cwes, ssvc, dateUpdated
affectedarrayAffected products: vendor, product, versions (each version, status, and optionally lessThan, versionType, changes), cpes, and where the CNA supplied them packageName, collectionURL, defaultStatus
referencesarray{ url, tags } objects
problemTypesarray{ cweId, description } objects
capecsarrayCAPEC entries
tagsarrayRecord tags
sourcestringAlways "cve-list-v5"

EPSS Object ​

FieldTypeDescription
epssnumberProbability of exploitation in the next 30 days (0-1)
percentilenumberRanking among all scored CVEs (0-1)
datestringScore date (YYYY-MM-DD)

KEV Object ​

Included when the CVE appears in the CISA Known Exploited Vulnerabilities catalog.

FieldTypeDescription
vendorProjectstringVendor or project name
productstringAffected product name
vulnerabilityNamestringVulnerability name
dateAddedstringDate added to the KEV catalog
dueDatestringFederal remediation deadline
requiredActionstringRequired remediation action
knownRansomwareCampaignUsestring"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.

FieldTypeDescription
candidatesarrayOne entry per attribute and channel (may be empty)
attemptednumberAffected packages a mapping was attempted for
outcomesobjectCount of each resolution outcome, e.g. { "ok_fallback": 2 } or { "not_found_mirrored": 3, "empty_mirrored": 1 }
mirroredAtstringPresent when an answer came from the mirrored package index: the time of that copy
carriedarrayPresent 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:

json
{
  "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).

FieldTypeDescription
verdictsarrayOne 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
attemptednumberRequests made to the tracker
outcomesobject{ "ok": 1 } when the tracker answered; { "exception": 1 } or { "http_<status>": 1 } when it did not
byCvebooleantrue: 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.

FieldTypeDescription
cveIdstringCVE id
fixPRsarrayCandidate pull requests: number, title, state, merged, mergedAt, url, createdAt, via (cve_search or tracker_issue), fixesChannel, and for merged ones mergeCommit and baseBranch
channelStatusobjectKeyed by channel (unstable, 26.05, 25.11, ...): fixed (true, or null when no evidence was found), pr, basis (a plain-language reason), and where a fix was found channelRevision, channelObservedAt, carriedByChannel
methodstringHow the evidence was gathered
trackerIssueLinksobjectnixpkgs security issues whose linked pull requests were asked for, read, and unread
checkedAtstringWhen 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:

json
"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:

FieldTypeDescription
typestringREJECTED, RESERVED, DRAFT, or UNPUBLISHED
labelstringDisplay label, e.g. "Rejected CVE", "Draft / Unconfirmed", "Awaiting NVD"
notestringExplanation
confidencestringhigh, medium, or low
nvdPresentbooleanWhether NVD holds a record

Quality Object ​

FieldTypeDescription
scorenumber0-100
gradestringLetter grade (A-F)
missingFieldsstring[]What the record lacks, e.g. official_cve_record, nvd_enrichment, package_mapping, cvss_metrics
strengthsstring[]What the record has
detailstringOne-line summary

sourceChanges Object ​

Keyed by source, one entry per source in data.sources.

FieldTypeDescription
changedbooleanWhether the payload differs from the last recorded one
hashstringSHA-256 of the payload as recorded
previousHashstring | nullThe 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 ​

FieldTypeDescription
sourcesobjectSource key to SHA-256 hash of that source's payload: cve_list_v5, nvd, github, osv, nix
primaryHashstringSHA-256 hash of primary
fetchedAtstringISO 8601 time the sources were fetched
indexedobjectHash (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 ​

bash
curl "https://api.vulnpatch.dev/api/v1/cve/CVE-2021-44228"

Response (trimmed) ​

json
{
  "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 ​

javascript
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');
python
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')
bash
# 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 ​

javascript
// 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:

StateConditionStatusBody
PublishedAt least one source holds a usable record and every source answered200Full envelope
PartialAt least one source holds a usable record, but one or more sources failed to answer200Full envelope with data.failedSources
ReservedThe CVE Program's register lists the id as reserved and no source holds a record200Minimal envelope with status.type = "RESERVED"
Rejected (unheld)The CVE Program's register lists the id as rejected and no source has ingested a record for it200Minimal envelope with status.type = "REJECTED"
Awaiting publicationNo 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 published200Minimal envelope with status.type = "UNPUBLISHED"
DegradedNo 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 placed503Structured error, code: "CVE_LOOKUP_DEGRADED"
UnknownEvery source answered with no record and the register does not list the id404Structured 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.

json
{
  "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) ​

json
{
  "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:

json
"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:

json
"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:

json
"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:

json
{
  "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) ​

json
{
  "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) ​

json
{
  "success": false,
  "error": "Invalid CVE ID format. Expected: CVE-YYYY-NNNNN"
}

Server error (500) ​

json
{
  "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: HIT means the response was served from that cache; X-Cache: MISS means it was built for this request. A cached response returns the envelope as it was built, including its timestamp, integrity.fetchedAt and hashes.
  • Partial records, reserved ids and awaiting-publication ids are cached for shorter periods, as described above. 404 and 503 responses are not cached.

Notes ​

  • CVE IDs are case-insensitive (cve-2024-3094 works)
  • Not all sources have data for every CVE; a source with no record is absent from data.sources and integrity.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; kevChecked identifies 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
json
{ "cves": ["CVE-2024-3094", "CVE-2026-110500", "CVE-2099-99999", "bogus"] }
json
{
  "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"
}
FieldTypeDescription
dataobjectCVE id to status object (see Status Object) or null. Carries the same RESERVED/REJECTED/UNPUBLISHED status types GET /api/v1/cve/:id 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
publishedAtobjectCVE id to CVE List V5 datePublished, when known
invalidIdsarrayEntries that are not well-formed CVE ids, as sent
pendingIdsstring[]Ids with no answer yet, including one whose register check, or recent-delta cache check, itself could not complete; their data entry is null
unknownIdsstring[]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
summaryobjectrequested (valid, deduplicated ids), cached, attempted (live lookups that produced an answer), pending. cached + attempted + pending = requested.
timestampstringISO 8601 time of the response
  • Maximum 50 IDs per request. IDs are normalized and deduplicated.
  • A malformed ID is returned in invalidIds rather than failing the batch.
  • A null entry means there is nothing to flag (the usual answer for a published CVE), unless the id is in pendingIds.
  • publishedAt carries 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 in pendingIds and resolved in the background. Distinguish null from pending using pendingIds, 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 in pendingIds rather than being guessed as unknown.
  • Served with Cache-Control: private, max-age=60.
  • Errors: 400 for a missing or empty cves array (ids is accepted as an alias), more than 50 entries, or no valid ids; 405 for a method other than POST; 500 with "error": "CVE status lookup failed".