APIFree · no API key

The dns.pizza JSON API

The same checks that power our report pages, as JSON — for scripts, CI pipelines, and AI agents. No signup, no key.

The basics

  • Anonymous callers get 30 lookups per minute per IP. Every JSON response carries X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset headers; 429 means slow down.
  • Machine-readable spec: /api/openapi.json (OpenAPI 3.1). AI-assistant site map: /llms.txt.
  • Errors are structured: { error, code, details? } with meaningful HTTP status codes.
  • These endpoints answer point-in-time questions. To keep watching — uptime, DNS changes, certificate expiry, blacklists, with alerts by email, Slack, Discord, or webhook — monitoring plans start free.
POST/api/tools/domain-age

Domain age: registration/expiry dates, registrar, Internet Archive history

RDAP/WHOIS registry dates plus first/latest Wayback Machine captures. Partial results are a feature: registry data still returns when archive.org is down, and vice versa; only when BOTH sources come back empty is the response a 404. Optional `part` runs half the check — "whois" answers in well under a second, "wayback" can take up to ~8s when archive.org is degraded; part-requests always return 200 (the skipped half is null) so callers can merge halves.

curl -s -X POST https://dns.pizza/api/tools/domain-age \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com"}'
POST/api/whois/lookup

Registration record (RDAP/WHOIS): registrar, abuse contact, dates, status codes, nameservers

Asks the domain's registry over RDAP, falling back to port-43 WHOIS for TLDs without RDAP. Returns the parsed record (`data`) plus the registry's raw response (`data.rawData`). Results are cached for up to an hour (`cached: true`). A hostname is looked up as its registrable domain (`mail.example.com` → `example.com`). A 404 with `reason: "not-registered"` means the registry's RDAP server answered 404, no such domain (reserved or premium names read the same way); `reason: "no-record-returned"` means the registry refused the query or sent nothing readable, and `reason: "no-registration-data-published"` means the TLD offers no lookup we can reach — both leave registration unknown. Signed-in callers use one of the plan's daily lookups per call.

curl -s -X POST https://dns.pizza/api/whois/lookup \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com"}'
POST/api/tools/email-auth

Email authentication: SPF, DKIM (common selectors), DMARC, MX

Checks SPF syntax and qualifier, DKIM keys across common selectors, DMARC policy/alignment/reporting, and MX records, with an overall 0–100 score and plain-language recommendations. DKIM covers common selectors only — a custom selector not found here can still exist.

curl -s -X POST https://dns.pizza/api/tools/email-auth \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com"}'
POST/api/tools/ssl

Live TLS certificate: issuer, expiry, SANs, protocol, cipher

Performs a real TLS handshake against port 443. A dead or misconfigured host still returns 200 with `valid: false` — the handshake outcome IS the answer. If the handshake failed, `error` is the raw message and `failure` names the cause in plain words with a fix (no address, nothing on port 443, refused, handshake refused, outdated TLS, weak DH key, not TLS). If a certificate was presented but is not trusted for this host (expired, issued for another name, self-signed, missing intermediate), `problem` names the cause and the fix. Revocation is not checked.

curl -s -X POST https://dns.pizza/api/tools/ssl \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com"}'
POST/api/tools/agree

Do all of a domain's authoritative nameservers give the same answers?

Walks the delegation from the root, then asks every nameserver the parent lists (and any extra the zone lists for itself, up to 13 hosts and 2 addresses each) directly for NS, SOA, A, MX and TXT, and compares them with each other and with the parent's NS set and glue. `verdict`: `agree`, `disagree` (a stale serial, lame server, record that differs, parent/zone NS mismatch or wrong glue; only a stale or differing server means the answer a resolver gets depends on which server it asks), `broken` (no nameserver answers for the zone) or `incomplete` (the servers that answered agree, but some could not be reached or asked from here: unknown, not a fault). `findings` lists each difference with level `fail`, `warn` or `note`; differing A records alone are a note (usually geo-routing). `differs[nameserver|ip][column]` marks the cells that left `consensus`. A hostname is checked as the zone it sits in. No key required; anonymous calls count against the per-IP lookup limit.

curl -s -X POST https://dns.pizza/api/tools/agree \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com"}'
POST/api/dns/lookup

DNS records for a domain, one or many types

Resolves the requested record types (default: A, AAAA, MX, NS, TXT, SOA, CAA) for the exact name given: `www.example.com` is its own DNS name (often a CNAME, or a different A than `example.com`), and underscore names such as `_dmarc.example.com` work too. An IPv4 or IPv6 address (`8.8.8.8`, `2001:4860:4860::8888`) is looked up at its reverse name (PTR by default): `domain` comes back as `8.8.8.8.in-addr.arpa` and `ip` as the address, and `nxdomain: true` there means the address has no reverse DNS. `records[type] === null` with an entry in `queryErrors` means that lookup could not be completed — NOT that the records don't exist.

