Skip to content
correct.email

API documentation

The same checks as the website, as JSON. Try any endpoint without a key; for regular use, create a key in your account.

Create an API keyOpenAPI 3.1 document (JSON)

Quick start

  1. Try a check without a key — anonymous requests have lower limits per client address:

    curl -s "https://correct.email/api/v1/spf?domain=example.com"
  2. Create an API key in your account and keep it in an environment variable, never in code: Create an API key

    export CORRECT_EMAIL_API_KEY=ce_live_…
  3. Send it as a bearer token. Checks with a key count against your account’s daily quota:

    curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
      "https://correct.email/api/v1/dmarc?domain=example.com"

API keys

Keys look like ce_live_…; we store only a hash, so a key is shown once, when you create it. Send it in the Authorization header: Authorization: Bearer ce_live_….

Never put a key in a web page or mobile app. Requests a browser makes on behalf of another website (Sec-Fetch-Site: cross-site) are refused, with or without a key.

To replace a key without downtime, rotate it in your account: you get a new secret at once and the old one keeps working for 24 hours (0 to 168 hours via the API). If a key leaked, delete it — that takes effect immediately.

Your account shows API checks per day and per key and check for the last 30 days.

Responses

Every check answers 200 with the same envelope, also when it finds problems — HTTP errors are reserved for requests that could not be run.

FieldMeaning
checkwhich check ran ("spf", "dmarc"…)
checkedAtwhen it ran (UTC)
durationMshow long it took
dnsQueriesDNS queries it made (each check has a fixed budget)
resultthe result: status, findings and check-specific fields (see each endpoint)

result.status

FieldMeaning
passno problems found
warnworks, with warnings
failproblems that affect mail
errorthe check could not complete (for example a DNS timeout) — not a verdict on the domain

result.findings lists what the check found. Each finding has a stable code (for example spf.too_many_lookups), a severity (error, warning, info, ok), a short English message and, for some codes, params. Match on the code; the message may be reworded.

Limits

Default limits (the operator can change them; the RateLimit-Policy header always has the current values):

WhoLimit
Without a key, one checkabout 20 requests per minute per client address, in bursts of up to 10
Without a key, all checks60 requests per minute per client address
Slow checks (dnsbl, smtp, propagation, dmarc-report)6 per minute, bursts of 3
With a key30 requests per minute per key
Daily quota of the free plan300 checks per account (resets at midnight UTC)

Limit headers

HeaderMeaning
RateLimit-Policythe limits that apply, for example "check";q=10;w=30 — q requests per w seconds (draft-ietf-httpapi-ratelimit-headers-11, still a draft)
RateLimitwhat is left, for example "check";r=9;t=3 — r requests now, the bucket is full again in t seconds; for "daily", t is the time to the reset
X-Quota-Limit, X-Quota-Remaining, X-Quota-Resetthe daily quota of your account (requests with a key), reset as a Unix time
Retry-Afterwith 429 and 503: seconds to wait before retrying
X-Request-Idquote it when you report a problem

Retrying after 429 or 503

# --retry honours Retry-After on 429 and 503
curl -s --retry 3 -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
  "https://correct.email/api/v1/mx?domain=example.com"

Errors

Errors use HTTP status codes and a JSON body {"error": {"code": "…", "message": "…"}}. The code is stable; the message is a short English explanation.

CodeHTTPMeaning
domain_empty400The domain parameter is missing or empty.
domain_too_long400The domain name is longer than 253 characters.
domain_label_length400A label of the domain name is empty or longer than 63 characters.
domain_invalid400The domain name contains characters that are not allowed.
domain_single_label400The name has no dot (for example “localhost”).
domain_ip_literal400An IP address was given where a domain name is expected.
domain_reserved400The name uses a reserved or private-use suffix (.local, .test, .invalid…).
domain_numeric_tld400The last label is numeric.
selector_empty400The DKIM selector is missing.
selector_invalid400The selector contains characters that are not allowed.
ip_not_public400The address is private, reserved or otherwise not public.
type_invalid400The record type is not supported.
invalid_input400A parameter is not valid.
report_format400The body is not a DMARC aggregate report (or is empty).
report_archive400The archive is damaged or does not contain exactly one XML report.
report_xml400The XML is not well-formed or uses features that are not allowed (DTDs, entities).
report_not_dmarc400The XML is not a DMARC aggregate report.
report_no_domain400The report does not name the domain it covers.
invalid_api_key401The API key is malformed, unknown, revoked or past its rotation grace period.
cross_site403A browser sent the request on behalf of another website.
method_not_allowed405Wrong HTTP method; see the Allow header.
report_too_large413The report exceeds the size or record limits.
query_too_long414The query string is too long.
rate_limited429A per-address or per-key limit was hit; wait Retry-After seconds.
quota_exceeded429The account’s daily quota is used up; it resets at midnight UTC.
domain_busy429Too many checks of the same domain are running at once.
internal500Unexpected server error; quote X-Request-Id when reporting it.
busy503The service is at capacity; retry after a few seconds.
smtp_disabled503SMTP checks are switched off on this server.
client_timeout504The check did not finish in time.

