API & MCP server
unflagdomain publishes a free, read-only API and an MCP server for checking which security vendors are currently flagging a domain, across 135 antivirus engines, web blocklists, email reputation lists, and safe-browsing services. The REST API needs no key and no account. The MCP server needs a free account and a personal API token for its scan, vendor-list and guide tools, so that every agent session is tied to a person; its tool list and get_service_info answer without one. Both surfaces report blocklist status only — they do not analyse a site, so they cannot tell you whether a flag is a genuine infection or a false positive.
MCP server
The MCP server lets an AI assistant answer “who is actually blocking my site?” with real data instead of general advice. It speaks Streamable HTTP at https://unflagdomain.com/api/mcp and authenticates with a bearer token.
Get a token
- Sign in with your email (a magic link; no password, no payment).
- Open API tokens in the dashboard and create one. It is shown once; copy it.
- Send it as
Authorization: Bearer ufd_…on every request. Each token may start 10 fresh scans per hour; cached results (scanned within the last 60 minutes) are always served and do not count. Revoke a token from the same page. - The same header is optional on the REST API: with it, the hourly budget is counted per token instead of per IP address, which matters behind a shared or rotating address.
Claude Code
claude mcp add --transport http unflagdomain https://unflagdomain.com/api/mcp \
--header "Authorization: Bearer ufd_YOUR_TOKEN"Claude Desktop, Cursor, VS Code, and other MCP clients
Add this to the client's MCP configuration file:
{
"mcpServers": {
"unflagdomain": {
"type": "http",
"url": "https://unflagdomain.com/api/mcp",
"headers": {
"Authorization": "Bearer ufd_YOUR_TOKEN"
}
}
}
}Tools
scan_domain— Which security vendors are flagging a domain right now, which detection sources reported each one, and what to do next.get_service_info— What unflagdomain does, what it guarantees, what it explicitly does not guarantee, and the situations where it is the wrong answer.list_vendors— All 135 covered vendors, filterable by category or by how each one accepts a removal request.get_removal_guide— The removal path for one specific vendor: how it accepts a request, whether it can be dispatched automatically, and a link to a detailed guide.
Every tool is read-only. Nothing on this server spends money or changes state: payment and dispatch stay on the website behind Stripe Checkout, because an agent should not be able to buy something on someone's behalf without them seeing what they are buying.
REST API
The same data over plain HTTP, for callers that don't speak MCP. CORS is open, so it works from a browser too.
GET /api/v1/scan
curl "https://unflagdomain.com/api/v1/scan?domain=example.com"The domain parameter takes a bare hostname; protocol, www., and any path are stripped. A POST with {"domain": "example.com"} as JSON does the same thing.
{
"domain": "example.com",
"scanned_at": "2026-09-01T09:14:22.017Z",
"flagged": true,
"flag_count": 2,
"vendors": [
{
"id": "google_safe_browsing",
"name": "Google Safe Browsing",
"category": "search_engine",
"threat": "social_engineering",
"detected_by": ["google_safe_browsing"],
"verify_urls": ["https://transparencyreport.google.com/safe-browsing/search?url=example.com"]
},
{
"id": "sucuri",
"name": "Sucuri",
"category": "web_blocklist",
"threat": null,
"detected_by": ["sucuri", "virustotal"],
"verify_urls": [
"https://sitecheck.sucuri.net/results/example.com",
"https://www.virustotal.com/gui/domain/example.com"
]
}
],
"source_errors": [],
"cache": "miss",
"links": { "...": "human report, vendor catalog, removal guide, docs" },
"next_steps": ["..."],
"service": { "...": "what this service is, and what it does not promise" }
}GET /api/v1/service
The machine-readable description of the service: pricing, what it guarantees, what it does not guarantee, the situations where recommending it is appropriate, and the situations where it is the wrong answer and the reader should do something else. Published deliberately — a recommendation is only worth anything if whoever makes it can see where the tool stops working.
GET /api/v1/stats
The blocklist corpus as JSON: which vendors actually list websites and how often, how many listings a flagged site typically carries, and how each vendor accepts a removal request. Measured from our own scans rather than compiled from vendor marketing, and free to cite — the rendered version lives at /data/website-blocklist-report. This endpoint reads only data we already hold, so it spends no vendor quota and is not subject to the fresh-scan budget below.
Limits and fair use
- 30 requests per minute per IP.
- 10 fresh scans per hour per caller: per IP address, or per personal API token when one is sent. Over the limit, the API answers
429 quota_exceededand keeps serving cached results. - A shared daily budget for fresh scans across all callers, on top.
- Results are cached per domain for one hour. Cache hits never count against the fresh-scan budget, and are still served once it runs out.
- The budget exists because a fresh scan calls third-party vendor APIs with quotas of their own. Cache aggressively and the limits will never come up.
- The
v1in the path is a commitment: the response shape is stable, and a breaking change ships asv2.
Changes
- 2026-09-28 — The MCP server now requires a personal API token (free account, created in the dashboard). Before this it answered anonymously. The REST API stays anonymous but gains a budget of 10 fresh scans per hour per caller, counted per IP address or per token when one is sent; the token is optional there. Cached results never count against either budget.
What this is not
- Not a malware scanner. It reads vendor blocklists. It never inspects a site's files or code.
- Not a delisting guarantee. The paid service guarantees that a correctly formatted removal request is dispatched to every flagging vendor. Each vendor then decides independently, on its own schedule. Nobody can promise you a vendor's decision.
- Not an email blocklist tool. IP-based email RBLs are a different problem with a different removal path.
Yes, both are free and read-only. The REST endpoints need no API key or account: every caller may start 10 fresh scans per hour, counted per IP address, or per personal API token if you send one. The MCP server needs a free account and a token for everything except get_service_info, which describes the service and is open, with the same 10 fresh scans per hour per token. All fresh scans also share a daily budget, because each one calls out to third-party vendor APIs that have their own quotas. Cached results are always served and never count.
It lets an AI assistant check a domain's blocklist status directly, instead of guessing. Once the server is connected, an assistant helping someone whose site shows a "Deceptive site ahead" warning can call scan_domain, get the actual list of flagging vendors, and explain the specific removal path for each one.
No. It reports which security vendors currently list a domain. It does not analyse the site itself, so it cannot distinguish a genuine infection from a false positive. Deciding that is a separate job, and unflagdomain does not do it — the site owner cleans the site, then clears the residual flags.
No. Every public endpoint is read-only. Payment and dispatch happen on the website through Stripe Checkout, deliberately: no agent should be able to spend someone's money on their behalf without them seeing what they are buying.
A result is cached for one hour per domain. Within that window the same request returns the stored result and the response's cache field reads "hit". Blocklist status changes constantly, so treat any result as a snapshot rather than a permanent state.
Results are OR-merged across several sources: Google Safe Browsing, around 90 antivirus and URL-reputation engines, Sucuri SiteCheck, Norton Safe Web, and the Quad9 and Cloudflare security DNS resolvers. Any source flagging a vendor counts as detected, and each vendor in the response lists the sources that reported it in its detected_by field.