Skip to content

CVE History ​

Query the change history of a CVE or retrieve its state at a specific point in time. Useful for audit trails and tracking how vulnerability severity evolved.

Get CVE History ​

Retrieve retained change snapshots for a CVE from the last 24 months. This is not a complete upstream history.

Snapshot sources describe the record at that observation, not every source currently available. Older snapshots affected by a source-recording bug may have an empty list. When the same CVE's archived record has the exact snapshot hash, the response recovers its sources and includes sourcesRecovery with basis: "matching_archived_record", hash and historyKey. The original audit entry, observation time and hash are not rewritten. Verified recoveries are stored separately by CVE and snapshot hash for two years, so later archive updates do not erase them. Recovery checks cover retained missing-source hashes in batches of at most 100 keys, with at most four concurrent bulk lookups.

If the archived version cannot be matched or read, sources stay empty. The service never copies today's index sources into an older snapshot. Empty sources mean provenance was not recorded or could not be recovered, not that the vulnerability has no authoritative source.

Endpoint ​

GET /api/v1/cve/:id/history

Parameters ​

ParameterTypeRequiredDescription
idstringYesCVE identifier (e.g., CVE-2024-45490)

Response ​

json
{
  "success": true,
  "data": {
    "cveId": "CVE-2024-45490",
    "currentState": {
      "id": "CVE-2024-45490",
      "severity": "high",
      "cvssScore": 8.1,
      "state": "PUBLISHED",
      "publishedAt": "2024-09-15T12:00:00Z",
      "hash": "abc123...",
      "sources": ["mitre", "nvd"]
    },
    "history": [
      {
        "timestamp": "2024-09-15T12:00:00Z",
        "changeType": "created",
        "hash": "def456...",
        "severity": "medium",
        "cvssScore": 5.3,
        "state": "PUBLISHED",
        "sources": ["mitre"]
      },
      {
        "timestamp": "2024-09-20T08:30:00Z",
        "changeType": "updated",
        "hash": "abc123...",
        "severity": "high",
        "cvssScore": 8.1,
        "state": "PUBLISHED",
        "sources": ["mitre", "nvd"],
        "changes": [
          { "field": "severity", "from": "medium", "to": "high" },
          { "field": "cvssScore", "from": 5.3, "to": 8.1 }
        ]
      }
    ],
    "totalChanges": 2
  },
  "timestamp": "2026-02-06T14:36:33.927Z"
}

Response Fields ​

FieldTypeDescription
cveIdstringThe CVE identifier
currentStateobjectCurrent state from the CVE index (may be null)
historyarrayArray of historical snapshots
totalChangesnumberTotal number of recorded changes

History Snapshot Object ​

FieldTypeDescription
timestampstringISO 8601 timestamp of the change
changeTypestringType of change: created, updated, severity_changed
hashstringSHA-256 hash of the CVE data at this point
severitystringSeverity level at this point
cvssScorenumberCVSS score at this point
statestringCVE state (PUBLISHED, REJECTED, etc.)
sourcesarrayData sources at this point
changesarray(Optional) Specific field changes from previous state

Query CVE at Date ​

Retrieve the state of a CVE at a specific point in time.

Endpoint ​

GET /api/v1/cve/:id/at/:date

Parameters ​

ParameterTypeRequiredDescription
idstringYesCVE identifier (e.g., CVE-2024-45490)
datestringYesTarget date in ISO 8601 format (e.g., 2025-01-01 or 2025-01-01T12:00:00Z)

Response ​

json
{
  "success": true,
  "data": {
    "found": true,
    "cveId": "CVE-2024-45490",
    "targetDate": "2025-01-01T00:00:00.000Z",
    "snapshotDate": "2024-12-15T08:30:00.000Z",
    "state": {
      "timestamp": "2024-12-15T08:30:00.000Z",
      "changeType": "updated",
      "hash": "abc123...",
      "severity": "high",
      "cvssScore": 8.1,
      "state": "PUBLISHED",
      "sources": ["mitre", "nvd"]
    },
    "totalSnapshots": 5
  },
  "timestamp": "2026-02-06T14:40:00.000Z"
}

Response Fields (at date) ​