Endpoints

Base URL https://correct.email/api/v1. All checks are GET requests with query parameters, except the DMARC report analysis, which takes the file as the request body.

GET /bimi

Checks the BIMI record, the SVG logo, the mark certificate and the DMARC requirements. Same check on the website

ParameterRequiredMeaningExample
domainyesDomain name; international names are accepted and converted.example.com
selectornoDKIM or BIMI selector.default
Fields of result (BimiResult)
FieldType
declinedboolean
domainstring
findingsFinding[]
namestring
selectorstring
status"pass" | "warn" | "fail" | "error"
tagsTaglistTag[]
dmarc?BimiDMARCSummary
evidence?BimiEvidence
evidenceUrl?string
logo?BimiLogo
logoUrl?string
record?string
recordDomain?string
records?string[]
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
  "https://correct.email/api/v1/bimi?domain=example.com"

Try it

Runs a real request from your browser without a key, with the anonymous limits.

Domain name; international names are accepted and converted.

DKIM or BIMI selector.

GET /api/v1/bimi?domain=example.com&selector=default

GET /dkim

Fetches the key at <selector>._domainkey.<domain> and checks the key type, size and tags. Same check on the website

ParameterRequiredMeaningExample
domainyesDomain name; international names are accepted and converted.example.com
selectoryesDKIM or BIMI selector.selector1
Fields of result (DkimResult)
FieldType
dnssecboolean
domainstring
findingsFinding[]
namestring
selectorstring
status"pass" | "warn" | "fail" | "error"
tagsTaglistTag[]
cnames?string[]
flags?string[]
hashes?string[]
key?DkimKeyInfo
notes?string
record?string
services?string[]
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
  "https://correct.email/api/v1/dkim?domain=example.com&selector=selector1"

Try it

Runs a real request from your browser without a key, with the anonymous limits.

Domain name; international names are accepted and converted.

DKIM or BIMI selector.

GET /api/v1/dkim?domain=example.com&selector=selector1

GET /dmarc

Finds the DMARC record that applies to the domain, explains the policy and checks external report destinations. Same check on the website

ParameterRequiredMeaningExample
domainyesDomain name; international names are accepted and converted.example.com
Fields of result (DmarcResult)
FieldType
contextDmarcContext
dnssecboolean
domainstring
findingsFinding[]
reportingDmarcReporting
status"pass" | "warn" | "fail" | "error"
tagsDmarcTagInfo[]
walkDmarcWalkStep[]
orgDomain?string
policy?DmarcPolicy
pslOrgDomain?string
record?string
recordDomain?string
source?string
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
  "https://correct.email/api/v1/dmarc?domain=example.com"

Try it

Runs a real request from your browser without a key, with the anonymous limits.

Domain name; international names are accepted and converted.

GET /api/v1/dmarc?domain=example.com

POST /dmarc-report

Analyzes one DMARC aggregate report sent as the request body: authentication, alignment, sending sources and recommendations. Same check on the website

Request body: the report file (XML, or ZIP/GZIP with one XML file) — up to 1 MB uploaded, 5 MB of XML and 5,000 records. Not stored.

Fields of result (DmarcReportAnalysis)
FieldType
categoriesmap of integer
dispositionsmap of integer
dkimAlignedinteger
findingsFinding[]
invalidinteger
messagesinteger
metadataDmarcReportMetadata
passedinteger
passRatenumber
policyDmarcReportPolicy
providersmap of integer
reverseDnsCheckedinteger
skippedinteger
sourceCountinteger
sourcesDmarcReportSource[]
spfAlignedinteger
status"pass" | "warn" | "fail" | "error"
curl -s -X POST \
  -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
  -H "Content-Type: application/xml" \
  --data-binary @report.xml \
  "https://correct.email/api/v1/dmarc-report"

Try it

Runs a real request from your browser without a key, with the anonymous limits.

Without a file, the example report is sent.

POST /api/v1/dmarc-report

GET /dnsbl

Queries IP blocklists whose terms allow this use. A list that does not answer is reported as not checked, never as clean. Same check on the website

ParameterRequiredMeaningExample
targetyesPublic IPv4 or IPv6 address, or a host name.mx.example.com
Fields of result (DnsblResult)
FieldType
addressesDnsblAddressResult[]
cachedboolean
checkedAtstring (date-time)
findingsFinding[]
status"pass" | "warn" | "fail" | "error"
targetstring
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
  "https://correct.email/api/v1/dnsbl?target=mx.example.com"

Try it

Runs a real request from your browser without a key, with the anonymous limits.

Public IPv4 or IPv6 address, or a host name.

GET /api/v1/dnsbl?target=mx.example.com

GET /mtasts

Checks the _mta-sts record, fetches the HTTPS policy, compares it with the MX hosts and includes the TLS-RPT result. Same check on the website

