{
  "openapi": "3.1.0",
  "info": {
    "title": "VulnSea API",
    "version": "1.0.0",
    "description": "Newly released CVE and 0day intelligence across every platform — sleek to read, verbose on demand, served raw as markdown for AI/agent ingestion.\n\nTiered access: the latest 100 CVEs (by ingestedAt) are free and require no auth. Older CVEs and the full corpus require an API key (`Authorization: Bearer vsk_…`, create one at /account) or a logged-in browser session. List/search/digest responses include an `access` field (`public` | `full`); anonymous results are capped to the public window.\n\nIncremental polling: call /api/cve?since=<lastIngestedAt>&order=asc, follow the `next` cursor until null, and send If-None-Match for 304s.\n\nDepth triage: every CVE carries a `depth` zone (severity + exploitation signal). Filter any list/digest/search with `depth=` — `depth=hadal` is critical & actively exploited, `depth=abyssal` is critical with a public exploit.\n\nDepth zones (shallow→deep): sunlit = low / informational; twilight = medium, or low/medium with a signal; midnight = high, or medium with a signal; abyssal = critical, or high with a public exploit / in-the-wild use; hadal = critical and actively exploited (CISA KEV / 0day).\n\nCost controls for agents: `fields=` returns a sparse fieldset (fewer tokens); `ids=` batch-fetches a known set in one request; `probe=1` (or a HEAD request) returns only the corpus size + newest ingest timestamp so you can skip a fetch when nothing changed.\n\nMCP: an MCP server (JSON-RPC 2.0 over Streamable HTTP) is at POST /api/mcp for drop-in use by Claude/Cursor-style agents — tools: list_cves, search_cve, get_cve, batch_get_cve, triage_sbom, related_cve, changes_since, resolve_id, get_epss_movers, get_stats. Same auth + tiering as the REST API.\n\nDependency triage: POST /api/sbom a list of purls (or {ecosystem,name,version}) to get the CVEs affecting each, ranked by depth with a versionMatch. Correlation: GET /api/cve/{id}/related for sibling flaws connected by shared product / vendor / CWE / advisory.\n\nChange feed: GET /api/changes streams per-CVE field-change events (KEV added, severity bumped, exploit signal flipped, material EPSS move) with an exact numeric cursor — /api/cve?since= answers what's NEW, /api/changes answers what CHANGED about records you already triaged.\n\nOSV compatibility: POST /api/osv/v1/query and /api/osv/v1/querybatch speak the osv.dev request/response schema, so existing OSV clients work unchanged; results carry VulnSea signal (depth, depthScore, KEV, EPSS) under database_specific."
  },
  "servers": [
    {
      "url": "https://vulnsea.com"
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "vsk_*",
        "description": "VulnSea API key. Also accepted via the x-api-key header."
      },
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "authjs.session-token",
        "description": "Browser session cookie (logged-in users)."
      }
    },
    "schemas": {
      "Cve": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "CVE-2021-44228"
          },
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "severity": {
            "type": "string",
            "enum": [
              "critical",
              "high",
              "medium",
              "low",
              "none"
            ]
          },
          "cvss": {
            "type": "number",
            "nullable": true
          },
          "cvssVector": {
            "type": "string",
            "nullable": true
          },
          "cwe": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ],
            "nullable": true
          },
          "vendor": {
            "type": "string",
            "nullable": true
          },
          "product": {
            "type": "string",
            "nullable": true
          },
          "platforms": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "affected": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "patched": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "exploited": {
            "type": "boolean",
            "description": "Observed exploited in the wild (CISA KEV / inthewild.io / evidence)"
          },
          "exploitAvailable": {
            "type": "boolean",
            "description": "Public exploit/PoC code exists (Exploit-DB)"
          },
          "zeroDay": {
            "type": "boolean",
            "description": "Exploited at or before public disclosure"
          },
          "epss": {
            "type": "number",
            "nullable": true,
            "description": "FIRST.org EPSS probability 0..1"
          },
          "epssPercentile": {
            "type": "number",
            "nullable": true
          },
          "kev": {
            "type": "boolean",
            "description": "Listed in CISA KEV catalog"
          },
          "kevDateAdded": {
            "type": "string",
            "nullable": true
          },
          "kevDueDate": {
            "type": "string",
            "nullable": true
          },
          "kevRansomware": {
            "type": "boolean",
            "nullable": true
          },
          "published": {
            "type": "string",
            "format": "date",
            "description": "Upstream publish date"
          },
          "updated": {
            "type": "string",
            "nullable": true
          },
          "ingestedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When VulnSea added the record — use for incremental polling"
          },
          "source": {
            "type": "string",
            "nullable": true,
            "enum": [
              "NVD",
              "GHSA",
              "manual"
            ]
          },
          "sourceUrl": {
            "type": "string",
            "nullable": true
          },
          "references": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "nullable": true
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "depth": {
            "type": "string",
            "enum": [
              "sunlit",
              "twilight",
              "midnight",
              "abyssal",
              "hadal"
            ],
            "description": "Composite triage zone key = severity + exploitation signal (shallow→deep). abyssal = critical with a public exploit / in-the-wild; hadal = critical and actively exploited. Human labels/meanings are in info.description (sent once, not per row, to keep responses small)."
          },
          "depthScore": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Composite 0–100 urgency score blending impact (CVSS/severity), likelihood (EPSS) and proof of exploitation (KEV/0day/in-the-wild/PoC). Intentionally discounts un-exploited criticals below exploited highs. Sort the feed by it in the UI with sort=risk."
          },
          "aliases": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true,
            "description": "Other identifiers for the same flaw (e.g. the GHSA id). Resolve any id via /api/resolve."
          }
        },
        "required": [
          "id",
          "slug",
          "title",
          "summary",
          "severity",
          "published"
        ]
      },
      "DepthScoreParts": {
        "type": "object",
        "description": "Additive components of depthScore (pre-clamp): score ≈ impact + likelihood + exploitation + ransomware. Returned by /api/cve/{id} so the ranking is explainable.",
        "properties": {
          "impact": {
            "type": "number",
            "description": "CVSS/severity · 0–55"
          },
          "likelihood": {
            "type": "number",
            "description": "EPSS · 0–20"
          },
          "exploitation": {
            "type": "number",
            "description": "KEV/0day 25 · in-the-wild 18 · public PoC 12 · else 0"
          },
          "ransomware": {
            "type": "number",
            "description": "Known ransomware campaign use · 0 or 5"
          }
        }
      },
      "ChangeEvent": {
        "type": "object",
        "properties": {
          "seq": {
            "type": "integer",
            "description": "Strictly increasing event cursor"
          },
          "id": {
            "type": "string",
            "description": "CVE id the change belongs to"
          },
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "field": {
            "type": "string",
            "enum": [
              "severity",
              "cvss",
              "epss",
              "kev",
              "exploited",
              "exploit_available",
              "zero_day"
            ]
          },
          "old": {
            "type": "string",
            "nullable": true
          },
          "new": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "CveDigest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "severity": {
            "type": "string",
            "enum": [
              "critical",
              "high",
              "medium",
              "low",
              "none"
            ]
          },
          "cvss": {
            "type": "number",
            "nullable": true
          },
          "epss": {
            "type": "number",
            "nullable": true
          },
          "kev": {
            "type": "boolean"
          },
          "exploited": {
            "type": "boolean"
          },
          "depth": {
            "type": "string",
            "enum": [
              "sunlit",
              "twilight",
              "midnight",
              "abyssal",
              "hadal"
            ],
            "description": "Depth zone key"
          },
          "published": {
            "type": "string"
          },
          "ingestedAt": {
            "type": "string"
          },
          "md": {
            "type": "string",
            "description": "URL of the raw markdown"
          }
        }
      },
      "CvePage": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "next": {
            "type": "string",
            "nullable": true,
            "description": "Cursor for the next page, or null"
          },
          "generated": {
            "type": "string"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Cve"
            }
          }
        }
      }
    }
  },
  "security": [
    {},
    {
      "apiKey": []
    },
    {
      "sessionCookie": []
    }
  ],
  "paths": {
    "/api/cve": {
      "get": {
        "operationId": "listCves",
        "summary": "List / poll / batch / probe CVEs",
        "description": "Filtered, ordered, cursor-paginated. Supports since + If-None-Match (304), `ids=` batch fetch, `fields=` sparse fieldsets, and `probe=1` for a cheap freshness check.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Full-text filter"
          },
          {
            "name": "severity",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated: critical,high,…"
          },
          {
            "name": "vendor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "exploited",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "depth",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "sunlit",
                "twilight",
                "midnight",
                "abyssal",
                "hadal"
              ]
            },
            "description": "Filter by depth zone (severity + exploitation). e.g. depth=hadal for the most urgent."
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "ISO date or epoch ms; ingestedAt strictly greater"
          },
          {
            "name": "order",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            },
            "description": "By ingestedAt"
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from a previous response's `next`"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 50
            }
          },
          {
            "name": "ids",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Batch fetch a specific set in one round-trip — comma-separated CVE IDs (max 200). Returns matched records in request order; unknown IDs are skipped. Cursor/order/since are ignored."
          },
          {
            "name": "fields",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Sparse fieldset — comma-separated subset of Cve fields (e.g. fields=id,severity,depth,cvss). Trims the response to cut tokens/egress; unknown names are dropped, id is always returned."
          },
          {
            "name": "probe",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Any value → return only { total, maxIngestedAt, generated }; no row bodies."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of CVEs (or a probe summary)",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CvePage"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified (If-None-Match matched)"
          },
          "401": {
            "description": "Invalid API key"
          }
        }
      },
      "head": {
        "operationId": "probeCves",
        "summary": "Freshness probe (headers only)",
        "description": "Body-less. Returns x-total-count and x-max-ingested-at headers plus an ETag, so pollers can decide whether to fetch.",
        "responses": {
          "200": {
            "description": "Probe headers",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              },
              "x-total-count": {
                "schema": {
                  "type": "integer"
                }
              },
              "x-max-ingested-at": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      }
    },
    "/api/cve/{id}": {
      "get": {
        "operationId": "getCve",
        "summary": "Get one CVE (structured)",
        "description": "Full record incl. markdown body, plus depthScoreParts (why the score is what it is) and `changes` (recent field-change events for this record).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "CVE record",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Cve"
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/cve/{id}.md": {
      "get": {
        "operationId": "getCveMarkdown",
        "summary": "Get one CVE as raw markdown (agent ingestion)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Markdown",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/digest": {
      "get": {
        "operationId": "getDigest",
        "summary": "Compact triage digest",
        "description": "One line per CVE for cheap scanning. Same filters/paging as /api/cve.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Full-text filter"
          },
          {
            "name": "severity",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated: critical,high,…"
          },
          {
            "name": "vendor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "exploited",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "depth",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "sunlit",
                "twilight",
                "midnight",
                "abyssal",
                "hadal"
              ]
            },
            "description": "Filter by depth zone (severity + exploitation). e.g. depth=hadal for the most urgent."
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "ISO date or epoch ms; ingestedAt strictly greater"
          },
          {
            "name": "order",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            },
            "description": "By ingestedAt"
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from a previous response's `next`"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 50
            }
          },
          {
            "name": "ids",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Batch fetch a specific set in one round-trip — comma-separated CVE IDs (max 200). Returns matched records in request order; unknown IDs are skipped. Cursor/order/since are ignored."
          },
          {
            "name": "fields",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Sparse fieldset — comma-separated subset of Cve fields (e.g. fields=id,severity,depth,cvss). Trims the response to cut tokens/egress; unknown names are dropped, id is always returned."
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "text",
                "ndjson"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Digest page",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer"
                    },
                    "next": {
                      "type": "string",
                      "nullable": true
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CveDigest"
                      }
                    }
                  }
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/x-ndjson": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/search": {
      "get": {
        "operationId": "searchCves",
        "summary": "Typo-tolerant full-text search",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "severity",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "exploited",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "kev",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "depth",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "sunlit",
                "twilight",
                "midnight",
                "abyssal",
                "hadal"
              ]
            },
            "description": "Filter by depth zone (severity + exploitation). e.g. depth=hadal for the most urgent."
          },
          {
            "name": "fields",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Sparse fieldset — comma-separated subset of Cve fields (e.g. fields=id,severity,depth,cvss). Trims the response to cut tokens/egress; unknown names are dropped, id is always returned."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Search results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "query": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Cve"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing query"
          }
        }
      }
    },
    "/api/cve/{id}/related": {
      "get": {
        "operationId": "getRelatedCves",
        "summary": "CVEs related to a given id",
        "description": "Sibling flaws connected by shared product / vendor / CWE / references / tags, with the reasons and a relation score (highest first). Walk an attack chain or find other flaws in the same component.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Related CVEs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "access": {
                      "type": "string",
                      "enum": [
                        "public",
                        "full"
                      ]
                    },
                    "related": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "severity": {
                            "type": "string",
                            "enum": [
                              "critical",
                              "high",
                              "medium",
                              "low",
                              "none"
                            ]
                          },
                          "depth": {
                            "type": "string",
                            "enum": [
                              "sunlit",
                              "twilight",
                              "midnight",
                              "abyssal",
                              "hadal"
                            ],
                            "description": "Composite triage zone key = severity + exploitation signal (shallow→deep). abyssal = critical with a public exploit / in-the-wild; hadal = critical and actively exploited. Human labels/meanings are in info.description (sent once, not per row, to keep responses small)."
                          },
                          "depthScore": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 100,
                            "description": "Composite 0–100 urgency score blending impact (CVSS/severity), likelihood (EPSS) and proof of exploitation (KEV/0day/in-the-wild/PoC). Intentionally discounts un-exploited criticals below exploited highs. Sort the feed by it in the UI with sort=risk."
                          },
                          "cvss": {
                            "type": "number",
                            "nullable": true
                          },
                          "kev": {
                            "type": "boolean"
                          },
                          "exploited": {
                            "type": "boolean"
                          },
                          "score": {
                            "type": "number",
                            "description": "Relation strength (higher = more shared signals)"
                          },
                          "reasons": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string"
                                },
                                "value": {
                                  "type": "string"
                                }
                              }
                            }
                          },
                          "url": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Seed CVE outside the public window and no auth"
          }
        }
      }
    },
    "/api/sbom": {
      "post": {
        "operationId": "triageSbom",
        "summary": "Dependency / SBOM triage",
        "description": "Match a list of components against the corpus. Each component is a purl (pkg:npm/lodash@4.17.20) or a {ecosystem,name,version} object. Returns, per component, the affected CVEs ranked by depth, each tagged with a versionMatch (in-range | unknown). Package data is GHSA-sourced (NVD-only CVEs won't match). Anonymous callers match against the free public window; auth unlocks the full corpus. Up to 200 components.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "components"
                ],
                "properties": {
                  "components": {
                    "type": "array",
                    "maxItems": 200,
                    "items": {
                      "oneOf": [
                        {
                          "type": "string",
                          "description": "purl, e.g. pkg:pypi/django@3.1"
                        },
                        {
                          "type": "object",
                          "required": [
                            "name"
                          ],
                          "properties": {
                            "ecosystem": {
                              "type": "string"
                            },
                            "name": {
                              "type": "string"
                            },
                            "version": {
                              "type": "string"
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Triage report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "generated": {
                      "type": "string"
                    },
                    "access": {
                      "type": "string"
                    },
                    "summary": {
                      "type": "object",
                      "properties": {
                        "components": {
                          "type": "integer"
                        },
                        "resolved": {
                          "type": "integer"
                        },
                        "vulnerable": {
                          "type": "integer"
                        },
                        "matches": {
                          "type": "integer"
                        }
                      }
                    },
                    "invalid": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "input": {
                            "type": "string"
                          },
                          "ecosystem": {
                            "type": "string",
                            "nullable": true
                          },
                          "name": {
                            "type": "string"
                          },
                          "version": {
                            "type": "string",
                            "nullable": true
                          },
                          "matches": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string"
                                },
                                "title": {
                                  "type": "string"
                                },
                                "severity": {
                                  "type": "string",
                                  "enum": [
                                    "critical",
                                    "high",
                                    "medium",
                                    "low",
                                    "none"
                                  ]
                                },
                                "depth": {
                                  "type": "string",
                                  "enum": [
                                    "sunlit",
                                    "twilight",
                                    "midnight",
                                    "abyssal",
                                    "hadal"
                                  ],
                                  "description": "Composite triage zone key = severity + exploitation signal (shallow→deep). abyssal = critical with a public exploit / in-the-wild; hadal = critical and actively exploited. Human labels/meanings are in info.description (sent once, not per row, to keep responses small)."
                                },
                                "cvss": {
                                  "type": "number",
                                  "nullable": true
                                },
                                "epss": {
                                  "type": "number",
                                  "nullable": true
                                },
                                "kev": {
                                  "type": "boolean"
                                },
                                "exploited": {
                                  "type": "boolean"
                                },
                                "versionMatch": {
                                  "type": "string",
                                  "enum": [
                                    "in-range",
                                    "out-of-range",
                                    "unknown"
                                  ]
                                },
                                "affectedRange": {
                                  "type": "string",
                                  "nullable": true
                                },
                                "fixedVersion": {
                                  "type": "string",
                                  "nullable": true
                                },
                                "url": {
                                  "type": "string"
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          }
        }
      }
    },
    "/api/changes": {
      "get": {
        "operationId": "listChanges",
        "summary": "Change feed — per-CVE field-change events",
        "description": "Append-only mutation stream: KEV added, severity bumped, exploit signal flipped, material EPSS move (≥0.05 absolute). Store the response's nextSeq and pass it as `after` next poll — an exact cursor, no missed or duplicate events. Use `since` (ISO/epoch ms) only for the first run.",
        "parameters": [
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Exact cursor: events with seq strictly greater"
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "ISO date or epoch ms time floor (first run)"
          },
          {
            "name": "field",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "severity",
                "cvss",
                "epss",
                "kev",
                "exploited",
                "exploit_available",
                "zero_day"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of change events (oldest→newest)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "nextSeq": {
                      "type": "integer",
                      "nullable": true,
                      "description": "Pass as ?after= on the next poll"
                    },
                    "hasMore": {
                      "type": "boolean"
                    },
                    "access": {
                      "type": "string",
                      "enum": [
                        "public",
                        "full"
                      ]
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ChangeEvent"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/resolve": {
      "get": {
        "operationId": "resolveId",
        "summary": "Resolve any advisory identifier to its canonical CVE record",
        "description": "Accepts a CVE id, GHSA id, or any recorded alias and returns the canonical record's id, aliases, urls, and compact metadata. Use when an upstream tool hands you a non-CVE identifier.",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "GHSA-jfh8-c2jp-5v3q"
          }
        ],
        "responses": {
          "200": {
            "description": "Canonical record mapping",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "input": {
                      "type": "string"
                    },
                    "id": {
                      "type": "string"
                    },
                    "aliases": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "url": {
                      "type": "string"
                    },
                    "md": {
                      "type": "string"
                    },
                    "access": {
                      "type": "string",
                      "enum": [
                        "public",
                        "full"
                      ]
                    },
                    "locked": {
                      "type": "boolean",
                      "description": "True when metadata is outside the free window for an anonymous caller"
                    },
                    "title": {
                      "type": "string"
                    },
                    "severity": {
                      "type": "string",
                      "enum": [
                        "critical",
                        "high",
                        "medium",
                        "low",
                        "none"
                      ]
                    },
                    "depth": {
                      "type": "string",
                      "enum": [
                        "sunlit",
                        "twilight",
                        "midnight",
                        "abyssal",
                        "hadal"
                      ],
                      "description": "Composite triage zone key = severity + exploitation signal (shallow→deep). abyssal = critical with a public exploit / in-the-wild; hadal = critical and actively exploited. Human labels/meanings are in info.description (sent once, not per row, to keep responses small)."
                    },
                    "depthScore": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 100,
                      "description": "Composite 0–100 urgency score blending impact (CVSS/severity), likelihood (EPSS) and proof of exploitation (KEV/0day/in-the-wild/PoC). Intentionally discounts un-exploited criticals below exploited highs. Sort the feed by it in the UI with sort=risk."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown identifier"
          }
        }
      }
    },
    "/api/movers": {
      "get": {
        "operationId": "listEpssMovers",
        "summary": "EPSS movers — rising exploit probability",
        "description": "CVEs whose EPSS climbed the most over a recent window: rising exploitation risk before it lands in KEV. Backed by daily EPSS snapshots; needs ≥2 snapshot days inside the window to return anything.",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 90,
              "default": 14
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "minDelta",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "default": 0.1
            },
            "description": "Minimum absolute EPSS rise"
          }
        ],
        "responses": {
          "200": {
            "description": "Top risers, biggest delta first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "windowDays": {
                      "type": "integer"
                    },
                    "minDelta": {
                      "type": "number"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "severity": {
                            "type": "string",
                            "enum": [
                              "critical",
                              "high",
                              "medium",
                              "low",
                              "none"
                            ]
                          },
                          "depth": {
                            "type": "string",
                            "enum": [
                              "sunlit",
                              "twilight",
                              "midnight",
                              "abyssal",
                              "hadal"
                            ],
                            "description": "Composite triage zone key = severity + exploitation signal (shallow→deep). abyssal = critical with a public exploit / in-the-wild; hadal = critical and actively exploited. Human labels/meanings are in info.description (sent once, not per row, to keep responses small)."
                          },
                          "depthScore": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 100,
                            "description": "Composite 0–100 urgency score blending impact (CVSS/severity), likelihood (EPSS) and proof of exploitation (KEV/0day/in-the-wild/PoC). Intentionally discounts un-exploited criticals below exploited highs. Sort the feed by it in the UI with sort=risk."
                          },
                          "kev": {
                            "type": "boolean"
                          },
                          "exploited": {
                            "type": "boolean"
                          },
                          "epssNow": {
                            "type": "number"
                          },
                          "epssPrev": {
                            "type": "number"
                          },
                          "epssDelta": {
                            "type": "number"
                          },
                          "url": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/osv/v1/query": {
      "post": {
        "operationId": "osvQuery",
        "summary": "OSV-compatible vulnerability query",
        "description": "Speaks the osv.dev query schema — point an existing OSV client here and it works unchanged. Pass package.purl, or package.name + package.ecosystem + version. Each result carries VulnSea signal (depth, depthScore, KEV, EPSS) under database_specific. Commit queries return no matches (no commit data).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "version": {
                    "type": "string"
                  },
                  "package": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "ecosystem": {
                        "type": "string",
                        "example": "PyPI"
                      },
                      "purl": {
                        "type": "string",
                        "example": "pkg:npm/lodash@4.17.15"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OSV vulnerability list ({} when nothing matches, matching osv.dev)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "vulns": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/osv/v1/querybatch": {
      "post": {
        "operationId": "osvQueryBatch",
        "summary": "OSV-compatible batch query (up to 200)",
        "description": "osv.dev querybatch schema: results align positionally with `queries`, each containing only {id, modified} per match. Deep-fetch via /api/osv/v1/query or /api/cve/{id}.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "queries"
                ],
                "properties": {
                  "queries": {
                    "type": "array",
                    "maxItems": 200,
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "vulns": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string"
                                },
                                "modified": {
                                  "type": "string"
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}