ValtersIT CVE & Threat-IP API: Querying, Filtering and Automating Vulnerability Data
Most CVE data sources give you an ID, a score, and a description. The ValtersIT API gives you what you need to act: CVSS vector, EPSS exploitation probability, CISA KEV membership, whether a public exploit or Nuclei template exists, affected version strings, Shodan dorks for exposed instances, YARA/Wazuh/Elastic/Sigma detection rules (on paid plans), and the raw PoC where one exists (Pro). This guide covers every endpoint, every filter, every response field, and the code to use them.
Base URL and authentication
Section titled “Base URL and authentication”Base URL: https://api.valtersit.com/api/v1Auth: Authorization: Bearer <your key>API keys look like vit_v1_xxxxxxxxxxxxxxxxxxxxxxxx. Every request requires the Authorization header — there is no public unauthenticated access. A sandbox key (vit_sandbox_free_demo_key_2026) is available for testing CVE lookups; it returns fixed demo data and returns 403 on IP feed and scan submission endpoints.
# Test authenticationcurl -s "https://api.valtersit.com/api/v1/cve/CVE-2024-44000" \ -H "Authorization: Bearer vit_sandbox_free_demo_key_2026" | jq .import urllib.request, json
BASE = "https://api.valtersit.com/api/v1"KEY = "vit_v1_your_key_here"HEADERS = {"Authorization": f"Bearer {KEY}"}
def get(path, params=None): url = BASE + path if params: from urllib.parse import urlencode url += "?" + urlencode(params) req = urllib.request.Request(url, headers=HEADERS) with urllib.request.urlopen(req) as r: return json.loads(r.read())Endpoints
Section titled “Endpoints”GET /api/v1/cve — list CVE records with filters
Section titled “GET /api/v1/cve — list CVE records with filters”Returns a paginated list of CVE records. At least one filter is required.
GET /api/v1/cve?vendor=Microsoft&severity=CRITICAL&has_exploit=true&limit=20&page=1Query parameters
| Parameter | Type | Description |
|---|---|---|
vendor | string | Filter by vendor name, case-insensitive |
severity | string | CRITICAL, HIGH, MEDIUM, LOW, NONE |
cvss_min | float | Minimum CVSS score (0.0–10.0) |
cvss_max | float | Maximum CVSS score |
epss_min | float | Minimum EPSS score (0.0–1.0). 0.05 means ≥5% probability of exploitation |
has_exploit | bool | true — only CVEs with a public exploit or Nuclei template |
has_patch | bool | true — only CVEs with a known patch |
cisa_kev | bool | true — only CISA KEV entries |
cwe_id | string | Filter by CWE — e.g. CWE-89 for SQL injection |
changed_since | date | ISO 8601 — CVEs updated after this date |
published_since | date | ISO 8601 — CVEs published after this date |
limit | int | Results per page, max 100 (default 20) |
page | int | Page number, 1-based (default 1) |
Example: actively exploited critical vulnerabilities in the last 7 days
curl -s "https://api.valtersit.com/api/v1/cve" \ -H "Authorization: Bearer $VALTERSIT_API_KEY" \ -G \ --data-urlencode "severity=CRITICAL" \ --data-urlencode "has_exploit=true" \ --data-urlencode "published_since=$(date -d '7 days ago' +%Y-%m-%d)" \ --data-urlencode "limit=50" | jq '.data[] | {cve_id, cvss, epss, cisa_kev, poc_status}'from datetime import date, timedelta
records = get("/cve", { "severity": "CRITICAL", "has_exploit": "true", "published_since": (date.today() - timedelta(days=7)).isoformat(), "limit": 50,})for r in records["data"]: print(f"{r['cve_id']} CVSS {r['cvss']} EPSS {r['epss']:.4f} KEV: {r['cisa_kev']}")Example: high-EPSS CVEs for a specific vendor
curl -s "https://api.valtersit.com/api/v1/cve" \ -H "Authorization: Bearer $VALTERSIT_API_KEY" \ -G \ --data-urlencode "vendor=Cisco" \ --data-urlencode "epss_min=0.1" \ --data-urlencode "limit=100" | jq '.data[] | {cve_id, cvss, epss, affected_version}'Example: SQL injection CVEs with public exploits
curl -s "https://api.valtersit.com/api/v1/cve" \ -H "Authorization: Bearer $VALTERSIT_API_KEY" \ -G \ --data-urlencode "cwe_id=CWE-89" \ --data-urlencode "has_exploit=true" \ --data-urlencode "limit=50"GET /api/v1/cve/{id} — single CVE record
Section titled “GET /api/v1/cve/{id} — single CVE record”GET /api/v1/cve/CVE-2024-44000Returns the full record for one CVE. This is the primary lookup for enriching alerts, tickets and patch notes.
Response fields
| Field | Type | Notes |
|---|---|---|
cve_id | string | CVE-YYYY-NNNNN format |
vendor | string | Affected vendor/project |
cvss_score | float | 0.0–10.0. Null while NVD is still scoring (typically 2–14 days from publication) |
cvss_vector | string | CVSS:3.1/AV:N/AC:L/… format. Null while pending |
severity | string | CRITICAL / HIGH / MEDIUM / LOW / NONE |
epss | float | Probability of exploitation in next 30 days, 0.0–1.0. 0.0031 = 0.31%. Updated daily by FIRST.org |
cisa_kev | bool | true if in CISA Known Exploited Vulnerabilities catalogue |
cwe_id | string | e.g. CWE-89 (SQL injection) |
mitre_tactic | string | ATT&CK technique, e.g. T1190 (Exploit Public-Facing Application) |
affected_version | string | Version strings from NVD CPE + CNA data |
patch_status | string | PATCHED / FIX AVAILABLE · PATCH UNDER REVIEW · UNKNOWN / UNPATCHED |
patch_details | string | Specific patch/version guidance |
poc_status | string | Public — a known PoC exists. None — no known public PoC |
poc_url | string | URL to PoC or exploit reference |
has_exploit | bool | true if poc_status=Public or a Nuclei template exists |
has_patch | bool | true if patch_status is confirmed patched |
nuclei_template | string | ProjectDiscovery Nuclei template ID if one exists |
shodan_dorks | array | Shodan search strings for exposed instances |
logo_base64 | string | Vendor logo as data:image/png;base64,… |
website | string | Vendor’s official website |
origin_country | string | Vendor’s country of origin |
publish_date | date | NVD publication date |
pending_fields | array | Fields NVD has not yet filled in |
backfilled_fields | array | Fields filled in by ValtersIT after NVD publication |
yara_rule | string | YARA detection rule — Standard+ plan only, where a rule exists |
wazuh_rule | string | Wazuh/OSSEC XML rule — Standard+ plan only |
elastic_rule | string | Elastic SIEM detection rule — Standard+ plan only |
sigma_rule | string | Sigma rule in YAML — Pro only |
poc_code | string | Raw proof-of-concept code — Pro only, where legally permissible |
Example: full record lookup
curl -s "https://api.valtersit.com/api/v1/cve/CVE-2024-44000" \ -H "Authorization: Bearer $VALTERSIT_API_KEY" | jq '{ cve_id, vendor, cvss_score, cvss_vector, epss, cisa_kev, poc_status, affected_version, patch_status, shodan_dorks, nuclei_template }'record = get("/cve/CVE-2024-44000")
# Triage logickev = record.get("cisa_kev", False)epss = record.get("epss", 0) or 0cvss = record.get("cvss_score", 0) or 0exploit = record.get("has_exploit", False)
priority = "P1" if kev or epss > 0.3 else \ "P2" if cvss >= 9.0 or exploit else \ "P3" if cvss >= 7.0 else "P4"
print(f"{record['cve_id']} {priority} " f"CVSS {cvss} EPSS {epss:.4f} KEV: {kev} Exploit: {exploit}")GET /api/v1/ip/{ip} — single IP lookup
Section titled “GET /api/v1/ip/{ip} — single IP lookup”Returns threat intelligence for one IP address. Requires Standard plan or above.
GET /api/v1/ip/203.0.113.5Response fields
| Field | Type | Notes |
|---|---|---|
ip | string | IPv4 or IPv6 address |
threat_type | string | scanner · malware_dist · botnet_c2 · phishing · tor · exploit_host · c2_server · spam · brute_force · mixed |
confidence | int | 0–100. Conservative scoring — few IPs reach 75+. Start with min_confidence=50 for practical filtering |
sources | array | Data source names |
cve_ids | array | CVEs this IP has been observed exploiting |
malware_families | array | Malware families associated with this IP |
country_code | string | ISO country code |
asn | string | Autonomous system number |
org | string | AS organisation name |
ports | array | Observed open ports |
is_tor | bool | Known Tor exit node |
is_vpn | bool | Known VPN exit IP |
last_seen | datetime | Last observed activity |
result = get(f"/ip/203.0.113.5")if result.get("threat_type") and result.get("confidence", 0) >= 50: print(f"Block {result['ip']}: {result['threat_type']} " f"(confidence {result['confidence']}, seen {result['last_seen']})")GET /api/v1/ip/feed — bulk threat IP feed
Section titled “GET /api/v1/ip/feed — bulk threat IP feed”Returns the full threat-IP dataset for firewall import. Requires Standard plan or above. Standard plan: 500 rows per request. Pro plan: 1000 rows per request.
GET /api/v1/ip/feed?threat_type=botnet_c2&min_confidence=60Query parameters
| Parameter | Type | Description |
|---|---|---|
threat_type | string | One of the type strings above |
min_confidence | int | Minimum confidence score (0–100) |
page | int | Page for pagination (1-based) |
This endpoint is what the Threat-IP Blocklist Importer calls to render nftables, ipset, fail2ban, pfSense and MikroTik rulesets. Use it directly if you need a custom integration.
all_ips = []page = 1while True: batch = get("/ip/feed", {"min_confidence": 50, "page": page}) ips = batch.get("data") or batch.get("ips") or [] if not ips: break all_ips.extend(ips) if len(ips) < 500: # or 1000 on Pro break page += 1
print(f"Feed: {len(all_ips)} IPs")POST /api/v1/scan — submit a component inventory for CVE matching
Section titled “POST /api/v1/scan — submit a component inventory for CVE matching”Accepts a component list and a fingerprint, returns matched CVEs. This is the endpoint the open-source scanner suite calls after collecting the local component inventory.
POST /api/v1/scan{ "fingerprint": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", "components": [ {"vendor": "openssl", "product": "openssl", "version": "3.0.13"}, {"vendor": "wordpress", "product": "akismet", "version": "5.3.3"} ]}Fields
| Field | Type | Description |
|---|---|---|
fingerprint | string | System identifier ≥16 chars. The scanner generates a random 32-hex token stored in ~/.valtersit/fingerprint — it is never derived from your IP/hostname/MAC |
components | array | Array of {vendor, product, version} objects. vendor and product are required |
Response
{ "matched_cves": 1, "results": [ { "slug": "openssl", "version": "3.0.13-0ubuntu3.4", "cve_id": "CVE-2026-11023", "cvss": 7.5, "patched_in": "3.0.13-0ubuntu3.6" } ]}Pagination
Section titled “Pagination”All list endpoints (/cve, /ip/feed) support limit and page. When you need more records than one page returns, iterate:
def paginate(path, params, max_pages=100): params = dict(params) for page in range(1, max_pages + 1): params["page"] = page batch = get(path, params) records = batch.get("data", []) yield from records if len(records) < params.get("limit", 20): break
# All critical CVEs for Cisco, up to 500for cve in paginate("/cve", {"vendor": "Cisco", "severity": "CRITICAL", "limit": 100}): print(cve["cve_id"], cve.get("cvss_score"), cve.get("epss"))Response field guide: CVSS, EPSS, CISA KEV
Section titled “Response field guide: CVSS, EPSS, CISA KEV”CVSS score and vector
The CVSS score (cvss_score) ranges from 0.0 to 10.0. The vector string (cvss_vector) encodes the metric breakdown, e.g. CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H. Parse it to prioritise:
def parse_cvss_vector(vector): """Returns a dict of CVSS metrics from a CVSS:3.1/... string.""" if not vector or not vector.startswith("CVSS:"): return {} parts = vector.split("/")[1:] # drop the 'CVSS:3.1' prefix return dict(p.split(":") for p in parts if ":" in p)
v = parse_cvss_vector("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H")# network-accessible, low complexity, no privileges, no user interaction → auto-exploitableis_trivial = v.get("AV") == "N" and v.get("AC") == "L" and \ v.get("PR") == "N" and v.get("UI") == "N"NVD typically assigns CVSS scores 2–14 days after publication. During that window, cvss_score is null and pending_fields will include "cvss_score". Plan for null in your code.
EPSS
EPSS (Exploit Prediction Scoring System) is the probability that a CVE will be exploited in the wild within the next 30 days, as scored by FIRST.org’s model. It is returned as a decimal float: 0.0031 means 0.31% probability. It is updated daily.
# Practical thresholdsdef epss_tier(score): if score is None: return "UNKNOWN" if score >= 0.5: return "CRITICAL — actively exploited ecosystem" if score >= 0.1: return "HIGH — significant exploitation activity" if score >= 0.01: return "MEDIUM — some exploitation in the wild" return "LOW — rare exploitation, mostly theoretical"CISA KEV
cisa_kev: true means the CVE is in CISA’s Known Exploited Vulnerabilities catalogue — confirmed exploitation in a real attack. Treat any KEV entry as P1 regardless of CVSS. Federal agencies in the US are required to patch KEV entries within a defined remediation window; many private-sector teams use the same criterion.
Common query patterns
Section titled “Common query patterns”Daily brief: new KEV entries
curl -s "https://api.valtersit.com/api/v1/cve" \ -H "Authorization: Bearer $VALTERSIT_API_KEY" \ -G \ --data-urlencode "cisa_kev=true" \ --data-urlencode "changed_since=$(date -d 'yesterday' +%Y-%m-%d)" \ --data-urlencode "limit=100" | jq '.data[] | {cve_id, vendor, cvss_score, epss}'Watchlist alert: has a vendor’s CVE gained an exploit?
import json, urllib.request
WATCH_VENDORS = ["Palo Alto Networks", "Fortinet", "Cisco", "Ivanti"]
for vendor in WATCH_VENDORS: results = get("/cve", { "vendor": vendor, "has_exploit": "true", "changed_since": "2026-09-24", "limit": 50, }) for r in results.get("data", []): print(f"[EXPLOIT ADDED] {r['cve_id']} {vendor} CVSS {r['cvss_score']} " f"EPSS {(r['epss'] or 0):.4f}")Enrich a SIEM alert with one call
def enrich_cve(cve_id: str) -> dict: """Returns triage fields for a CVE alert. Safe for missing or None values.""" try: r = get(f"/cve/{cve_id}") except Exception: return {"cve_id": cve_id, "error": True} return { "cve_id": r.get("cve_id"), "severity": r.get("severity"), "cvss": r.get("cvss_score"), "epss": r.get("epss"), "cisa_kev": r.get("cisa_kev"), "has_exploit": r.get("has_exploit"), "nuclei": bool(r.get("nuclei_template")), "patch_status": r.get("patch_status"), "patched_in": r.get("patch_details"), "shodan_dorks": r.get("shodan_dorks", []), }
# In a Wazuh/Elastic alert handler:fields = enrich_cve("CVE-2024-44000")priority = "P1" if fields.get("cisa_kev") or (fields.get("epss") or 0) > 0.3 else "P2"Gate a CI/CD build on newly published CVEs for your stack
#!/usr/bin/env bash# ci-cve-gate.sh — exit 1 if new critical exploitable CVEs appeared since yesterdaySINCE=$(date -d 'yesterday' +%Y-%m-%d)
COUNT=$(curl -s "https://api.valtersit.com/api/v1/cve" \ -H "Authorization: Bearer $VALTERSIT_API_KEY" \ -G \ --data-urlencode "severity=CRITICAL" \ --data-urlencode "has_exploit=true" \ --data-urlencode "vendor=nginx" \ --data-urlencode "published_since=$SINCE" \ --data-urlencode "limit=1" | jq '.total // .count // 0')
if [ "$COUNT" -gt 0 ]; then echo "::error::$COUNT new critical exploitable CVE(s) for nginx since $SINCE. Review before merging." exit 1fiecho "CVE gate: no new critical exploits since $SINCE."Build a YARA-based detection set (Standard plan)
yara_rules = []for cve in paginate("/cve", {"has_exploit": "true", "epss_min": "0.1", "limit": 100}): rule = cve.get("yara_rule") if rule: yara_rules.append(rule)
with open("valtersit_threat_rules.yar", "w") as f: f.write("\n\n".join(yara_rules))print(f"Wrote {len(yara_rules)} YARA rules")API pricing and plan differences
Section titled “API pricing and plan differences”| PAYG | Standard | Pro | |
|---|---|---|---|
| Price | €0.08/credit | €19/mo founding (€29 reg) | €65/mo founding (€99 reg) |
| CVE API | ✓ per credit | ✓ unlimited | ✓ unlimited |
| Threat-IP API | ✓ per credit | ✓ | ✓ |
| YARA / Wazuh / Elastic rules | — | ✓ | ✓ |
| Sigma rules | — | — | ✓ |
| PoC code | — | — | ✓ |
| IP feed rows/request | 500 | 500 | 1000 |
| Scanner system slots | pay-as-you-go | 1 included | 5 included |
Founding price locked for life while the subscription stays active. Offer ends 31 Dec 2026.
PAYG works without a subscription: buy credits, each API request costs one credit. Standard and Pro are monthly subscriptions with unlimited API requests plus the scanner slots above.
See full pricing and sign up →
Error handling
Section titled “Error handling”import urllib.error
def safe_get(path, params=None): try: return get(path, params) except urllib.error.HTTPError as e: body = json.loads(e.read()) if e.code == 401: raise SystemExit("Invalid API key — check VALTERSIT_API_KEY") if e.code == 403: raise SystemExit(f"Plan limit: {body.get('message')}") if e.code == 429: import time; time.sleep(60); return safe_get(path, params) if e.code == 422: raise ValueError(f"Invalid request: {body}") raise except urllib.error.URLError as e: # Network or DNS failure — log and continue print(f"Network error on {path}: {e}") return NoneCommon HTTP responses:
200— success400— missing required filter (list endpoints need at least one filter)401— invalid or missingAuthorizationheader403— plan restriction (e.g. accessing/ip/feedwithout Standard+ plan)404— CVE ID not in the database422— validation error (malformedPOST /scanbody, invalid filter values)429— rate limit exceeded — back off and retry
Sandbox key for testing
Section titled “Sandbox key for testing”The key vit_sandbox_free_demo_key_2026 returns fixed demo data on CVE endpoints and allows you to test authentication and response parsing without a paid account. It returns 403 on /ip/feed, /ip/{ip}, and POST /scan with a clear “register for a real key” message.
# Test CVE lookup with sandbox keycurl -s "https://api.valtersit.com/api/v1/cve/CVE-2024-44000" \ -H "Authorization: Bearer vit_sandbox_free_demo_key_2026" | jq .Use this to build and test your integration before subscribing. The demo response shape is identical to the live API shape.