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.
Release files for vulners-py 1.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vulners_py-1.2.0.tar.gz | 102.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vulners_py-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:140.1 kB
Release files / vulners_py-1.2.0.tar.gz
| Download URL | vulners_py-1.2.0.tar.gz |
|---|---|
| Size | 102.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bdf3f4a4604a7ed34db278b67e1d00562f1be6895de2f950a0966ba9961c0da4
|
|
BLAKE2b-256 checksum How to use checksums |
a36063dc38e275f7fdaa06feda6e690b234ed674303dba677d9cd177475f5760
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / vulners_py-1.2.0-py3-none-any.whl
| Download URL | vulners_py-1.2.0-py3-none-any.whl |
|---|---|
| Size | 37.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a4752bc286a39b700090d0102256b7c3f914696663f857ca05af489a2bc39006
|
|
BLAKE2b-256 checksum How to use checksums |
23a4de905c753122c5ff4faea099c903a9a3586b2d8c43fe64b910b754b1217c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|