Skip to content

Build with the API / Exploited this week

Exploited this week

CVEs from the last 7 days that are in KEV or have a public exploit.

What you get

Combines two views of the last 7 days of CVEs, the ones CISA added to the Known Exploited Vulnerabilities catalog and the ones with a known public exploit, into one table sorted by CVSS, with a link to each CVE's ThreatCluster page.

Endpoints
/vulnerabilities
Credits per run
2
Key
Works on the free key

This week, live

What the script returns right now, refreshed hourly. CVEs published in the last seven days that CISA lists as exploited or that have a public exploit, highest CVSS first.

CVECVSSEPSSWhy it is here
CVE-2026-571559.9 critical1%public exploit
CVE-2026-597749.8 critical28%public exploit
CVE-2026-765199.8 critical2%KEV
CVE-2026-1027649.8 critical1%KEV, public exploit
CVE-2026-1047489.8 critical1%public exploit
CVE-2026-890799.8 critical0%public exploit
CVE-2026-999999.3 critical0%public exploit
CVE-2026-941349.1 critical0%public exploit
CVE-2026-969518.8 high2%KEV
CVE-2026-1049358.6 high26%KEV, public exploit
CVE-2026-03227.2 high1%KEV

Step by step

  1. 1. Get a key

    Sign in, open Settings, then API & Feeds, and select Generate API key. The key is shown once, so copy it. Every account has one and the free key covers this recipe.

    The ThreatCluster API page with the Get a free key button and the quick start, which begins with creating a key
    The API page. The key itself is created in Settings, under API & Feeds.
  2. 2. Download the script and set the key

    The script is one file with one dependency. It reads the key from the environment, so the key never goes into the file.

    curl -sO https://raw.githubusercontent.com/Jam0k/Cyber-Threat-Intelligence-API/main/examples/recipes/exploited-this-week.py
    pip install requests
    export THREATCLUSTER_API_KEY=tc_live_your_key_here
  3. 3. Run it

    No arguments. The script lists CVEs published in the last 7 days that are in CISA KEV or have a public exploit, highest CVSS first.

    python3 exploited-this-week.py
    A terminal running the script and printing a table of CVEs with CVSS, severity, KEV and public-exploit flags, publication date and a link
    Run on 29 September 2026. 4 CVEs from that week were in KEV and 8 had a public exploit.
  4. 4. Open a CVE

    Each row links to the CVE's page.

    The ThreatCluster page for CVE-2026-93616, a Check Point vulnerability, showing it is exploited in the wild with a critical CVSS score
    A Check Point CVE that was both in KEV and had a public exploit that week.
  5. 5. Post it to the team on Mondays

    The output is plain text, so it pipes into mail or any chat webhook. The run costs 2 credits.

    python3 exploited-this-week.py | mail -s "Exploited this week" [email protected]

The code

exploited-this-week.py, 95 lines. Python 3, needs requests.

#!/usr/bin/env python3
"""exploited-this-week: CVEs from the last 7 days that are in KEV or have a public exploit.

What it does
  Combines two views of the last 7 days of CVEs, the ones CISA added to the Known
  Exploited Vulnerabilities catalog and the ones with a known public exploit, into one
  table sorted by CVSS, with a link to each CVE's ThreatCluster page.

Endpoints
  GET /vulnerabilities?days=7&kev_only=true     (1 credit)
  GET /vulnerabilities?days=7&has_exploit=true  (1 credit)

Cost: 2 credits per run. Works on the free tier (25 rows per list).

Usage
  python3 exploited-this-week.py
"""
import json
import os
import sys
import time

import requests

API_BASE = os.environ.get("THREATCLUSTER_API_BASE", "https://threatcluster.io/api/public/v1").rstrip("/")
API_KEY = os.environ.get("THREATCLUSTER_API_KEY", "")
SITE = os.environ.get("THREATCLUSTER_SITE", "https://threatcluster.io")  # where the linked pages live
PACE_SECONDS = 2.1  # free keys get 30 requests/min; ~28/min keeps loops clear of 429s
_credits = {"used": 0, "remaining": None}