FieldTypeDescription
foundbooleanWhether a snapshot was found for the date
cveIdstringThe CVE identifier
targetDatestringThe requested date
snapshotDatestringDate of the closest snapshot on or before target
stateobjectThe CVE state at that point
totalSnapshotsnumberTotal historical snapshots available
earliestKnownstring(If not found) Earliest known date for this CVE, when every snapshot postdates the target
partialboolean(If not found) Present and true only when an audit bucket could not be read; failedBuckets names them and note says the absence is not evidence

A plain found: false with neither earliestKnown nor partial is a genuine absence from the audit log. Buckets begin in 2020, so an earlier date is read from there.

Examples ​

bash
# Get full history for a CVE
curl "https://api.vulnpatch.dev/api/v1/cve/CVE-2024-45490/history"

# Query CVE state on January 1st, 2025
curl "https://api.vulnpatch.dev/api/v1/cve/CVE-2024-45490/at/2025-01-01"

# Query with specific time
curl "https://api.vulnpatch.dev/api/v1/cve/CVE-2024-45490/at/2025-01-01T12:00:00Z"

Code Examples ​

javascript
async function getCVEHistory(cveId) {
  const response = await fetch(
    `https://api.vulnpatch.dev/api/v1/cve/${cveId}/history`
  );
  const { data } = await response.json();

  console.log(`CVE: ${data.cveId}`);
  console.log(`Total changes: ${data.totalChanges}`);

  for (const snapshot of data.history) {
    console.log(`[${snapshot.timestamp}] ${snapshot.changeType}: severity=${snapshot.severity}`);
    if (snapshot.changes) {
      for (const change of snapshot.changes) {
        console.log(`  - ${change.field}: ${change.from} -> ${change.to}`);
      }
    }
  }

  return data;
}

async function getCVEAtDate(cveId, date) {
  const response = await fetch(
    `https://api.vulnpatch.dev/api/v1/cve/${cveId}/at/${date}`
  );
  const { data } = await response.json();

  if (data.found) {
    console.log(`CVE ${cveId} on ${date}: severity=${data.state.severity}`);
  } else {
    console.log(`CVE not found at ${date}, earliest: ${data.earliestKnown}`);
  }

  return data;
}

getCVEHistory('CVE-2024-45490');
getCVEAtDate('CVE-2024-45490', '2025-01-01');
python
import requests

def get_cve_history(cve_id):
    url = f"https://api.vulnpatch.dev/api/v1/cve/{cve_id}/history"
    response = requests.get(url)
    data = response.json()['data']

    print(f"CVE: {data['cveId']}")
    print(f"Total changes: {data['totalChanges']}")

    for snapshot in data['history']:
        print(f"[{snapshot['timestamp']}] {snapshot['changeType']}: severity={snapshot['severity']}")
        if 'changes' in snapshot:
            for change in snapshot['changes']:
                print(f"  - {change['field']}: {change['from']} -> {change['to']}")

    return data

def get_cve_at_date(cve_id, date):
    url = f"https://api.vulnpatch.dev/api/v1/cve/{cve_id}/at/{date}"
    response = requests.get(url)
    data = response.json()['data']

    if data['found']:
        print(f"CVE {cve_id} on {date}: severity={data['state']['severity']}")
    else:
        print(f"CVE not found at {date}, earliest: {data.get('earliestKnown')}")

    return data

get_cve_history('CVE-2024-45490')
get_cve_at_date('CVE-2024-45490', '2025-01-01')

Use Cases ​

  • Audit trails: Track how a CVE's severity evolved over time
  • Compliance reporting: Document CVE state at specific compliance checkpoints
  • Incident response: Determine what was known about a CVE at incident time
  • Research: Analyze patterns in CVE severity changes

Data Retention ​

  • Historical snapshots are retained for 2 years
  • Data is stored in monthly buckets (cve_audit_YYYY_MM)
  • History is recorded automatically when CVEs are updated during sync

What an empty history means ​

The audit log records a snapshot when the indexed record changes. A CVE indexed once and never revised has at most one snapshot, and one indexed before the log existed, or never indexed at all, has none: history is then [], totalChanges is 0 and currentState is null. That is a statement about this service's log, not about upstream activity. For the upstream payloads themselves, hashed per source and diffed field by field, use GET /api/v1/cve/:id/provenance.

Both routes are in the spec as /cve/{id}/history and /cve/{id}/at/{date}.