curl -s -X POST https://dns.pizza/api/dns/lookup \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com"}'
POST/api/dns/blacklist

DNS blocklist (DNSBL) check for an IPv4 or IPv6 address or a domain

Checks an IPv4 or IPv6 address against IP blocklists (SpamCop, Barracuda, UCEPROTECT and others; an IPv6 address only against lists whose provider documents IPv6 listings (Spamhaus ZEN, when this server has its key), with every other list named in `notChecked`) or a domain against domain blocklists (SURBL, URIBL) plus the IPs it resolves to. No key required; anonymous calls count against the per-IP lookup limit. A hit with `listed: false` and an `error` means that list could not be checked — NOT that the target is clean. `ipResolutionFailed: true` means the domain's IPs were never checked.

curl -s -X POST https://dns.pizza/api/dns/blacklist \
  -H 'Content-Type: application/json' \
  -d '{"target":"192.0.2.1"}'
POST/api/dns/propagation

Has a DNS change propagated? Ask 26 public resolvers

Queries one record type at 26 public resolvers (Google, Cloudflare, Quad9, OpenDNS and regional ones) and reports what each returns. With `expectedValue`, a resolver returning other records is `mismatch` (counted as pending); matching is exact and case-insensitive, and MX answers read `<priority> <exchange>`. No key required; anonymous calls count against the per-IP lookup limit. Multi-region probe results (`globalResults`) are only included for signed-in paid plans.

curl -s -X POST https://dns.pizza/api/dns/propagation \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com"}'
POST/api/dns/dnssec

Is DNSSEC working for a domain? Chain of trust from the root

Walks the DNSSEC chain of trust (root → TLD → domain): DS and DNSKEY records, RRSIG signatures and NSEC/NSEC3, and asks a validating resolver what it does with the domain. `validationStatus`: `secure` (validated), `insecure` (not signed, or DNSKEY with no DS at the parent), `bogus` (validation fails — validating resolvers return SERVFAIL, so their users cannot reach the domain) or `indeterminate` (lookups failed or the chain could not be fully checked — unknown, not evidence of a problem). A name that does not exist is a 404, not a verdict. No key required; anonymous calls count against the per-IP lookup limit.

curl -s -X POST https://dns.pizza/api/dns/dnssec \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com"}'
POST/api/dns/security

DNS security score and grade (A+ to F) with recommendations

Scores a domain's DNS security posture by category — DNSSEC, email authentication (SPF, DMARC, and DKIM at the common selectors), CAA, nameserver operators and SOA hygiene, domain age from the registry's creation date, and by default blocklist status — and lists each check with pass/fail, severity and a recommendation. A check with `indeterminate: true` could not be verified (its lookup failed, or no DKIM key was found at the selectors tried) and is excluded from the score. Set `includeBlacklist: false` for a faster DNS-only score. No key required; anonymous calls count against the per-IP lookup limit and do not query the metered Abusix list.

curl -s -X POST https://dns.pizza/api/dns/security \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com"}'
POST/api/dns/threat-analysis

Does a domain look malicious? Lookalike, DGA, blocklist and DNS-anomaly signals

