Skip to content

Build with the API / Vendor in the news

Vendor in the news

Which of my vendors/products are in incident reporting this week?

What you get

For each vendor or product name you give it, pulls the highest-scoring threat clusters of the last 7 days whose title, summary or keywords mention that name, keeps those at or above a threat-score threshold, and prints one line per hit with a link to the ThreatCluster cluster page.

Endpoints
/threats
Credits per run
5
Key
Works on the free key

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/vendor-in-the-news.py
    pip install requests
    export THREATCLUSTER_API_KEY=tc_live_your_key_here
  3. 3. Run it

    Pass the vendors and products you run. For each, the script lists the incidents from the last 7 days that score 60 or more.

    python3 vendor-in-the-news.py Microsoft Cisco Fortinet Ivanti Citrix
    A terminal running the script for five vendors and printing, for Microsoft, a list of incidents with threat score, urgency, title and link
    Run on 29 September 2026. 20 incidents across the five names: Microsoft 11, Citrix 7, Cisco 2, Fortinet and Ivanti none.
  4. 4. Open an incident

    Each line links to the incident, which has the summary and every article that covered it.

    The ThreatCluster incident page for Microsoft's Patch Tuesday, with key points, a threat score of 74.6 and 31 extracted entities
    One of the Microsoft incidents from the run above.
  5. 5. Raise the bar

    Set a higher score to see only the serious ones. Each vendor name costs one credit.

    python3 vendor-in-the-news.py --min-score 70 "Palo Alto" SonicWall

The code

vendor-in-the-news.py, 102 lines. Python 3, needs requests.

#!/usr/bin/env python3
"""vendor-in-the-news: which of my vendors/products are in incident reporting this week?

What it does
  For each vendor or product name you give it, pulls the highest-scoring threat clusters
  of the last 7 days whose title, summary or keywords mention that name, keeps those at
  or above a threat-score threshold, and prints one line per hit with a link to the
  ThreatCluster cluster page.

Endpoints
  GET /threats?keyword=<name>&time_filter=7d&sort_by=threat_score   (1 credit per vendor)

Cost: 1 credit per vendor name. Works on the free tier (7-day window, 25 rows per query).

Usage
  python3 vendor-in-the-news.py Microsoft Cisco Fortinet Ivanti Citrix
  python3 vendor-in-the-news.py --min-score 70 "Palo Alto" SonicWall
"""
import argparse
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))

DEFAULT_VENDORS = ["Microsoft", "Cisco", "Fortinet", "Ivanti", "Citrix"]


def main():
    ap = argparse.ArgumentParser(description="Incident clusters mentioning your vendors this week")
    ap.add_argument("vendors", nargs="*", default=DEFAULT_VENDORS, help="vendor/product names")
    ap.add_argument("--min-score", type=float, default=60.0, help="threat-score threshold (0-100)")
    args = ap.parse_args()

    total_hits = 0
    seen = set()  # the same cluster can match two vendors; print it once, under the first
    for i, vendor in enumerate(args.vendors):
        if i:
            time.sleep(PACE_SECONDS)
        data = api_get("/threats", keyword=vendor, time_filter="7d", sort_by="threat_score", limit=25).json()
        hits = [t for t in data.get("threats", [])
                if (t.get("threat_score") or 0) >= args.min_score and t.get("cluster_id") not in seen]
        print("%s: %d cluster(s) >= %g in the last 7 days" % (vendor, len(hits), args.min_score))
        for t in hits:
            seen.add(t.get("cluster_id"))
            total_hits += 1
            title = t.get("ai_title") or t.get("title") or "(untitled)"
            print("  %5.1f  %-7s  %s\n         %s" % (t.get("threat_score") or 0, t.get("urgency_level") or "-",
                                                   title, cluster_url(t)))
    print("\n%d cluster(s) across %d vendor name(s)." % (total_hits, len(args.vendors)))
    report_credits()


if __name__ == "__main__":
    main()

The shell one-liner, vendor-in-the-news.sh:

#!/usr/bin/env bash
# vendor-in-the-news.sh: clusters >= 60 mentioning each vendor named on the command line, last 7 days. 1 credit per vendor. Free tier OK.
# usage: ./vendor-in-the-news.sh Microsoft Cisco Fortinet
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'*}"; }
MIN="${MIN_SCORE:-60}"
for vendor in "$@"; do
  tc_get /threats --data-urlencode "keyword=$vendor" --data-urlencode time_filter=7d --data-urlencode sort_by=threat_score --data-urlencode limit=25 \
   | jq -r --arg v "$vendor" --arg site "$SITE" --argjson min "$MIN" '.threats[] | select(.threat_score >= $min) | "\(.threat_score)\t\($v)\t\(.ai_title // .title)\t\($site)/cluster/\(.slug)"'
  sleep 2.1  # 30 requests/min on the free tier
