TweetFeed API
Free REST API for Threat Intelligence queries (limited to 10k results per search)
TweetFeed API
TweetFeed.live API (limited to 10k results per search)
Introduction
Free API to get IOCs shared by the community on Twitter/X. Building an AI agent? Use the MCP server instead.
Cloudflare's bot protection returns 403 to requests sent with a bare Python-urllib or libwww-perl User-Agent - use requests, curl or another standard HTTP client instead.
Endpoint
GET https://api.tweetfeed.live/v1/{time}/{filter1}/{filter2}Example 1: IOCs posted today
https://api.tweetfeed.live/v1/todayExample 2: URLs posted today with phishing tag
https://api.tweetfeed.live/v1/today/phishing/urlExample 3: IPs posted in the last 7 days with CobaltStrike tag
https://api.tweetfeed.live/v1/week/cobaltstrike/ipExample 4: URLs posted by @malwrhunterteam in the last month
https://api.tweetfeed.live/v1/month/@malwrhunterteam/url
Result cap
A response is capped at 10,000 rows, truncated from the oldest end so you keep the most recent data. In practice only the unfiltered /v1/month exceeds it - that window holds about 15,000 rows, so roughly its first ten days are dropped. today, week and every filtered variant stay below the cap.
Truncation is never silent. Every 200 reports what you actually received:
X-Result-Count- rows in this response.X-Result-Window-Start/X-Result-Window-End- the real date range of the payload.X-Result-Truncated: true- sent only when the cap was hit. Test for presence; it is never sent asfalse.
Need the complete 30-day window? The CSV feed is served in full, with no cap:
https://tweetfeed.live/feeds/month.csv
Caching and conditional requests
Every 200 response carries an ETag and a Last-Modified header. Send either one back on your next request and get a 304 Not Modified with an empty body instead of re-downloading the full payload - this works on every route above, and also on the static apex files (/feeds/*.csv, /rss.xml, /misp/*, /stix/*).
If-None-Match
curl -H 'If-None-Match: "a1b2c3d4e5f60718"' -i https://api.tweetfeed.live/v1/todayExample: revalidate with
curl -z / wget -N
curl -z today.csv -o today.csv https://tweetfeed.live/feeds/today.csv
wget -N https://tweetfeed.live/feeds/today.csv
If-None-Match is the recommended validator: ETag changes only when the response body actually changes. Last-Modified reflects when this Worker cached the feed data behind the response, not when that data last changed, so If-Modified-Since only saves a round trip while that cache entry is still alive. If a request sends both, If-None-Match wins (RFC 7232 section 6).
Diff endpoint (incremental sync)
Returns IOCs added after the given ISO 8601 timestamp. Useful for delta-syncing a blocklist or a Threat Intelligence pipeline without re-pulling the full window. Same /{filter1}/{filter2} syntax as the time-window routes. Use the Z form for unambiguous UTC.
GET https://api.tweetfeed.live/v1/since/{ISO8601}/{filter1}/{filter2}Example A: phishing IOCs added since a given timestamp
https://api.tweetfeed.live/v1/since/2026-05-01T00:00:00Z/phishing
Edge cases: future timestamp returns 200 with an empty array. Timestamp older than 365 days returns 410 Gone. Malformed ISO returns 400. Strict ISO 8601 with Z or +HH:MM offset is required (no ambiguous local-time forms).
The same 10,000-row cap applies, but truncated from the opposite end: this route returns rows oldest-first, so you get a contiguous block. That makes it safe to page - when X-Result-Truncated is present, pass X-Result-Window-End back as the next since and repeat until it stops appearing.
Reach is the trailing 30 days, not 365. The route is served from the 30-day window file, because scanning the full year exceeds the Worker's CPU budget. A since older than that still returns 200, but the payload starts at the oldest row available rather than at your timestamp, and the response says so via X-Result-Window-Incomplete: true alongside the real X-Result-Window-Start. For a deeper backfill pull year.csv once, then switch to this endpoint for incremental updates.
Campaigns (AI-clustered)
Groups related IOCs from the last 30 days into named campaign clusters: deterministic pre-grouping (shared registered domain, cross-domain URL path patterns, shared specific tag) plus an AI-generated name and context. The AI only names and describes clusters - it never adds or removes IOCs. Regenerated once a day. Building an agent integration? The MCP server exposes the same data as a get_campaigns tool.
GET https://api.tweetfeed.live/v1/campaignsResponse (trimmed)
{
"version": 1,
"generated_at": "2026-07-05T19:36:35Z",
"window": "month",
"stale": false,
"stale_since": null,
"campaign_count": 23,
"campaigns": [
{
"id": "tfc-6d1f6fb9260c",
"name": "Multi-family RAT/stealer C2 on compromised and DDNS hosts",
"context": "A broad malware distribution campaign delivering AsyncRAT and NjRAT payloads across compromised legitimate websites, newly registered domains, and dynamic DNS hostnames...",
"confidence": "high",
"targeted_brand": null,
"first_seen": "2026-06-29",
"last_seen": "2026-07-05",
"ioc_count": 198,
"types": { "domain": 53, "ip": 32, "md5": 17, "sha256": 15, "url": 81 },
"tags": ["#APT", "#AsyncRAT", "#C2", "#Njrat", "#RAT"],
"reporters": ["0xb1lal", "BlinkzSec"],
"iocs": [
{
"date": "2026-07-05 18:00:00",
"user": "phatomcandle",
"type": "domain",
"value": "vibranovax.click",
"tags": ["#APT", "#malware"],
"tweet": "https://x.com/phatomcandle/status/2073829278639325327"
}
],
"member_cluster_ids": ["tfc-6d1f6fb9260c"],
"anchors": {
"registered_domains": ["vibranovax.click", "..."],
"url_path_patterns": ["/unicorn/aes.js", "..."],
"tags": ["#APT", "#C2"]
}
}
]
}
stale: true means the daily clustering run failed and this document is the previous day's snapshot (stale_since holds the date of the last successful run). Human page: tweetfeed.live/campaigns/.
Counts
Pre-aggregated totals for each time window: overall count, a per-type breakdown, a per-tag breakdown, and the first/last IOC timestamp - without fetching and parsing the full year CSV. Regenerated every 15 minutes alongside the rest of the feed.
GET https://api.tweetfeed.live/v1/countsResponse (trimmed)
{
"generated_at": "2026-07-29T18:15:03Z",
"windows": {
"today": {
"total": 214,
"types": { "url": 120, "domain": 60, "ip": 20, "sha256": 10, "md5": 4 },
"tags": { "phishing": 140, "malware": 50 },
"first_date": "2026-07-29 00:04:11",
"last_date": "2026-07-29 18:11:47"
},
"week": { "total": 1490, "types": { "...": "..." }, "tags": { "...": "..." }, "first_date": "2026-07-23 00:01:09", "last_date": "2026-07-29 18:11:47" },
"month": { "total": 6320, "types": { "...": "..." }, "tags": { "...": "..." }, "first_date": "2026-06-30 00:00:41", "last_date": "2026-07-29 18:11:47" },
"year": {
"total": 118342,
"types": { "...": "..." },
"tags": { "...": "..." },
"first_date": "2025-07-30 03:12:01",
"last_date": "2026-07-29 18:11:47",
"months": [9812, 8765, 10230, 9540, 11020, 12480, 10990, 9800, 8700, 9100, 9400, 8500]
}
}
}
months (only present on year) is indexed by calendar month, not relative to the window - index 0 is always January, index 11 is always December. Any window with no IOCs reports first_date/last_date as null.
Trends
Aggregate analytics computed from the same 15-minute pipeline that feeds the raw IOC endpoints: a rolling 31-day daily IOC volume series with a per-type breakdown, week-over-week top-moving tags, the most-abused TLDs among malicious domains from the last 30 days, and the novelty rate (new vs. recurring share of this week's distinct IOC values). Human page: tweetfeed.live/trends/. Building an agent integration? The MCP server exposes the same data as a get_trends tool.
GET https://api.tweetfeed.live/v1/trendsResponse (trimmed)
{
"version": 1,
"generated_at": "2026-08-04T19:00:36Z",
"daily": {
"days": 31,
"dates": ["2026-07-05", "2026-07-06"],
"total": [62, 154],
"types": { "url": [30, 71], "domain": [22, 60], "ip": [7, 15], "sha256": [2, 5], "md5": [1, 3] }
},
"movers": {
"current_range": ["2026-07-29", "2026-08-04"],
"previous_range": ["2026-07-22", "2026-07-28"],
"tags": [{ "tag": "phishing", "count": 783, "previous": 3600, "delta": -2817, "pct": -78.2 }]
},
"tlds": { "window": "month", "domains_total": 5991, "top": [{ "tld": "com", "count": 1601 }], "other": 1931 },
"novelty": { "window": "week", "distinct_values": 1579, "new": 1504, "recurring": 75, "pct_new": 95.3 },
"producers": { }
}
daily is column-oriented: dates, total and each types entry are parallel arrays sharing one index. The most recent day is partial until 00:00 UTC. movers compares the last 7 days against the 7 before them, so pct is negative for a tag that cooled off.
IOC lookup (exact match)
Checks whether a specific IOC appears in the trailing 365 days and returns one aggregated record per IOC type: first/last seen, times seen, reporters, tags and the latest source tweets. URL-encode the value. Defanged input (evil[.]tld, hxxps://) and http/https variants resolve to the same record. Building an agent integration? The MCP server exposes this endpoint as the enrich_ioc tool.
GET https://api.tweetfeed.live/v1/ioc?value={ioc}
Response
{
"found": true,
"query": "praserfelizlu.site",
"window": "365d",
"records": [
{
"type": "domain",
"value": "praserfelizlu.site",
"first_seen": "2026-07-06 21:19:00",
"last_seen": "2026-07-06 21:19:00",
"count": 1,
"users": ["Coolcarlos17"],
"tags": ["#phishing", "#scam"],
"tweets": ["https://x.com/Coolcarlos17/status/2074241960101941737"]
}
]
}
Unknown values return 200 with found: false. Values containing / or ? must use the ?value= query form.
TweetFeed's own feed data and API responses are CC0; the optional external corroboration block is courtesy of abuse.ch (URLhaus / ThreatFox / MalwareBazaar) and governed by abuse.ch's terms of use, not CC0.
Blocklists
Ready-to-consume exports of the last 30 days, one file per consumer format. Rebuilt every 15 minutes, but a file only changes when its entries change. Community-reported IOCs - use at your own risk.
| Format | Endpoint | Feeds |
|---|---|---|
| Domain list | /v1/blocklist/domains.txt |
Pi-hole, and anything that takes one domain per line |
| hosts file | /v1/blocklist/hosts.txt |
/etc/hosts, Windows hosts file |
| AdGuard rules | /v1/blocklist/adguard.txt |
AdGuard Home |
| IP list | /v1/blocklist/ips.txt |
Firewalls and edge ACLs |
| DNS RPZ zone | /v1/blocklist/rpz.txt |
BIND, Unbound, PowerDNS Recursor |
| dnsmasq config | /v1/blocklist/dnsmasq.txt |
dnsmasq |
| Full URLs | /v1/blocklist/urls.txt |
Proxies, IDS, SOAR enrichment - consumers that match on path |
The first six block at DNS or host level. urls.txt is one full URL per line instead, so it also covers url-type IOCs on shared or otherwise legitimate infrastructure that DNS-level blocking cannot safely reach.
# TweetFeed blocklist - plain domain list (Pi-hole compatible) # Window: 30 days # Entries: 4399 # Updated: 2026-07-12T21:11:25Z # License: CC0 # Community-reported IOCs. Use at your own risk. 008963466.weebly.com 06updatelive.xyz ...
TAXII 2.1
Read-only TAXII 2.1 server exposing the same STIX 2.1 data (indicators, identity, TLP:CLEAR marking) for Threat Intelligence Platforms such as OpenCTI or ThreatQ. No authentication.
GET https://api.tweetfeed.live/taxii2/Example: incremental poll of the rolling 31-day collection
GET https://api.tweetfeed.live/taxii2/root/collections/b7dc78af-1d12-5059-898c-3f0e77636204/objects/?added_after=2026-07-01T00:00:00Z
Paginate with limit and next. Media type application/taxii+json;version=2.1. An added_after poll returns indicators only: the identity and marking-definition objects that every indicator references via created_by_ref and object_marking_refs were added when the collection was created, so they fall outside any recent window. Fetch them once by id from /objects/{id}/ (or with match[id]) so your platform can resolve the attribution and the TLP:CLEAR marking.
Details
| Key | Required? | Possible values |
|---|---|---|
| time | Required |
Select 1 of these timeframes.
today (Today starting 00:00 UTC) week (Last 7 days) month (Last 30 days) year (Last 365 days, full CSV file - filters do not apply) |
| filter1 | Optional |
Can be an specific user, type or tag.
Type (url / domain / ip / sha256 / md5) Tag (phishing / ransomware / CobaltStrike ...) User (@malwrhunterteam / @1ZRR4H / @MBThreatIntel ...) |
| filter2 | Optional |
Can be an specific user, type or tag.
Type (url / domain / ip / sha256 / md5) Tag (phishing / ransomware / CobaltStrike ...) User (@malwrhunterteam / @1ZRR4H / @MBThreatIntel ...) |
Output example
[
{
"date": "2022-05-11 19:39:06",
"user": "Unit42_Intel",
"type": "ip",
"value": "138.124.183.147",
"tags": [
"#CobaltStrike",
"#IcedID"
],
"tweet": "https://x.com/Unit42_Intel/status/1524474195471745028"
},
{
"date": "2022-05-11 19:43:48",
"user": "GootLoaderSites",
"type": "url",
"value": "https://www.kipperfamily.co.uk/forum.php",
"tags": [
"#GootLoader"
],
"tweet": "https://x.com/GootLoaderSites/status/1524475379997675522"
},
{
"date": "2022-05-11 20:00:44",
"user": "ecarlesi",
"type": "url",
"value": "https://tinpan.top/bancocuscatlan/",
"tags": [
"#phishing"
],
"tweet": "https://x.com/ecarlesi/status/1524479640584466432"
},
...
]
Frequently asked questions
Does the TweetFeed API require authentication?
No. The TweetFeed REST API is open and does not require an API key, OAuth token or registration.
How do I query IOCs by type?
Call GET https://api.tweetfeed.live/v1/{time}/{type} where time is today, week or month and type is url, domain, ip, sha256 or md5. The response is JSON by default.
Can I filter by tag or malware family?
Yes. Chain a second filter: GET /v1/{time}/{filter1}/{filter2}. Filters can be an IOC type, a generic tag (phishing, malware) or a malware family (LockBit, RansomHub, and others).
Does the year timeframe support type, tag or user filters?
No. GET /v1/year (and any path under it) returns an HTTP 302 redirect to the full year.csv file - the last 365 days of IOCs, unfiltered. Filter it client-side after downloading, or use today, week or month for JSON responses with type, tag or user filtering.
Is there a rate limit on the TweetFeed API?
No hard rate limit for normal use. The API runs on Cloudflare Workers with edge caching; the tweetfeed.live zone serves roughly 45,000 requests per day. This is a side project, not an SLA-backed service, so be reasonable.
How do I avoid re-downloading the whole feed?
Use conditional requests: every 200 response carries an ETag and a Last-Modified header, send either one back and get a 304 with an empty body when nothing changed. See Caching and conditional requests above.