ParameterRequiredMeaningExample
domainyesDomain name; international names are accepted and converted.example.com
Fields of result (MtaStsResult)
FieldType
domainstring
findingsFinding[]
mxMtaStsMXMatch[]
namestring
policyUrlstring
status"pass" | "warn" | "fail" | "error"
cnames?string[]
fetch?FetchResponse
id?string
policy?MtaStsPolicy
record?string
records?string[]
tlsrpt?TlsRptResult
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
  "https://correct.email/api/v1/mtasts?domain=example.com"

Try it

Runs a real request from your browser without a key, with the anonymous limits.

Domain name; international names are accepted and converted.

GET /api/v1/mtasts?domain=example.com

GET /mx

Lists MX hosts with their addresses, detects null MX, CNAME targets and private addresses, and names known mail providers. Same check on the website

ParameterRequiredMeaningExample
domainyesDomain name; international names are accepted and converted.example.com
Fields of result (MxResult)
FieldType
acceptsMailstring
dnssecboolean
domainstring
findingsFinding[]
nullMxboolean
providersMxProvider[]
recordsMxHost[]
status"pass" | "warn" | "fail" | "error"
ttlinteger
implicit?MxImplicit
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
  "https://correct.email/api/v1/mx?domain=example.com"

Try it

Runs a real request from your browser without a key, with the anonymous limits.

Domain name; international names are accepted and converted.

GET /api/v1/mx?domain=example.com

GET /propagation

Asks public resolvers and the domain’s authoritative servers for one record type and compares the answers. Same check on the website

ParameterRequiredMeaningExample
domainyesDomain name; international names are accepted and converted.example.com
typenoRecord type to compare. A, AAAA, CAA, CNAME, MX, NS, TXTTXT
Fields of result (PropagationResult)
FieldType
answersPropagationServerAnswer[]
consistentboolean
domainstring
findingsFinding[]
referencestring[]
status"pass" | "warn" | "fail" | "error"
typestring
zone?string
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
  "https://correct.email/api/v1/propagation?domain=example.com&type=TXT"

Try it

Runs a real request from your browser without a key, with the anonymous limits.

Domain name; international names are accepted and converted.

Record type to compare.

GET /api/v1/propagation?domain=example.com&type=A

GET /rdns

Looks up the PTR record of an address and checks that it resolves back (forward-confirmed reverse DNS). Same check on the website

ParameterRequiredMeaningExample
targetyesPublic IPv4 or IPv6 address, or a host name.mx.example.com
Fields of result (RdnsResult)
FieldType
findingsFinding[]
ipsRdnsIPResult[]
isIpboolean
status"pass" | "warn" | "fail" | "error"
targetstring
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
  "https://correct.email/api/v1/rdns?target=mx.example.com"

Try it

Runs a real request from your browser without a key, with the anonymous limits.

Public IPv4 or IPv6 address, or a host name.

GET /api/v1/rdns?target=mx.example.com

GET /smtp

Connects to the MX hosts on port 25 and checks STARTTLS, the TLS version and the certificate. No mail is sent. Same check on the website

ParameterRequiredMeaningExample
domainyesDomain name; international names are accepted and converted.example.com
Fields of result (SmtpResult)
FieldType
domainstring
findingsFinding[]
hostsSmtpHostResult[]
status"pass" | "warn" | "fail" | "error"
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
  "https://correct.email/api/v1/smtp?domain=example.com"

Try it

Runs a real request from your browser without a key, with the anonymous limits.

Domain name; international names are accepted and converted.

GET /api/v1/smtp?domain=example.com

GET /spf

Fetches the SPF record, follows include and redirect, counts DNS lookups against the limit of 10 and flags syntax and policy problems. Same check on the website

ParameterRequiredMeaningExample
domainyesDomain name; international names are accepted and converted.example.com
Fields of result (SpfResult)
FieldType
dnssecboolean
domainstring
findingsFinding[]
lookupsSpfLookupStats
networksSpfNetworks
status"pass" | "warn" | "fail" | "error"
record?string
records?string[]
recordStrings?string[]
tree?SpfNode
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
  "https://correct.email/api/v1/spf?domain=example.com"

Try it

Runs a real request from your browser without a key, with the anonymous limits.

Domain name; international names are accepted and converted.

GET /api/v1/spf?domain=example.com

GET /tlsrpt

Checks the _smtp._tls record and its report destinations. Same check on the website

ParameterRequiredMeaningExample
domainyesDomain name; international names are accepted and converted.example.com
Fields of result (TlsRptResult)
FieldType
domainstring
fieldsTlsRptField[]
findingsFinding[]
namestring
ruaReportUriDestination[]
status"pass" | "warn" | "fail" | "error"
cnames?string[]
record?string
records?string[]
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
  "https://correct.email/api/v1/tlsrpt?domain=example.com"

Try it

Runs a real request from your browser without a key, with the anonymous limits.

Domain name; international names are accepted and converted.

GET /api/v1/tlsrpt?domain=example.com