done

Output

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

Microsoft: 11 cluster(s) >= 60 in the last 7 days
   76.5  medium   AI Agent Breaches Australian Medicare Portal, Microsoft Patches SharePoint Vulnerability
         https://threatcluster.io/cluster/ai-agent-breaches-australian-medicare-portal-microsoft-patch-08657247
   74.6  medium   Microsoft's Record Patch Tuesday Addresses 167 CVEs Including Zero-Days
         https://threatcluster.io/cluster/microsofts-record-patch-tuesday-addresses-167-cves-including-94b61f6e
   74.0  medium   Active Exploitation of SharePoint and MikroTik Vulnerabilities Confirmed
         https://threatcluster.io/cluster/active-exploitation-of-sharepoint-and-mikrotik-vulnerabiliti-7767f025
   72.9  medium   Critical Remote Code Execution Vulnerabilities in SENAITE and SharePoint
         https://threatcluster.io/cluster/critical-remote-code-execution-vulnerabilities-in-senaite-an-82e7b9a7
   72.6  medium   ShinyHunters Escalate Oracle PeopleSoft Exploitation Amid Microsoft Mega-Patch
         https://threatcluster.io/cluster/shinyhunters-escalate-oracle-peoplesoft-exploitation-amid-mi-1d5cd23e
   71.8  medium   Storm-3168 and Storm-2570: Evolving Ransomware Tactics in Cloud Environments
         https://threatcluster.io/cluster/storm-3168-and-storm-2570-evolving-ransomware-tactics-in-clo-3a83504e
   71.8  medium   Microsoft and Partners Disrupt EvilTokens AI-Powered Phishing Service
         https://threatcluster.io/cluster/microsoft-dismantles-eviltokens-ai-phishing-service-two-arre-34b13b0e
   71.0  medium   TeamFiltration Campaign Targets Microsoft 365 Accounts in Chile
         https://threatcluster.io/cluster/teamfiltration-campaign-targets-microsoft-365-accounts-in-ch-a3e7ee2d
   70.5  medium   Attackers Compile Cryptominer on Victim Endpoint via Samsung MagicINFO Exploit
         https://threatcluster.io/cluster/attackers-compile-cryptominer-on-victim-endpoint-via-samsung-fd4fda06
   63.9  medium   NeedyMantis Malware Framework Targeting Telecommunications and Government Entities
         https://threatcluster.io/cluster/needymantis-malware-discovered-in-targeted-operations-ee4e32b2
   60.6  medium   Nvidia Launches Open Agent Safety Platform to Contain Rogue AI Agents
         https://threatcluster.io/cluster/nvidia-launches-open-agent-safety-platform-to-address-ai-age-d2abebb6
Cisco: 2 cluster(s) >= 60 in the last 7 days
   80.0  medium   Critical Zero-Day Exploits Target Citrix and Other Major Platforms
         https://threatcluster.io/cluster/critical-zero-day-exploits-target-citrix-and-other-major-pla-8a8e4260
   69.5  medium   Qilin Ransomware Claims New Australian Victims Amid Ongoing Exploitation
         https://threatcluster.io/cluster/qilin-ransomware-claims-new-australian-victims-amid-ongoing--6ec4fb69
Fortinet: 0 cluster(s) >= 60 in the last 7 days
Ivanti: 0 cluster(s) >= 60 in the last 7 days
Citrix: 7 cluster(s) >= 60 in the last 7 days
   82.2  medium   Critical Citrix NetScaler Zero-Day Vulnerabilities Exploited
         https://threatcluster.io/cluster/critical-citrix-netscaler-zero-day-exploitation-confirmed-4d8f6ff9
   80.8  medium   Critical Zero-Day Vulnerabilities in Citrix NetScaler Under Active Exploitation
         https://threatcluster.io/cluster/active-exploitation-of-citrix-netscaler-zero-day-vulnerabili-bada67d1
   80.0  medium   Critical Zero-Day Exploits in Citrix NetScaler Confirmed by CISA
         https://threatcluster.io/cluster/critical-zero-day-exploits-in-citrix-netscaler-confirmed-by--5b100c3f
   78.0  medium   Critical RCE Vulnerability in Citrix NetScaler Exploited via DTLS Memory Overflow
         https://threatcluster.io/cluster/critical-rce-vulnerability-in-citrix-netscaler-exploited-via-132a1fb0
…

The file on GitHub: vendor-in-the-news.pyand vendor-in-the-news.sh.

Goes well with

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