A modern, strictly typed Python SDK for the Vulners API.
Project description
vulners-py
A modern, strictly typed Python SDK for the Vulners API. It provides matching synchronous and asynchronous clients, immutable Pydantic v2 models, resilient HTTP handling, and no deprecated top-level compatibility aliases.
Features
- Search, single-bulletin lookup, multi-document retrieval, software/host/package/SBOM audits, and Smart Audit.
- ZIP, gzip, JSON, and NDJSON archive decoding with a stream-to-disk option.
- Reports, v4 subscriptions, legacy email/polling subscriptions, STIX, CPE, and search helpers.
- API-key loading from
VULNERS_API_KEY, including.env-based development workflows. - Retry/backoff,
Retry-After, per-endpoint rate limiting, HTTP/2 support, and typed exceptions. - Strict mypy, Ruff, and pytest checks with sync/async contract tests.
VScanner is intentionally excluded because it is deprecated.
Requirements
- Python 3.10 or newer
- A Vulners API key
Versioning policy
The 1.x series is the SDK's public-API stabilization period. Patch releases preserve compatibility;
minor releases may include clearly documented breaking API refinements when correcting an early
design before further feature development avoids long-term compatibility debt. Review
CHANGELOG.md before upgrading to a new 1.x minor release.
Starting with 2.0.0, public API compatibility follows strict Semantic Versioning.
Installation
uv add vulners-py
or:
pip install vulners-py
Optional performance extras:
uv add "vulners-py[http2,orjson]"
Authentication
Create a local .env file that is not committed:
VULNERS_API_KEY=your-api-key
Load it into the environment before running an application:
set -a
source .env
set +a
The key can also be passed explicitly as Vulners(api_key="..."). The transport authenticates
with X-Api-Key, never places the key in a URL, and discards HTTP cookies. Client and transport
representations never contain the key.
Before wiring the SDK into your app, confirm the key in your .env is accepted with the bundled
preflight (one cheap, read-only call; exits 0 on success, 2 when no key is set):
uv run python examples/check_connection.py # synchronous
uv run python examples/async_check_connection.py # asynchronous
See examples/ for runnable snippets that load VULNERS_API_KEY from .env.
Quick start
Synchronous
from vulners import Vulners
with Vulners() as client:
page = client.search.bulletins("wordpress 4.7", limit=10)
document = client.bulletins.by_id("CVE-2024-23622")
for bulletin in page.documents:
print(bulletin.id, bulletin.title)
print(document)
Asynchronous
import asyncio
from vulners import AsyncVulners
async def main() -> None:
async with AsyncVulners() as client:
async for bulletin in client.search.iter_exploits("CVE-2021-44228"):
print(bulletin.id)
asyncio.run(main())
Namespaces
| Namespace | Capabilities |
|---|---|
search |
Paginated bulletin/exploit searches, history, and web vulnerability matching |
bulletins |
Bulletin lookup by ID, references, KB seeds, and KB updates |
audit |
Software, host, Linux, library, classic OS, Windows, CVE, SBOM, and Smart Audit |
archive |
v3/v4 collections, incremental updates, distributives, and Getsploit downloads |
reports |
Vulnerability, IP, scan, and host reports |
subscriptions |
v4 lifecycle plus legacy email subscriptions under .email |
webhooks |
Legacy polling/webhook subscriptions |
stix |
STIX bundle generation by bulletin ID |
misc |
Suggestions, autocomplete, CPE lookup, and WAF rules |
Search and bulletin lookup
The bulletins namespace owns bulletin lookup by ID. The search namespace owns Lucene search;
use its paged methods when you need result metadata or explicit offsets, and the iter_* methods
when you want lazy, auto-paginated results:
| Method | Result |
|---|---|
client.bulletins.by_id(id) |
One SearchDocument, or None when the ID is not found |
client.bulletins.by_ids(ids) |
Found bulletins in the requested ID order |
client.search.bulletins(query, ...) |
One SearchPage with documents and total metadata |
client.search.iter_bulletins(query, ...) |
Lazy iterator or async iterator over all matches |
client.search.exploits(query, ...) |
One exploit-filtered SearchPage |
client.search.iter_exploits(query, ...) |
Lazy iterator or async iterator over all exploits |
Reference and KB helpers also live under client.bulletins:
with Vulners() as client:
bulletins = client.bulletins.by_ids(("CVE-2024-23622", "CVE-2021-44228"))
references = client.bulletins.references("CVE-2024-23622")
Audit examples
from pathlib import Path
from vulners import Vulners
from vulners.types import AuditSoftware
with Vulners() as client:
matches = client.audit.software(
(AuditSoftware(product="curl", vendor="haxx", version="8.0"),)
)
packages = client.audit.library(("pkg:pypi/requests@2.20.0",))
sbom = client.audit.sbom(Path("bom.json"))
Smart Audit is a preview endpoint billed per
submitted software string. Calling client.audit.smart(...) may incur account charges.
Archive examples
from pathlib import Path
from vulners import Vulners
with Vulners() as client:
records = client.archive.collection_update("exploitdb", "2026-07-17T00:00:00")
client.archive.collection_v4(
"exploitdb",
raw=True,
destination=Path("exploitdb.ndjson.gz"),
)
Decoded archive calls return immutable ArchiveRecord objects. raw=True requires a destination
and streams the response without loading the archive into memory.
Subscriptions
from vulners import Vulners
from vulners.types import LuceneSubscriptionQuery, WebhookSubscriptionDelivery
query = LuceneSubscriptionQuery(query="cvss:[9 TO *] AND family:cve")
delivery = WebhookSubscriptionDelivery(
address="https://example.com/vulners",
crontab="0 * * * *",
)
with Vulners() as client:
created = client.subscriptions.create("Critical CVEs", query, delivery)
print(created.id)
Create, update, and delete calls mutate remote account state. Legacy email subscriptions are under
client.subscriptions.email; legacy polling subscriptions are under client.webhooks.
Error handling
from vulners import AuthenticationError, RateLimitError, Vulners, VulnersAPIError
try:
with Vulners() as client:
client.search.bulletins("wordpress")
except AuthenticationError:
print("Check VULNERS_API_KEY")
except RateLimitError as error:
print(f"Retry after {error.retry_after!r} seconds")
except VulnersAPIError as error:
print(f"Vulners API error {error.status_code}: {error.message}")
Migration from the legacy wrapper
| Legacy wrapper | vulners-py |
|---|---|
find(query) / search_bulletins(query) |
client.search.bulletins(query) |
find_all(query) |
client.search.iter_bulletins(query) |
find_exploit(query) |
client.search.exploits(query) |
get_bulletin(id) |
client.bulletins.by_id(id) |
audit_software(...) |
client.audit.software(...) |
winaudit(...) |
client.audit.winaudit(...) |
vulnssummary_report(...) |
client.reports.vulns_summary(...) |
No deprecated top-level aliases or DeprecationWarning shims are included.
Development
Development requires uv and just. Then install all optional and development dependencies:
uv sync --all-extras
just check
Available recipes:
just fmt
just lint
just typecheck
just test
just check
Tests use mocked HTTP contracts by default. Run the bounded, read-only integration suite with a key
loaded from .env:
set -a
source .env
set +a
VULNERS_LIVE=1 uv run pytest tests/test_integration.py
The integration suite deliberately excludes billed Smart Audit/SBOM requests, archive bulk downloads, and subscription mutations.
License
Distributed under the MIT License. Copyright © 2026 Aleksandr Pavlov ckidoz@gmail.com.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vulners_py-1.2.0.tar.gz.
File metadata
- Download URL: vulners_py-1.2.0.tar.gz
- Upload date:
- Size: 102.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bdf3f4a4604a7ed34db278b67e1d00562f1be6895de2f950a0966ba9961c0da4
|
|
| MD5 |
d119cb408c3d64f91d8ccebfe71b1632
|
|
| BLAKE2b-256 |
a36063dc38e275f7fdaa06feda6e690b234ed674303dba677d9cd177475f5760
|
File details
Details for the file vulners_py-1.2.0-py3-none-any.whl.
File metadata
- Download URL: vulners_py-1.2.0-py3-none-any.whl
- Upload date:
- Size: 37.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4752bc286a39b700090d0102256b7c3f914696663f857ca05af489a2bc39006
|
|
| MD5 |
234192be229a5c7693a6b8bb24723587
|
|
| BLAKE2b-256 |
23a4de905c753122c5ff4faea099c903a9a3586b2d8c43fe64b910b754b1217c
|