diff --git a/README.md b/README.md
index 74aaa20f..989113d5 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ Bring the VulnCheck API to your Python applications.
- [CPE](#cpe)
- [Index](#index)
- [Indices](#indices)
+ - [NVD API 2.0 compatibility](#nvd-api-20-compatibility)
- [Pagination](#pagination)
- [PURL](#purl)
- [Proxy Support](#proxy-support)
@@ -626,6 +627,92 @@ if __name__ == "__main__":
```
+
+
+### NVD API 2.0 compatibility
+
+Query the NVD API 2.0 compatible endpoints under `/rest/json`. They take NVD's parameters and return NVD's response shapes, served from VulnCheck's data, and work with any VulnCheck API token, including community. `NvdApi` covers CVEs, CVE change history, CPEs, CPE match criteria and sources.
+
+Look up `CVE-2021-44228`, then page through a date range using `cursor`
+
+```python
+import vulncheck_sdk
+import os
+
+TOKEN = os.environ["VULNCHECK_API_TOKEN"]
+
+configuration = vulncheck_sdk.Configuration()
+configuration.api_key["Bearer"] = TOKEN
+
+with vulncheck_sdk.ApiClient(configuration) as api_client:
+ nvd_client = vulncheck_sdk.NvdApi(api_client)
+
+ # Look up a single CVE, shaped exactly as NVD's API returns it
+ api_response = nvd_client.nvd_cves_get(cve_id="CVE-2021-44228")
+ cve = api_response.vulnerabilities[0].cve
+ print(cve.id, cve.vuln_status)
+
+ # Walk every CVE modified in a date range. Sending an empty cursor opts in
+ # to cursor paging; each response then carries the cursor for the next page
+ cursor = ""
+ count = 0
+ while cursor is not None:
+ api_response = nvd_client.nvd_cves_get(
+ last_mod_start_date="2026-09-01T00:00:00.000",
+ last_mod_end_date="2026-09-02T00:00:00.000",
+ results_per_page=500,
+ cursor=cursor,
+ )
+ count += len(api_response.vulnerabilities)
+ print(f"Total Items: {count}/{api_response.total_results}")
+ cursor = api_response.next_cursor
+```
+
+
+Click to View Async Implementation
+
+```python
+import asyncio
+import os
+import vulncheck_sdk.aio as vcaio
+
+TOKEN = os.environ.get("VULNCHECK_API_TOKEN")
+
+configuration = vcaio.Configuration()
+configuration.api_key["Bearer"] = TOKEN
+
+
+async def main():
+ async with vcaio.ApiClient(configuration) as api_client:
+ nvd_client = vcaio.NvdApi(api_client)
+
+ # Look up a single CVE, shaped exactly as NVD's API returns it
+ api_response = await nvd_client.nvd_cves_get(cve_id="CVE-2021-44228")
+ cve = api_response.vulnerabilities[0].cve
+ print(cve.id, cve.vuln_status)
+
+ # Walk every CVE modified in a date range. Sending an empty cursor opts
+ # in to cursor paging; each response then carries the cursor for the
+ # next page
+ cursor = ""
+ count = 0
+ while cursor is not None:
+ api_response = await nvd_client.nvd_cves_get(
+ last_mod_start_date="2026-09-01T00:00:00.000",
+ last_mod_end_date="2026-09-02T00:00:00.000",
+ results_per_page=500,
+ cursor=cursor,
+ )
+ count += len(api_response.vulnerabilities)
+ print(f"Total Items: {count}/{api_response.total_results}")
+ cursor = api_response.next_cursor
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
+```
+
+
### Pagination
diff --git a/README.template b/README.template
index 57c1f4d5..03ccac0b 100644
--- a/README.template
+++ b/README.template
@@ -21,6 +21,7 @@ Bring the VulnCheck API to your Python applications.
- [CPE](#cpe)
- [Index](#index)
- [Indices](#indices)
+ - [NVD API 2.0 compatibility](#nvd-api-20-compatibility)
- [Pagination](#pagination)
- [PURL](#purl)
- [Proxy Support](#proxy-support)
@@ -199,6 +200,30 @@ with open("./tests/indicies_aio.py", 'r') as f:
+### NVD API 2.0 compatibility
+
+Query the NVD API 2.0 compatible endpoints under `/rest/json`. They take NVD's parameters and return NVD's response shapes, served from VulnCheck's data, and work with any VulnCheck API token, including community. `NvdApi` covers CVEs, CVE change history, CPEs, CPE match criteria and sources.
+
+Look up `CVE-2021-44228`, then page through a date range using `cursor`
+
+
+
+
+Click to View Async Implementation
+
+
+
+
+
+
### Pagination
Paginate over results for a query to VulnCheck-KEV using `cursor`
diff --git a/tests/integration_aio.py b/tests/integration_aio.py
index 33195e62..df769423 100644
--- a/tests/integration_aio.py
+++ b/tests/integration_aio.py
@@ -3,6 +3,7 @@
import vulncheck_sdk.aio as vcaio
from vulncheck_sdk.aio.api.endpoints_api import EndpointsApi
from vulncheck_sdk.aio.api.indices_api import IndicesApi
+from vulncheck_sdk.aio.api.nvd_api import NvdApi
from vulncheck_sdk.aio.exceptions import ApiException, UnauthorizedException
API_TOKEN = os.environ.get("VULNCHECK_API_TOKEN", "")
@@ -108,6 +109,28 @@ async def _check_all_indices(indices: IndicesApi) -> None:
assert status == 200
+# --- NvdApi Tests ---
+
+
+async def _check_nvd(api: NvdApi) -> None:
+ print("Testing NvdApi")
+ cve_id = "CVE-2021-44228"
+
+ cves = await api.nvd_cves_get(cve_id=cve_id)
+ assert cves.total_results == 1
+ assert cves.vulnerabilities[0].cve.id == cve_id
+
+ first = await api.nvd_cves_get(cve_tag="disputed", results_per_page=5, cursor="")
+ assert first.next_cursor
+ second = await api.nvd_cves_get(cve_tag="disputed", results_per_page=5, cursor=first.next_cursor)
+ assert second.vulnerabilities[0].cve.id != first.vulnerabilities[0].cve.id
+
+ assert (await api.nvd_cve_history_get(cve_id=cve_id)).cve_changes
+ assert (await api.nvd_cpes_get(cpe_match_string="cpe:2.3:a:apache:log4j", results_per_page=5)).products
+ assert (await api.nvd_cpe_match_get(cve_id=cve_id, results_per_page=5)).match_strings
+ assert (await api.nvd_source_get(results_per_page=5)).sources
+
+
# --- Orchestrator ---
@@ -137,6 +160,7 @@ async def main() -> None:
)
await _check_all_indices(indices_api)
+ await _check_nvd(vcaio.NvdApi(client))
# --- CRITICAL FIX FOR "UNCLOSED CONNECTOR" ---
# aiohttp requires a small window of time to allow the
diff --git a/tests/integration_test.py b/tests/integration_test.py
index e8b3a303..83ca7eab 100644
--- a/tests/integration_test.py
+++ b/tests/integration_test.py
@@ -2,6 +2,7 @@
import vulncheck_sdk
from vulncheck_sdk.api.endpoints_api import EndpointsApi
from vulncheck_sdk.api.indices_api import IndicesApi
+from vulncheck_sdk.api.nvd_api import NvdApi
from vulncheck_sdk.api_response import ApiResponse
from vulncheck_sdk.exceptions import ApiException, UnauthorizedException
@@ -101,6 +102,49 @@ def test_all_indicies():
assert status == 200
+"""
+NvdApi Tests
+"""
+
+NVD_CVE_ID = "CVE-2021-44228"
+
+
+def test_nvd_cves_get():
+ response = _get_nvd_instance(API_TOKEN).nvd_cves_get(cve_id=NVD_CVE_ID)
+ assert response.total_results == 1
+ assert response.vulnerabilities[0].cve.id == NVD_CVE_ID
+
+
+def test_nvd_cves_get_cursor():
+ nvd = _get_nvd_instance(API_TOKEN)
+ first = nvd.nvd_cves_get(cve_tag="disputed", results_per_page=5, cursor="")
+ assert first.next_cursor
+ second = nvd.nvd_cves_get(cve_tag="disputed", results_per_page=5, cursor=first.next_cursor)
+ assert second.vulnerabilities[0].cve.id != first.vulnerabilities[0].cve.id
+
+
+def test_nvd_cve_history_get():
+ response = _get_nvd_instance(API_TOKEN).nvd_cve_history_get(cve_id=NVD_CVE_ID)
+ assert response.cve_changes
+
+
+def test_nvd_cpes_get():
+ response = _get_nvd_instance(API_TOKEN).nvd_cpes_get(
+ cpe_match_string="cpe:2.3:a:apache:log4j", results_per_page=5
+ )
+ assert response.products
+
+
+def test_nvd_cpe_match_get():
+ response = _get_nvd_instance(API_TOKEN).nvd_cpe_match_get(cve_id=NVD_CVE_ID, results_per_page=5)
+ assert response.match_strings
+
+
+def test_nvd_source_get():
+ response = _get_nvd_instance(API_TOKEN).nvd_source_get(results_per_page=5)
+ assert response.sources
+
+
"""
Helpers
"""
@@ -138,3 +182,11 @@ def _get_indices_instance(token: str = "") -> IndicesApi:
config.api_key["Bearer"] = token
client = vulncheck_sdk.ApiClient(config)
return vulncheck_sdk.IndicesApi(client)
+
+
+def _get_nvd_instance(token: str = "") -> NvdApi:
+ config = vulncheck_sdk.Configuration()
+ if token != "":
+ config.api_key["Bearer"] = token
+ client = vulncheck_sdk.ApiClient(config)
+ return vulncheck_sdk.NvdApi(client)
diff --git a/tests/nvd.py b/tests/nvd.py
new file mode 100644
index 00000000..2117a24c
--- /dev/null
+++ b/tests/nvd.py
@@ -0,0 +1,30 @@
+import vulncheck_sdk
+import os
+
+TOKEN = os.environ["VULNCHECK_API_TOKEN"]
+
+configuration = vulncheck_sdk.Configuration()
+configuration.api_key["Bearer"] = TOKEN
+
+with vulncheck_sdk.ApiClient(configuration) as api_client:
+ nvd_client = vulncheck_sdk.NvdApi(api_client)
+
+ # Look up a single CVE, shaped exactly as NVD's API returns it
+ api_response = nvd_client.nvd_cves_get(cve_id="CVE-2021-44228")
+ cve = api_response.vulnerabilities[0].cve
+ print(cve.id, cve.vuln_status)
+
+ # Walk every CVE modified in a date range. Sending an empty cursor opts in
+ # to cursor paging; each response then carries the cursor for the next page
+ cursor = ""
+ count = 0
+ while cursor is not None:
+ api_response = nvd_client.nvd_cves_get(
+ last_mod_start_date="2026-09-01T00:00:00.000",
+ last_mod_end_date="2026-09-02T00:00:00.000",
+ results_per_page=500,
+ cursor=cursor,
+ )
+ count += len(api_response.vulnerabilities)
+ print(f"Total Items: {count}/{api_response.total_results}")
+ cursor = api_response.next_cursor
diff --git a/tests/nvd_aio.py b/tests/nvd_aio.py
new file mode 100644
index 00000000..cf2fed7f
--- /dev/null
+++ b/tests/nvd_aio.py
@@ -0,0 +1,38 @@
+import asyncio
+import os
+import vulncheck_sdk.aio as vcaio
+
+TOKEN = os.environ.get("VULNCHECK_API_TOKEN")
+
+configuration = vcaio.Configuration()
+configuration.api_key["Bearer"] = TOKEN
+
+
+async def main():
+ async with vcaio.ApiClient(configuration) as api_client:
+ nvd_client = vcaio.NvdApi(api_client)
+
+ # Look up a single CVE, shaped exactly as NVD's API returns it
+ api_response = await nvd_client.nvd_cves_get(cve_id="CVE-2021-44228")
+ cve = api_response.vulnerabilities[0].cve
+ print(cve.id, cve.vuln_status)
+
+ # Walk every CVE modified in a date range. Sending an empty cursor opts
+ # in to cursor paging; each response then carries the cursor for the
+ # next page
+ cursor = ""
+ count = 0
+ while cursor is not None:
+ api_response = await nvd_client.nvd_cves_get(
+ last_mod_start_date="2026-09-01T00:00:00.000",
+ last_mod_end_date="2026-09-02T00:00:00.000",
+ results_per_page=500,
+ cursor=cursor,
+ )
+ count += len(api_response.vulnerabilities)
+ print(f"Total Items: {count}/{api_response.total_results}")
+ cursor = api_response.next_cursor
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/tests/readme_examples_test.py b/tests/readme_examples_test.py
index 06d528e0..931beba8 100644
--- a/tests/readme_examples_test.py
+++ b/tests/readme_examples_test.py
@@ -25,6 +25,8 @@
"./tests/index_aio.py",
"./tests/indicies.py",
"./tests/indicies_aio.py",
+ "./tests/nvd.py",
+ "./tests/nvd_aio.py",
"./tests/pagination.py",
"./tests/pagination_aio.py",
"./tests/purl.py",