JSON API
Everything the web checker does, as JSON. No key, no signup, no tracking.
Request
GET /api/check?domain=example.com
| Parameter | Required | Notes |
|---|---|---|
domain | yes | Bare domain, URL or email address — all are accepted and normalised. |
selectors | no | Comma-separated DKIM selectors to try in addition to the built-in list. Maximum five. |
CORS is open, so you can call this from a browser. OPTIONS is handled.
Response
{
"version": 1,
"domain": "example.com",
"passing": true,
"counts": { "critical": 0, "warning": 1, "info": 2, "pass": 3 },
"findings": [
{
"id": "spf-lookup-limit",
"severity": "critical",
"title": "SPF needs 14 DNS lookups — the limit is 10",
"detail": "Receivers stop evaluating at ten lookups…",
"fix": {
"kind": "record",
"host": "example.com",
"type": "TXT",
"value": "v=spf1 include:… ip4:… -all",
"caveat": "Flattened records are a snapshot…"
}
}
],
"spf": { "found": true, "lookupCount": 14, "limitExceeded": true, "suggestedRecord": "v=spf1 …" },
"dkim": { "selectorsTried": 50, "wildcardDns": false, "keys": [ … ] },
"dmarc": { "effectivePolicy": "reject", "externalDestinations": [ … ] },
"mtaSts": { "mode": "enforce", "unmatchedMx": [] },
"dnssec": true,
"meta": { "dnsQueries": 58, "elapsedMs": 43 }
}
Fixes
A finding's fix is null or one of two kinds.
"record" is a DNS record to publish at host, ready to paste.
"action" is a list of steps.
An "action" fix may also carry managedRecord
(host, target, type, value,
caveat). It is set when the record that needs changing sits at a name
that is a CNAME, such as a _dmarc name aliased to a DMARC provider. DNS
allows nothing else at a CNAME, so the record cannot be published at
host: value is what the record at target, the
end of the alias chain, should read. Otherwise managedRecord is
null.
DMARC fields
dmarc.pct is the percentage the policy applies to. It reads
100 when the record has no pct= tag, the RFC 7489 default:
RFC 9989 removed the tag, and a record without it applies its policy to all failing
mail under either. dmarc.pctTag is the tag as published, or
null when there is none. Read it to tell the two apart.
Errors
| Status | error | Meaning |
|---|---|---|
| 400 | missing_domain | No domain parameter. |
| 400 | invalid_domain | Not a public domain name — IP addresses and internal names are rejected. A public suffix such as gov.uk is checked when it publishes SPF or DMARC, and rejected when it publishes neither. |
| 429 | rate_limited | Over the limit. Retry-After is set. |
| 502 | check_failed | Usually a DNS timeout. Retrying is reasonable. |
Limits
10 requests per 60 seconds per IP address. A single check makes around 50 outbound DNS queries, which is why the limit is what it is. If you need more, get in touch rather than working around it — a request that explains what you are building is likely to get a yes.
Stability
The version field is the contract. Fields may be added within
a version; anything that removes or changes the meaning of an existing field will
increment it. The findings[].id values are stable identifiers — build
against those rather than matching on title, which is prose and may be
reworded.
Fair use
This runs on one small server and is free because it is cheap to provide at a
reasonable volume. Cache results — DNS records change on the order of months, not
seconds — and identify your tool in the User-Agent header so a problem
can be traced to something other than an anonymous flood.
Example
curl -s "https://notspoofed.com/api/check?domain=paypal.com" | jq '.findings[] | {severity, title}'