def _auth_headers():
    # A tc_... key is sent as X-API-Key; anything else is a short-lived bearer (e.g. from `tc login`).
    if not API_KEY:
        sys.exit("THREATCLUSTER_API_KEY is not set. Free keys: https://threatcluster.io/about/api")
    if API_KEY.startswith("tc_"):
        return {"X-API-Key": API_KEY}
    return {"Authorization": "Bearer " + API_KEY}


def api_get(path, _allow=(), **params):
    """GET one endpoint. Exits non-zero with the API's own error body, unless the
    status is listed in _allow (per-item lookups where a 404 is itself an answer)."""
    r = requests.get(API_BASE + path, headers=_auth_headers(), params=params, timeout=60)
    if "X-Request-Cost" in r.headers:
        _credits["used"] += int(r.headers["X-Request-Cost"])
    if "X-RateLimit-Remaining" in r.headers:
        _credits["remaining"] = r.headers["X-RateLimit-Remaining"]
    if r.status_code >= 400 and r.status_code not in _allow:
        try:
            body = json.dumps(r.json())
        except ValueError:
            body = r.text[:500]
        sys.exit("HTTP %d from %s: %s" % (r.status_code, path, body))
    return r


def cluster_url(cluster):
    # The free payload carries `slug` (canonical page) and `cluster_id`; the last 8 hex
    # chars of the id also resolve, via a 301 to the slug, when slug is missing.
    ident = cluster.get("slug") or cluster.get("short_id") or cluster.get("cluster_id", "").replace("-", "")[-8:]
    return "%s/cluster/%s" % (SITE, ident)


def report_credits():
    # Stderr, so it never pollutes piped output (brief.md, blocklists, JSON).
    # Keys without a daily cap send no remaining-credits header.
    left = ", remaining today: %s" % _credits["remaining"] if _credits["remaining"] is not None else ""
    sys.stderr.write("[threatcluster] credits used this run: %d%s\n" % (_credits["used"], left))


def main():
    kev = api_get("/vulnerabilities", days=7, kev_only="true", limit=25).json()
    time.sleep(PACE_SECONDS)
    poc = api_get("/vulnerabilities", days=7, has_exploit="true", limit=25).json()

    merged = {}
    for v in kev.get("cves", []) + poc.get("cves", []):
        merged.setdefault(v["cve_id"], v)
    rows = sorted(merged.values(), key=lambda v: (-(v.get("cvss_v3_score") or 0), v["cve_id"]))

    print("Last 7 days: %s CVE(s) in KEV, %s with a public exploit (showing up to 25 of each)\n"
          % (kev.get("total", "?"), poc.get("total", "?")))
    print("%-16s %5s %-9s %-4s %-4s %-10s  %s" % ("CVE", "CVSS", "severity", "KEV", "PoC", "published", "ThreatCluster page"))
    for v in rows:
        print("%-16s %5s %-9s %-4s %-4s %-10s  %s/entities/cve/%s" % (
            v["cve_id"], v.get("cvss_v3_score") if v.get("cvss_v3_score") is not None else "-",
            v.get("cvss_v3_severity") or "-", "yes" if v.get("in_kev") else "no",
            "yes" if v.get("has_exploit") else "no", (v.get("published_date") or "")[:10], SITE, v["cve_id"]))
    print("\n%d unique CVE(s)." % len(rows))
    report_credits()


if __name__ == "__main__":
    main()

The shell one-liner, exploited-this-week.sh:

