API Reference
Real-time phishing intelligence as JSON, CSV, or TXT. Open, free, no auth required.
Building an AI agent? See For AI Agents for the MCP server and Agent Skills wrappers.
Introduction
The detection pipeline identifies active suspicious phishing sites in real time by monitoring multiple threat intelligence sources and Certificate Transparency logs. Data is enriched with IP geolocation, TLS certificate information, and detection verdicts.
Detection sources:
- Certificate Transparency - real-time monitoring of newly issued TLS certificates
- Google Safe Browsing - batch lookup against Google's threat lists
- OpenPhish - community phishing feed
- PhishTank - verified phishing URLs
- TweetFeed - IOCs shared on social media
- urlscan.io - live URL scanning verdicts
Pipeline schedule
| Process | Frequency | Description |
|---|---|---|
| Detection pipeline | Every hour | Scans Certificate Transparency logs and checks threat intel sources for new suspicious phishing domains |
| Active site re-check | Every 6 hours | Re-visits active sites, captures fresh screenshots, and updates detection verdicts |
| New registration scan | Daily (00:30 UTC) | Scans newly registered domains for suspicious keyword patterns |
| Data enrichment | On detection | IP geolocation, ASN, TLS certificate, and hosting organization via ipinfo.io |
Endpoint URL
https://phishunt.io/api/v1/domains
Get domains
Returns active suspicious phishing domains with enrichment data: IP geolocation, TLS certificate issuer, hosting org, and detection verdicts from Google Safe Browsing, OpenPhish, PhishTank, TweetFeed, and urlscan.io.
Query parameters
| Parameter | Type | Description |
|---|---|---|
limit |
integer | Number of results to return. Omit to get all current results; max 1000 when set. |
offset |
integer | Number of results to skip for pagination. Default 0, max 10000. |
format |
string | Response format: json, csv, or txt. Default json. |
company |
string | Filter by targeted brand (e.g. amazon, paypal). See targeted brands. |
since |
ISO 8601 | Only entries after this date (e.g. 2026-03-01). |
tier |
string | Liveness filter: all or verified (has a usable screenshot capture or a positive urlscan.io verdict). Default all. |
Example request
curl "https://phishunt.io/api/v1/domains"
Example response
{
"count": 2,
"offset": 0,
"limit": null,
"results": [
{
"url": "https://amazon.example-phish.com/signin",
"domain": "amazon.example-phish.com",
"company": "amazon",
"date": "2026-03-27T14:30:00+00:00",
"first_seen": "2026-03-25T09:12:00+00:00",
"uuid": "54889cb5-146d-484f-8b94-7a0b7385bff7",
"ip": "198.51.100.42",
"country": "United States",
"asn": "64496",
"org": "Example Hosting Inc.",
"cert": "Let's Encrypt R3",
"malicious_google": false,
"malicious_openphish": true,
"malicious_phishtank": false,
"malicious_tweetfeed": false,
"malicious_urlscan": true
}
]
}
Feeds
Download the complete active suspicious phishing feed. Updated every hour. Full dataset with no filtering - use the domains endpoint for query parameters.
indicator per detection, with confidence derived from that detection's own score - never a flat number across every indicator. Import the OpenAPI spec into Postman or Insomnia.
Blocklists
DNS/firewall blockers match by domain, not hostname - the feeds above ship full hostnames instead. These six formats collapse each detection to its registrable domain first, so a resolver or filter list can block it outright.
| Format | Consumer | Setup |
|---|---|---|
| Domains | Pi-hole | Group Management → Adlists → https://phishunt.io/blocklist/domains.txt |
| Hosts | /etc/hosts-style blockers |
curl -fsSL https://phishunt.io/blocklist/hosts.txt | sudo tee -a /etc/hosts |
| Adblock | uBlock Origin / AdGuard | Filter subscription URL: https://phishunt.io/blocklist/adblock.txt |
| dnsmasq | dnsmasq | conf-file=/etc/dnsmasq.d/phishunt.conf |
| unbound | pfSense / OPNsense | include: "/etc/unbound/phishunt.conf" |
| RPZ | BIND / Knot | response-policy { zone "phishunt-rpz"; }; |
Analyze URL
Passive analysis of any URL: checks it against phishunt's stored detections (active feed, prior detections, new-registration feed) and runs live URL-shape heuristics (brand match, typosquat/homograph distance, TLD abuse, PaaS hosting, clone intent). The target URL is never contacted. Suspicious unknown domains are queued for full pipeline analysis.
Query parameters
| Parameter | Type | Description |
|---|---|---|
url |
string | The URL to analyze. Required. http/https only, max 2048 characters. |
Example request
curl "https://phishunt.io/api/v1/analyze?url=https://amazon-secure-login.example.com/signin"
Example response
{
"query": { "url": "...", "host": "...", "normalized_host": "...", "apex": "..." },
"known": {
"in_active_feed": false,
"previously_active": false,
"in_new_registration_feed": false,
"record": null
},
"live_analysis": {
"allowlisted": false,
"brand_match": "amazon",
"kw_score": 85,
"impersonation": false,
"url_signals": { "kw_score_norm": 0.85, "idn_homograph": 0.0, "tld_abuse": 0.3, "clone_intent": 0.0, "paas_host": 0.0, "leet_brand": 0.0 },
"path_signals": { "brand_off_host": 0.0, "suspicious_path": 1.0, "exec_page": 0.0 },
"url_risk_score": 72,
"url_risk": "high",
"why": [
{ "signal": "kw_score_norm", "value": 0.85, "weight": 0.26, "points": 22.1, "kind": "weighted" },
{ "signal": "suspicious_path", "value": 1.0, "weight": 0.0, "points": 12.0, "kind": "path_boost" }
]
},
"history": { "apex_prior_detections": 0, "brands_targeted": [] },
"external_feeds": { "openphish": false, "phishtank": false, "tweetfeed": false, "listed": false, "status": "ok", "feeds_ok": true },
"action": { "queued_for_analysis": true, "reason": "high url_risk on unknown apex" },
"meta": { "passive_only": true, "license": "CC0-1.0" }
}
external_feeds reports whether the apex domain is present in OpenPhish/PhishTank/TweetFeed (local cache cross-reference — the target is still never contacted), plus status/feeds_ok for the freshness of that local cache itself. live_analysis.why lists the top-5 score contributors (weighted signals, passive path/shape boosts, and a capped-row correction when one applies), consistent with url_risk_score. Full field reference in the OpenAPI spec (AnalyzeResponse schema).
Campaigns
Groups of indicators that share infrastructure or content signals (same TLS certificate, IP, hosting, page content, etc.), computed by the same daily correlation job as related infrastructure. This is a shared-infrastructure grouping of public detections, not an attribution claim - clusters are labeled "possible campaign" or "suspected cluster" only, never an actor or group. Low-confidence ("weak") clusters are never surfaced through these endpoints or the /campaigns/ pages.
key is the stable identifier - use it for permalinks and caching. The numeric id is deprecated: it changes on every daily rebuild (versioned per daily rebuild), so a bookmarked /api/v1/campaigns/41 link still resolves (the sidecar remembers the mapping for 180 days), but only key is guaranteed to keep naming the same campaign tomorrow. It's kept in responses only for backward compatibility - use key.
data_status (on the list endpoint and on GET /api/v1/campaigns/{key}) is ok, stale, or missing - it reports whether the correlation sidecar rebuilt on schedule, so a client can tell "genuinely zero live campaigns" apart from "the nightly rebuild didn't run". GET /api/v1/campaigns/{key} also carries state: live for a currently-live cluster, or archived for a key that is no longer live but whose history is still retained (200, not 404 - see below).
confidence is one of two labels, thresholded off the underlying confidence_score (0-1, only exposed on GET /api/v1/campaigns/{key}): >= 0.70 is "possible campaign", >= 0.40 is "suspected cluster". Below 0.40 ("weak") is never surfaced by this API or the /campaigns/ pages at all.
total (list endpoint only, distinct from count) is how many clusters match brand/status/min_size before limit/offset are applied - page through everything by incrementing offset until offset + count >= total.
List possible campaigns / suspected clusters.
Query parameters
| Parameter | Type | Description |
|---|---|---|
limit |
integer | Number of results to return. Default 50, max 200. |
offset |
integer | Number of results to skip for pagination. Default 0, max 10000. |
brand |
string | Filter to clusters with at least one member targeting this brand slug. |
status |
string | active (at least one currently-active member) or all. Default all. |
min_size |
integer | Only return clusters with at least this many members. Default 0. |
Example request
curl "https://phishunt.io/api/v1/campaigns?status=active&limit=10"
Example response
{
"count": 1,
"total": 1,
"offset": 0,
"limit": 50,
"algorithm_version": "corr-1.2.0",
"generated_at": "2026-07-18T06:10:00Z",
"results": [
{
"id": 41,
"key": "0c1b79ab9b24",
"size": 9,
"active_count": 6,
"brands": ["coinbase", "kraken"],
"confidence": "suspected cluster",
"first_seen": "2026-07-10T08:00:00Z",
"last_activity": "2026-07-17T10:00:00Z",
"top_evidence": { "type": "cert_fingerprint_exact", "coverage": "7/9 members" },
"url": "https://phishunt.io/campaigns/0c1b79ab9b24/"
}
]
}
0c1b79ab9b24 above is an example identifier, not necessarily a live campaign - use the "Try it" link on each endpoint below for a currently-live example.
Full campaign object: the list summary fields plus an evidence breakdown, a per-pair relationships drill-down (which specific member pairs are linked, by what evidence, capped at the 50 strongest), and every member indicator.
{key} accepts the stable key (preferred) or the legacy numeric id, resolved for the last 180 days. A key that is no longer live but whose history is still retained returns 200 with "state": "archived" and a thinner shape: key, label, confidence_score, size, first_tracked, last_seen, end_state, successors, history, members (no evidence_summary, and members is the last recorded snapshot only - no live enrichment). end_state is dissolved, merged, split, or unknown; successors lists the key(s) a merged/split lineage continues under, when resolvable. 404 with {"error": "Unknown campaign id", "list_url": "..."} only for an identifier the sidecar has never seen, a manually-suppressed key, or a "weak" campaign (never surfaced).
Same data as the endpoint above, served as a downloadable attachment.
| Parameter | Type | Description |
|---|---|---|
format |
string | json, csv, or txt. Default json. |
uuid,domain,url,company,status,first_seen,relationship_score,evidence_types (semicolon-joined signal ids). TXT is one member URL per line. Content-Disposition filenames use only the resolved campaign key, never a domain and never the raw identifier supplied by the caller. For an archived key, only format=json has an honest answer (the same "state": "archived" object as GET /api/v1/campaigns/{key}) - csv and txt stay 404, since both formats promise live enrichment (URL, status, relationship score) that an archived snapshot's last-known member set does not retain. Full field reference (evidence summary, history, members) in the OpenAPI spec (Campaign schema).
Server-rendered, paginated browser (?page=N, ~25 per page) over every archived campaign - not a JSON endpoint. noindex,follow.
Response fields
Each object in the results array (or feed entries) contains these fields, grouped by purpose.
Identity
| Field | Type | Description |
|---|---|---|
url | string | Full URL of the suspicious site |
domain | string | Domain name including subdomains |
company | string | Targeted brand slug (e.g. amazon, paypal) |
uuid | string | Unique identifier for this entry (UUID v4) |
Time
| Field | Type | Description |
|---|---|---|
first_seen | datetime | When the site was first detected (ISO 8601) |
date | datetime | Last check timestamp (ISO 8601) |
Hosting
| Field | Type | Description |
|---|---|---|
ip | string | Resolved IPv4 address |
country | string | Hosting country name |
asn | string | Autonomous System Number (e.g. 13335) |
org | string | Hosting organization |
cert | string | TLS certificate issuer |
Detection sources
| Field | Type | Description |
|---|---|---|
malicious_google | boolean | true if flagged by Google Safe Browsing |
malicious_openphish | boolean | true if present in OpenPhish feed |
malicious_phishtank | boolean | true if present in PhishTank |
malicious_tweetfeed | boolean | true if present in TweetFeed |
malicious_urlscan | boolean | true if flagged by urlscan.io |
phishunt score
| Field | Type | Description |
|---|---|---|
score | integer | phishunt's own 0-100 detection score from its explainable 5-layer scoring engine. |
verdict | string | Calibrated verdict tier for score: critical, high, medium, low, or noise. |
top_signals | array | Up to 5 highest-weighted signals behind score, as {signal, label, points}, sorted by points descending. |
risk_score is a heuristic risk score, not a calibrated probability - the same note we give on Analyze URL's risk_score applies to score here: it is not a probability of phishing.
Pagination
Use offset and limit to paginate through results. The response includes both values so you can compute the next page.
# Page 1 curl "https://phishunt.io/api/v1/domains?limit=100&offset=0" # Page 2 curl "https://phishunt.io/api/v1/domains?limit=100&offset=100" # Page 3 curl "https://phishunt.io/api/v1/domains?limit=100&offset=200"
When count is less than limit, you've reached the last page.
Status codes
| Code | Description |
|---|---|
| 200 | Success. Response body contains the requested data. |
| 400 | Bad request. Invalid parameter value (e.g. malformed since date). |
| 429 | Rate limited. Back off and retry. |
Notes
Access-Control-Allow-Origin: *, so you can call the API from browser applications.