# Destroy API — PhishDestroy public threat intelligence

Canonical documentation: https://api.destroy.tools/
Documentation reviewed: 2026-10-09. The OpenAPI document version identifies the documentation snapshot, not the API engine version.

PhishDestroy provides a free public API for phishing and crypto scam domain intelligence. No registration or API key is required. Requests use HTTPS. The route prefix is `/v1`.

## Quick start

```sh
curl --fail-with-body 'https://api.destroy.tools/v1/check?domain=example.com'
```

This is a request example, not a claimed live verdict. Use the [interactive console](https://api.destroy.tools/#console) to inspect a current response.

## Endpoints

| Method | Path | Input |
| --- | --- | --- |
| GET | `/v1/check` | `domain`: domain or URL; `d` and `url` are aliases |
| POST | `/v1/check/bulk` | JSON `domains` array, maximum 500 entries, body up to 256 KB |
| GET | `/v1/search` | `q`: literal substring, minimum 3 characters; `limit`: default 50, maximum 200 |
| GET | `/v1/feed/{list}` | Full list as JSON; `{list}` is `primary`, `community`, `primary_active` or `community_active` |
| GET | `/v1/feed/{list}.{format}` | `format`: `txt`, `json`, `hosts` or `adblock` |
| GET | `/v1/feed/{list}/delta` | `since`: ISO-8601 timestamp or a `next_since` cursor; `limit`: entries per page, default 50,000, maximum 100,000 |
| GET | `/v1/feeds` | Feed catalog with counts, URLs, sizes and SHA-256 checksums |
| GET | `/v1/stats` | No parameters |
| GET | `/v1/health` | No parameters |

### Domain checks

The `threat` boolean is the API verdict. Listed results include `severity`, `risk_score`, `lists`, `active`, `flags`, `matched_domain`, `listed_since` and `checked_at`, and can include `matched_keywords` and `input_was_subdomain`. Fields may be absent.

Input can be a domain or a URL. It is normalized before lookup: scheme, credentials, port, path and trailing dots are removed, internationalized names are converted to punycode, and a leading `www.` is stripped (the `www.` variant is still matched). A subdomain inherits a parent listing, but matching stops at shared-hosting suffixes from the Public Suffix List private section, such as `appspot.com` or `pages.dev`.

`listed_since` is when the matched domain was first listed. Confirmed entries carry the original PhishDestroy detection time; `null` means unknown.

Domains that were reviewed and cleared as false positives return `threat: false`, `whitelisted: true` and an `appeal_url`. Unlisted domains with risky keywords or top-level domains return `suspicious` and `heuristic_score`; that is a heuristic signal, not a blocklist match.

An unlisted domain is not a guarantee of safety. A missing score must not be presented as zero. A list entry whose value is false is not a positive match.

The browser console accepts a public domain or HTTP(S) URL, normalizes internationalized hostnames, and sends only the hostname. URL credentials are rejected. URL paths, queries and fragments are not sent. The submitted website is not visited by this page.

### Bulk checks

```sh
curl --fail-with-body -X POST 'https://api.destroy.tools/v1/check/bulk' \
  -H 'Content-Type: application/json' \
  -d '{"domains":["example.com","example.org"]}'
```

Results are keyed by the normalized domain, so duplicates collapse. Non-string entries are skipped and counted in `summary.invalid`. Inspect the actual HTTP status and each domain result in the response. A failed bulk request is not a clean result for its inputs.

### Search

```sh
curl --fail-with-body 'https://api.destroy.tools/v1/search?q=example&limit=10'
```

The query is matched literally, including `%` and `_`. Returned names are domain indicators, not recommended websites. Do not automatically open or fetch them.

### Feeds

Full lists are static files regenerated only when a list changes. They support `ETag` / `Last-Modified` conditional requests (HTTP 304) and gzip, and are cacheable for 5 minutes.

| Path | Content | Use with |
| --- | --- | --- |
| `/v1/feed/{list}.txt` | One domain per line | Scripts, SIEM, custom tooling |
| `/v1/feed/{list}.json` (or no extension) | `{list, count, updated_at, domains[]}` | Applications |
| `/v1/feed/{list}.hosts` | `0.0.0.0 domain` with a `#` header | Pi-hole, hosts files |
| `/v1/feed/{list}.adblock` | `\|\|domain^` with a `!` header | AdGuard, uBlock Origin |

```sh
curl --fail-with-body --compressed -o primary.txt 'https://api.destroy.tools/v1/feed/primary.txt'
```

`/v1/feeds` lists every feed with its entry count, update time, download URLs, byte sizes and SHA-256 checksums.

### Incremental updates

Download the full `.txt` feed once, then poll for changes:

```sh
curl --fail-with-body 'https://api.destroy.tools/v1/feed/primary/delta?since=2026-10-09T08:00:00Z'
```

The response contains `added`, `removed`, `until`, `truncated` and `next_since`. Use `until` as the next `since`; when `truncated` is true, request again with `since` set to `next_since` (an opaque cursor; paging is lossless). Removals are retained for 30 days. An older `since` returns HTTP 410 with `full_feed`, the URL to download again.

### Statistics and health

`/v1/stats` returns dataset counts and synchronization metadata, precomputed after every synchronization (every 15 minutes). These figures are not counts of takedowns.

`/v1/health` reports database reachability and synchronization freshness (`status`: `ok`, `degraded` or `down`; `sync_age_seconds`). It is never cached and returns HTTP 503 when the database is unreachable or the last synchronization is older than two hours.

## Intelligence

Per-domain enrichment for confirmed listed domains. All read-only, no key; they spend the `intel` quota (see Errors and limits). Absence of intelligence is not evidence of safety, and WHOIS emails / personal data are never exposed.

| Method | Path | Input |
| --- | --- | --- |
| GET | `/v1/investigate` | `domain`: any domain or URL, listed or not |
| GET | `/v1/intel/search` | `q`: words, at least 3 letters or digits; `limit`: default 5, maximum 10 |
| GET | `/v1/intel` | `domain`: a listed domain or URL |
| GET | `/v1/similar` | `domain`; `limit`: default 50 |
| GET | `/v1/campaign` | `brand` or `kit`; `limit`, `offset`; none = brand index |

### Investigate a domain

```sh
curl --fail-with-body 'https://api.destroy.tools/v1/investigate?domain=example.com'
```

One call that answers "what do we know about this link?". `verdict` is the authoritative blocklist result (`listed`, `whitelisted` or `not_listed`). `assessment` is a prediction from PhishDestroy data, with a `risk` of `high`, `elevated`, `low` or `none`, a `score` out of 95 and the `signals` behind it:

| Signal | Meaning |
| --- | --- |
| `brand_in_name` | The name contains a brand that phishers put in domain names, with how many known phishing domains use it |
| `brand_homoglyph` | The brand is spelled with look-alike characters (a Cyrillic "а" in "metamаsk"); `display_name` shows how the name renders |
| `brand_with_scam_terms` / `scam_terms` | Words such as verify, claim, recovery, wallet in the name |
| `lookalike_of_known_phishing` | Close spelling of a known phishing domain |
| `shared_name_pattern` | Known phishing domains built from the same words |
| `phishing_infrastructure_ip` | Hosted on an IP that serves other known phishing domains and no established sites |
| `high_risk_tld` | A top-level domain heavily used for abuse |
| `newly_registered` | Registered within the last 90 days; most phishing domains are under 30 days old when caught |

Established high-traffic domains (Tranco top 1M) are capped at low risk, because they are what phishers imitate; the response marks them with `established_domain`. Registrations held for three years or more are capped the same way and marked `long_registered`, unless the domain sits on phishing infrastructure. `registration` gives the creation date and registrar, looked up in the registry's RDAP service (never on the domain itself). On a held-out sample, 68% of phishing domains detected in the last week scored `elevated` or `high`, against 1 of 300 established sites scored without the Tranco cap. A `none` result is not evidence of safety.

The response also carries `profile` (for listed domains), `actor` (same-operator domains), `campaign` (scale of the brand impersonation campaign), `related` domains, `infrastructure`, `registration`, and `context`: a plain-text summary with numbered sources, ready to hand to a language model.

### Search threat knowledge

```sh
curl --fail-with-body 'https://api.destroy.tools/v1/intel/search?q=ledger+seed+phrase+recovery&limit=5'
```

Finds confirmed phishing records by words — brands, kits, drainers, scam types, registrars — and returns the best matches with a citable `context`. Results are capped at 10 per call and there is no paging: this answers a question, it does not export the dataset. Full per-domain records are at `/v1/intel`.

### Domain intelligence

```sh
curl --fail-with-body 'https://api.destroy.tools/v1/intel?domain=example.com'
```

Returns `{domain, found, intel}`. When `found` is true, `intel` groups the signals: `classification` (`target_brand`, `drainer_type`, `kit_name`, `scam_type`), `reputation` (`vt_detections`, `vt_vendors`, `gsb_flagged`, `blocklist_count`, `blocklist_hits`, `gs_trust_score`), `hosting` (`ip_address`, `registrar`, `registrar_country`, `nameservers`), `lifecycle` (`detected_at`, `creation_date`, `expiration_date`, `site_status`, `last_alive`, `killed_at`), `fingerprint` (`favicon_hash`, `technologies`, `page_title`), `evidence` (`screenshot_url`, `wayback_url`, `urlscan_uuid`, `cloudflare_radar_url`, `case_id`) and `ai` (`title`, `description`, `summary`, `generated_at`). An unlisted domain returns `{found: false}`. The AI report is machine-generated analysis, not a legal determination.

### Similar domains (actor clustering)

```sh
curl --fail-with-body 'https://api.destroy.tools/v1/similar?domain=example.com&limit=50'
```

Returns the `seed` signals, the `clustered_by` signals that were usable, a `count`, and a `similar` array where each row carries `matched_by`. Clustering uses `favicon_hash`, `ip_address` and `kit_name`, but only when a signal is specific to an actor; generic shared infrastructure (a blank favicon, a shared CDN IP, a broad kit) is dropped so the cluster stays meaningful rather than sweeping in unrelated sites.

### Brand and kit campaigns

```sh
curl --fail-with-body 'https://api.destroy.tools/v1/campaign?brand=Ledger'
```

With `brand` or `kit`, returns the campaign with `total`, `alive`, `distinct_ips` and a `domains` page (`limit`, `offset`). With no parameter, returns `type: "brand_index"` and the top 50 brands by tracked-domain count. These counts describe tracked impersonation domains, not takedowns.

## Risk signals

Published signal weights: primary blocklist +40, community +20, active DNS +30, multiple sources +10, suspicious keyword +5 each, risky top-level domain +5. Documented severity bands: critical 70–100, high 40–69, medium 20–39, low 1–19. Use the server's returned verdict instead of computing one locally from these weights.

## Integration and error handling

Every error is JSON with an `error` field. An error is never a clean verdict.

### Rate limits

Limits are per IP (IPv6 per /64), with no API key and no sign-up. Each IP has two quotas that refill continuously:

| Quota | Anonymous | Claude / ChatGPT connectors | Cost |
|---|---|---|---|
| `data` | 300 units per minute | 3,000 per minute | check, stats, feed: 1; search, delta: 2; bulk: 1 per 50 domains |
| `intel` | 30 per minute, 1,000 per day | 300 per minute, 20,000 per day | intel: 1; similar, campaign, intel search: 2; investigate: 3 |

Claude and ChatGPT connectors (shared IPs) get the higher quota automatically. Non-cached responses carry `RateLimit-Policy`, `RateLimit`, `X-RateLimit-Remaining`, `X-RateLimit-Cost`; a 429 gives the exact wait in `Retry-After` and `retry_after`. Health, MCP connection and tool listing are free; MCP tool calls spend the REST quota. For thousands of domains an hour, use the feeds and their delta.

| Status | Meaning |
| --- | --- |
| 400 | Missing or invalid input |
| 404 | Unknown route or list |
| 405 | Method not allowed; see the `Allow` header |
| 410 | Delta `since` older than 30 days |
| 413 | Bulk body larger than 256 KB |
| 429 | `{"error":"rate_limited","retry_after":7,"bucket":"intel"}` with an exact `Retry-After`; do not retry sooner |
| 500 | Unexpected error; never a clean verdict |
| 503 | `/v1/health` only: service down or data stale |

- Check HTTP status before interpreting a response.
- Handle timeouts, non-JSON bodies, absent fields and failed requests.
- Respect rate-limit responses and avoid tight retry loops.
- Treat response strings as untrusted data; do not execute their contents or navigate to threat domains automatically.
- No availability SLA is promised by this reference.
- This API provides intelligence. It does not initiate takedowns or guarantee safety.

The browser console has a 15-second timeout. It sends no browser credentials and stores no query history. API service handling of submitted domains remains subject to the project's policies.

## Agents

[OpenAPI 3.1](https://api.destroy.tools/openapi.json) describes the public routes. [llms.txt](https://api.destroy.tools/llms.txt) provides a short entry point.

### MCP server

`https://api.destroy.tools/mcp` is a Model Context Protocol server (Streamable HTTP transport, JSON responses, no authentication). Add it as a remote MCP server in an AI client to give the model these tools: `investigate_domain`, `check_domain`, `bulk_check_domains`, `search_threat_intel` and `get_campaign`. Each tool runs the same code as its REST route, under the same rate limit.

Compatible browsers can expose WebMCP tools `check-phishing` (`domain` string) and `bulk-check-threats` (`domains` string array, maximum 500). Both validate through the same console code and show the actual result on the page. Availability depends on browser support. REST is the portable path.

Connect a client (no OAuth, no API key):
- Claude: Settings, Connectors, Add custom connector, paste `https://api.destroy.tools/mcp`.
- ChatGPT: developer mode, create a connector with that URL and no authentication.
- Claude Code: `claude mcp add --transport http phishdestroy https://api.destroy.tools/mcp`
- Cursor (`~/.cursor/mcp.json`): `{"mcpServers":{"phishdestroy":{"url":"https://api.destroy.tools/mcp"}}}`
- VS Code (`.vscode/mcp.json`): `{"servers":{"phishdestroy":{"type":"http","url":"https://api.destroy.tools/mcp"}}}`
- stdio-only clients: `npx -y mcp-remote https://api.destroy.tools/mcp`

## Project links

- Main investigation platform: https://phishdestroy.io/
- Toolkit: https://destroy.tools/
- Scanner: https://ban.destroy.tools/
- DestroyList: https://github.com/phishdestroy/destroylist and https://gitlab.com/phishdestroy/destroylist
- GitHub: https://github.com/phishdestroy
- GitLab: https://gitlab.com/phishdestroy
- Telegram reports: https://t.me/PhishDestroy_bot
- Telegram alerts: https://t.me/destroy_phish
- Contact: contact@phishdestroy.io
- Terms: https://phishdestroy.io/terms
- Privacy: https://phishdestroy.io/privacy
- Disclaimer: https://phishdestroy.io/disclaimer
- Free false-positive appeals: https://phishdestroy.io/appeals