#!/usr/bin/env bash
# exploited-this-week.sh: last-7-day CVEs that are in KEV or have a public exploit, sorted by CVSS. 2 credits. Free tier OK.
# usage: ./exploited-this-week.sh
set -euo pipefail
: "${THREATCLUSTER_API_KEY:?set THREATCLUSTER_API_KEY (free keys: https://threatcluster.io/about/api)}"
BASE="${THREATCLUSTER_API_BASE:-https://threatcluster.io/api/public/v1}"
SITE="${THREATCLUSTER_SITE:-https://threatcluster.io}"
# A tc_... key goes in X-API-Key; anything else is a bearer (e.g. from `tc login`).
if [[ "$THREATCLUSTER_API_KEY" == tc_* ]]; then AUTH="X-API-Key: $THREATCLUSTER_API_KEY"; else AUTH="Authorization: Bearer $THREATCLUSTER_API_KEY"; fi
# tc_get <path> [curl -G args...]: prints the body; on HTTP >= 400 prints the API error to stderr and fails.
tc_get() { local path=$1; shift; local out code
  out=$(curl -sS -G -H "$AUTH" -w $'\n%{http_code}' "$BASE$path" "$@"); code=${out##*$'\n'}
  if [ "$code" -ge 400 ]; then echo "HTTP $code from $path: ${out%$'\n'*}" >&2; return 1; fi; printf '%s' "${out%$'\n'*}"; }
{ tc_get /vulnerabilities --data-urlencode days=7 --data-urlencode kev_only=true --data-urlencode limit=25; sleep 2.1
  tc_get /vulnerabilities --data-urlencode days=7 --data-urlencode has_exploit=true --data-urlencode limit=25; } \
 | jq -r --arg site "$SITE" '.cves[] | "\(.cvss_v3_score // 0)\t\(.cve_id)\t\(.cvss_v3_severity // "-")\tKEV=\(.in_kev)\tPoC=\(.has_exploit)\t\($site)/entities/cve/\(.cve_id)"' \
 | sort -t$'\t' -k2,2 -u | sort -t$'\t' -k1,1nr

Output

From a run on 29 September 2026. Yours will differ as the corpus moves.

Last 7 days: 4 CVE(s) in KEV, 8 with a public exploit (showing up to 25 of each)

CVE               CVSS severity  KEV  PoC  published   ThreatCluster page
CVE-2026-12227     9.8 CRITICAL  no   yes  2026-09-24  https://threatcluster.io/entities/cve/CVE-2026-12227
CVE-2026-14281     9.8 CRITICAL  no   yes  2026-09-25  https://threatcluster.io/entities/cve/CVE-2026-14281
CVE-2026-18143     9.8 CRITICAL  no   yes  2026-09-26  https://threatcluster.io/entities/cve/CVE-2026-18143
CVE-2026-82384     9.8 CRITICAL  no   yes  2026-09-28  https://threatcluster.io/entities/cve/CVE-2026-82384
CVE-2026-88771     9.8 CRITICAL  yes  no   2026-09-27  https://threatcluster.io/entities/cve/CVE-2026-88771
CVE-2026-94127     9.8 CRITICAL  yes  no   2026-09-22  https://threatcluster.io/entities/cve/CVE-2026-94127
CVE-2026-84388     9.6 CRITICAL  no   yes  2026-09-22  https://threatcluster.io/entities/cve/CVE-2026-84388
CVE-2026-89055     9.1 CRITICAL  no   yes  2026-09-25  https://threatcluster.io/entities/cve/CVE-2026-89055
CVE-2026-93399     9.1 CRITICAL  no   yes  2026-09-25  https://threatcluster.io/entities/cve/CVE-2026-93399
CVE-2026-87902     8.1 HIGH      yes  yes  2026-09-22  https://threatcluster.io/entities/cve/CVE-2026-87902
CVE-2026-88772     8.1 HIGH      yes  no   2026-09-27  https://threatcluster.io/entities/cve/CVE-2026-88772

11 unique CVE(s).
[threatcluster] credits used this run: 2

The file on GitHub: exploited-this-week.pyand exploited-this-week.sh.

Goes well with

All recipes: /build. Endpoint reference: /api/public/v1/docs.