Runs four checks on one domain: `typosquattingAnalysis` (is it a lookalike of a well-known domain — `matches[].targetDomain` names which, e.g. paypa1.com → paypal.com; a name that uses the brand on another domain, such as paypal.com.evil.net or paypal-login.com, is a match whose `detectionMethods` include a detected `Brand Impersonation Detection` with the reason in `details`), `dgaAnalysis` (does the name look machine-generated, as malware command-and-control names do), `blacklistAnalysis` (domain and resolved-IP DNS blocklists; a list with an `error` could not be checked, which is not the same as clean) and `anomalyAnalysis` (DNS tunneling and fast-flux patterns in the live records, and `patternAnalysis.newlyRegistered`: whether the registry's creation date is under 30 days old, which fires a `newly_registered` signal; `ageKnown: false` means the registry did not answer in time, not that the name is old). `overallRiskLevel` is the strongest signal that fired (a lookalike, a domain blocklist listing, a server-address listing other than Spamhaus PBL policy listings, or a DNS anomaly), and `signals` names each one with its severity, strongest first. `overallThreatScore` (0-100) is a weighted sum of the four checks, so one strong signal can score low: read the level and `signals`, not the score. If `complete` is false, some DNS queries or blocklists did not answer (listed in `incompleteChecks`) and a `none` level is not a clean result; if no DNS query answers at all the call fails with a retryable 503. These are heuristics about the name and its DNS, not a verdict on the site's content. No key required; anonymous calls count against the per-IP lookup limit and do not query the metered Abusix list.

curl -s -X POST https://dns.pizza/api/dns/threat-analysis \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com"}'
POST/api/dns/anomaly

DNS anomaly check only: tunneling, fast-flux and DGA-like names

The DNS-only part of `/api/dns/threat-analysis`, without lookalike or blocklist checks: `overallRisk`, `anomalies` (each with type, severity and evidence), `dgaAnalysis`, `tunnelingAnalysis` and `patternAnalysis.fastFlux`. Summary headers `X-Anomaly-Risk` and `X-Anomaly-Count` carry the verdict. No key required; anonymous calls count against the per-IP lookup limit.

curl -s -X POST https://dns.pizza/api/dns/anomaly \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com"}'
POST/api/dns/email-auth

Email authentication with your own DKIM selectors

Same result as `/api/tools/email-auth` (SPF, DKIM, DMARC, MX, score and recommendations), but `dkimSelectors` replaces the common-selector list, so a sender's own selector (for example `s1` or `20230601`) can be checked. Up to 25 selectors; each is one DNS query. No key required; anonymous calls count against the per-IP lookup limit.

curl -s -X POST https://dns.pizza/api/dns/email-auth \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com"}'
POST/api/deliverability

Email deliverability grade (A+ to F) with ranked fixes

Grades whether mail to and from a domain is set up to be delivered: `score` points out of `maxScore` across authentication (SPF, DKIM on common selectors, DMARC), reputation (IPv4 MX addresses against DNS blocklists), infrastructure (reverse DNS and forward-confirmed reverse DNS per MX address, hosting or residential-proxy networks) and configuration. `issues` lists problems by severity, each with a fix. `ptrLookupFailed`, `fcrdnsLookupFailed` and `lookupFailed` (on an MX host, `auth.spf` or `auth.dmarc`) mean a lookup failed on our side — unknown, not a fault, and left out of `score` and `maxScore`. `blacklistsChecked` of `blacklistsTotal` per address is how many blocklists answered; an address none answered (including every IPv6 address) is unknown and not scored. `acceptsNoMail: true` means the only MX is a null MX ("0 .", RFC 7505: the domain accepts no mail); there are no hosts to audit, so reputation and infrastructure have max 0 and only authentication and configuration are graded. `noMx: true` means the domain publishes no MX record at all: senders fall back to its own address (RFC 5321 implicit MX) and there is no mail host to blocklist, so reputation has max 0; infrastructure scores 0 of 20 only when the domain has no A/AAAA address either (confirmed, not a failed lookup), and is otherwise not scored, because we do not connect to that address. With `sendsNoMail: true` as well (SPF is exactly `v=spf1 -all`, with no SPF errors) infrastructure is not scored, unless no A/AAAA address exists either (SPF speaks only for outbound mail, so that stays 0 of 20 and critical), and an info issue says to publish a null MX only if the domain should receive no mail. If the MX lookup failed, or MX records exist but no MX host could be resolved because lookups failed, the answer is a 503 instead of a false low grade; retry. No key required; anonymous calls count against the per-IP lookup limit and do not query the metered Abusix list.

curl -s -X POST https://dns.pizza/api/deliverability \
  -H 'Content-Type: application/json' \
  -d '{"domain":"example.com"}'
GET/api/tools/domain-age/badge

Embeddable SVG domain-age badge

GET so it works as a plain <img> on third-party sites. Successful badges are edge-cached for a day per domain. Always returns an SVG body, even for errors.

curl -s 'https://dns.pizza/api/tools/domain-age/badge?domain=example.com'
GET/api/inhabited/asn/{asn}

What lives in a network: domain-prefix incidence counts, blast radius, top TLDs, sample domains

From dns.pizza's daily census of the domain namespace. `record` counts domains with a website (`webDomains`) or a nameserver (`nsDomains`) address announced by the AS, websites over IPv6, inhabited /24s, and the blast radius (`darkNs`/`darkWeb`: domains whose every observed IPv4 nameserver address / every observed web address is in this one AS; delegation, not anycast resilience). Counts are domain-prefix incidences across the census; on this endpoint each domain is counted once per network. AS is the announcer, not where servers stand. `samples` holds at most five names, or null. `data.preview` is true while only a sample slice is published. 404 means no domains were seen in the network on the census day, not that it is empty.

curl -s 'https://dns.pizza/api/inhabited/asn/{asn}'
GET/api/inhabited/prefix

What lives at an IP address or block, per /24 (IPv4) or /32 (IPv6) cell

From dns.pizza's daily census. Each cell has website and nameserver domain counts (IPv6 cells: websites only), the announcing ASNs, the registry (RIR) and any cloud label, and `place`. A place is named only (`place.isPlace`) when the cloud provider publishes one region for every address observed in the cell; anycast and global ranges are "not a place" and a registry country is where a block is registered, not where servers sit. Accepts an IP, an IPv4 prefix /16 to /32 or an IPv6 prefix /24 to /128 (up to 64 cells listed; `totals` are subtotals when `totals.complete` is false).

curl -s 'https://dns.pizza/api/inhabited/prefix'
GET/api/zone/census

Zone census: what appeared in the DNS zones on a day and what was behind it

Daily aggregates from dns.pizza's zone census — births and deaths across ~1,050 gTLD zones (plus a .com/.net sample), the five-way split of newborns (active site / parked / registered-no-site / nothing / unknown), what the live sites are about, per-TLD counts, parking vendors and platforms — together with the day's risk-scorer summary (tiers, model-opinion counts, per-TLD flagged rates). Omit `day` for the newest day; `days` lists the 60 most recent days with headline numbers. Aggregates only; no domain is named here.

curl -s 'https://dns.pizza/api/zone/census'
GET/api/zone/census/tlds

Zone census per TLD: births, deaths and flagged-newborn rates

One row per TLD for the day: births and deaths from the zone diff, plus the risk scorer's flagged / high / critical counts and `highPer1k` (high-or-critical rows per 1,000 births; null under 100 births). `sort` = births (default), deaths, high or highPer1k.

curl -s 'https://dns.pizza/api/zone/census/tlds'
GET/api/zone/risk

Newborn domains the risk scorer flagged on a day, with the evidence

Signals, not verdicts. Each row is a domain that appeared in the zone that day and matched name, infrastructure or first-day-page heuristics (typo of a brand, credential form on a brand page, nameserver born the same day, and so on), with every matched signal and its matching text, and — for the top rows — a model's opinion labelled as an opinion with its confidence. Nothing here is a finding that a site is malicious; look at the evidence and decide for yourself. `tier` is the minimum tier to include (default high). TLDs listed in `excludedTlds` are not shown. A dispute link is included in every response.

curl -s 'https://dns.pizza/api/zone/risk'
GET/api/zone/domain/{domain}

What the zone census saw for one domain: born-on day, day-one state, risk flags

The day the domain first appeared in a zone the census watches, what was behind it that day (active site, parked, nothing, and its subject when live), and every day the risk scorer flagged it with the matched signals and any model opinion. `born: null` with an empty `risk` array means the census has no record of the name inside its retained window — it does NOT mean the domain is old or safe.

curl -s 'https://dns.pizza/api/zone/domain/example.xyz'
GET/api/domain-card/{domain}

Then-vs-now domain card: 2006-2014 AboutUs self-description vs. today, judged by TypeSafe Jev

One then-vs-now card for a domain in the Tranco top 1M that has an AboutUs record (coverage is stated in the response's `dataset.coverage`): what the domain said about itself on the AboutUs wiki (2006-2014), RDAP registration facts, whether the front page resolves today, and a TypeSafe Jev model's judgments in plain English with short codes (then vs now, what it is now, upkeep, how it's used; confidence high/medium) — dated model judgments, not verified facts; see the response's `about` field. 404 (JSON, not an error page) when the domain isn't in the dataset. A browser that opens this URL is redirected (307) to the human page https://dns.pizza/then-vs-now/{domain}; fetch, curl and other API clients get JSON.

curl -s 'https://dns.pizza/api/domain-card/example.xyz'
GET/api/status

Live dns.pizza infrastructure health

The same data behind https://dns.pizza/status — component health and data freshness for the monitoring pipeline itself.

curl -s 'https://dns.pizza/api/status'
MCP/api/mcp

Model Context Protocol server for AI agents

The same checks as native MCP tools — plus get_fix_guide (provider-specific SPF/DMARC/CAA publishing steps) and list_monitoring_plans. Streamable HTTP, stateless, no auth; network checks share the rate limit above, tool discovery is free.

claude mcp add --transport http dns-pizza https://dns.pizza/api/mcp

Building an agent or integration?

The pattern we recommend: diagnose with these free endpoints, walk the user through the fix with our provider-specific fix guides, then set up a monitor so the fix stays fixed. Questions or higher-volume needs: contact us.