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. 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 API page. The key itself is created in Settings, under API & Feeds. 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_here3. 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
Run on 29 September 2026. 20 incidents across the five names: Microsoft 11, Citrix 7, Cisco 2, Fortinet and Ivanti none. 4. Open an incident
Each line links to the incident, which has the summary and every article that covered it.

One of the Microsoft incidents from the run above. 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
- Brand and domain dark-web monitor: Are my brands or domains showing up on the dark web?
- Sector ransomware watch for Slack: New dark-web ransomware victims for a sector/country, as a Slack message.
- A lookup tool for LLM agents: A tool-calling function `threatcluster_lookup(query)` for LLM agents.
- Bash integration: curl and jq in shell scripts and cron
All recipes: /build. Endpoint reference: /api/public/v1/docs.