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.
Quick start
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"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_…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.
| Field | Meaning |
|---|---|
check | which check ran ("spf", "dmarc"…) |
checkedAt | when it ran (UTC) |
durationMs | how long it took |
dnsQueries | DNS queries it made (each check has a fixed budget) |
result | the result: status, findings and check-specific fields (see each endpoint) |
result.status
| Field | Meaning |
|---|---|
pass | no problems found |
warn | works, with warnings |
fail | problems that affect mail |
error | the 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):
| Who | Limit |
|---|---|
| Without a key, one check | about 20 requests per minute per client address, in bursts of up to 10 |
| Without a key, all checks | 60 requests per minute per client address |
| Slow checks (dnsbl, smtp, propagation, dmarc-report) | 6 per minute, bursts of 3 |
| With a key | 30 requests per minute per key |
| Daily quota of the free plan | 300 checks per account (resets at midnight UTC) |
Limit headers
| Header | Meaning |
|---|---|
RateLimit-Policy | the limits that apply, for example "check";q=10;w=30 — q requests per w seconds (draft-ietf-httpapi-ratelimit-headers-11, still a draft) |
RateLimit | what 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-Reset | the daily quota of your account (requests with a key), reset as a Unix time |
Retry-After | with 429 and 503: seconds to wait before retrying |
X-Request-Id | quote 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.
| Code | HTTP | Meaning |
|---|---|---|
domain_empty | 400 | The domain parameter is missing or empty. |
domain_too_long | 400 | The domain name is longer than 253 characters. |
domain_label_length | 400 | A label of the domain name is empty or longer than 63 characters. |
domain_invalid | 400 | The domain name contains characters that are not allowed. |
domain_single_label | 400 | The name has no dot (for example “localhost”). |
domain_ip_literal | 400 | An IP address was given where a domain name is expected. |
domain_reserved | 400 | The name uses a reserved or private-use suffix (.local, .test, .invalid…). |
domain_numeric_tld | 400 | The last label is numeric. |
selector_empty | 400 | The DKIM selector is missing. |
selector_invalid | 400 | The selector contains characters that are not allowed. |
ip_not_public | 400 | The address is private, reserved or otherwise not public. |
type_invalid | 400 | The record type is not supported. |
invalid_input | 400 | A parameter is not valid. |
report_format | 400 | The body is not a DMARC aggregate report (or is empty). |
report_archive | 400 | The archive is damaged or does not contain exactly one XML report. |
report_xml | 400 | The XML is not well-formed or uses features that are not allowed (DTDs, entities). |
report_not_dmarc | 400 | The XML is not a DMARC aggregate report. |
report_no_domain | 400 | The report does not name the domain it covers. |
invalid_api_key | 401 | The API key is malformed, unknown, revoked or past its rotation grace period. |
cross_site | 403 | A browser sent the request on behalf of another website. |
method_not_allowed | 405 | Wrong HTTP method; see the Allow header. |
report_too_large | 413 | The report exceeds the size or record limits. |
query_too_long | 414 | The query string is too long. |
rate_limited | 429 | A per-address or per-key limit was hit; wait Retry-After seconds. |
quota_exceeded | 429 | The account’s daily quota is used up; it resets at midnight UTC. |
domain_busy | 429 | Too many checks of the same domain are running at once. |
internal | 500 | Unexpected server error; quote X-Request-Id when reporting it. |
busy | 503 | The service is at capacity; retry after a few seconds. |
smtp_disabled | 503 | SMTP checks are switched off on this server. |
client_timeout | 504 | The 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
| Parameter | Required | Meaning | Example |
|---|---|---|---|
domain | yes | Domain name; international names are accepted and converted. | example.com |
selector | no | DKIM or BIMI selector. | default |
Fields of result (BimiResult)
| Field | Type |
|---|---|
declined | boolean |
domain | string |
findings | Finding[] |
name | string |
selector | string |
status | "pass" | "warn" | "fail" | "error" |
tags | TaglistTag[] |
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"GET /dkim
Fetches the key at <selector>._domainkey.<domain> and checks the key type, size and tags. Same check on the website
| Parameter | Required | Meaning | Example |
|---|---|---|---|
domain | yes | Domain name; international names are accepted and converted. | example.com |
selector | yes | DKIM or BIMI selector. | selector1 |
Fields of result (DkimResult)
| Field | Type |
|---|---|
dnssec | boolean |
domain | string |
findings | Finding[] |
name | string |
selector | string |
status | "pass" | "warn" | "fail" | "error" |
tags | TaglistTag[] |
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"GET /dmarc
Finds the DMARC record that applies to the domain, explains the policy and checks external report destinations. Same check on the website
| Parameter | Required | Meaning | Example |
|---|---|---|---|
domain | yes | Domain name; international names are accepted and converted. | example.com |
Fields of result (DmarcResult)
| Field | Type |
|---|---|
context | DmarcContext |
dnssec | boolean |
domain | string |
findings | Finding[] |
reporting | DmarcReporting |
status | "pass" | "warn" | "fail" | "error" |
tags | DmarcTagInfo[] |
walk | DmarcWalkStep[] |
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"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)
| Field | Type |
|---|---|
categories | map of integer |
dispositions | map of integer |
dkimAligned | integer |
findings | Finding[] |
invalid | integer |
messages | integer |
metadata | DmarcReportMetadata |
passed | integer |
passRate | number |
policy | DmarcReportPolicy |
providers | map of integer |
reverseDnsChecked | integer |
skipped | integer |
sourceCount | integer |
sources | DmarcReportSource[] |
spfAligned | integer |
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"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
| Parameter | Required | Meaning | Example |
|---|---|---|---|
target | yes | Public IPv4 or IPv6 address, or a host name. | mx.example.com |
Fields of result (DnsblResult)
| Field | Type |
|---|---|
addresses | DnsblAddressResult[] |
cached | boolean |
checkedAt | string (date-time) |
findings | Finding[] |
status | "pass" | "warn" | "fail" | "error" |
target | string |
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
"https://correct.email/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
| Parameter | Required | Meaning | Example |
|---|---|---|---|
domain | yes | Domain name; international names are accepted and converted. | example.com |
Fields of result (MtaStsResult)
| Field | Type |
|---|---|
domain | string |
findings | Finding[] |
mx | MtaStsMXMatch[] |
name | string |
policyUrl | string |
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"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
| Parameter | Required | Meaning | Example |
|---|---|---|---|
domain | yes | Domain name; international names are accepted and converted. | example.com |
Fields of result (MxResult)
| Field | Type |
|---|---|
acceptsMail | string |
dnssec | boolean |
domain | string |
findings | Finding[] |
nullMx | boolean |
providers | MxProvider[] |
records | MxHost[] |
status | "pass" | "warn" | "fail" | "error" |
ttl | integer |
implicit? | MxImplicit |
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
"https://correct.email/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
| Parameter | Required | Meaning | Example |
|---|---|---|---|
domain | yes | Domain name; international names are accepted and converted. | example.com |
type | no | Record type to compare. A, AAAA, CAA, CNAME, MX, NS, TXT | TXT |
Fields of result (PropagationResult)
| Field | Type |
|---|---|
answers | PropagationServerAnswer[] |
consistent | boolean |
domain | string |
findings | Finding[] |
reference | string[] |
status | "pass" | "warn" | "fail" | "error" |
type | string |
zone? | string |
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
"https://correct.email/api/v1/propagation?domain=example.com&type=TXT"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
| Parameter | Required | Meaning | Example |
|---|---|---|---|
target | yes | Public IPv4 or IPv6 address, or a host name. | mx.example.com |
Fields of result (RdnsResult)
| Field | Type |
|---|---|
findings | Finding[] |
ips | RdnsIPResult[] |
isIp | boolean |
status | "pass" | "warn" | "fail" | "error" |
target | string |
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
"https://correct.email/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
| Parameter | Required | Meaning | Example |
|---|---|---|---|
domain | yes | Domain name; international names are accepted and converted. | example.com |
Fields of result (SmtpResult)
| Field | Type |
|---|---|
domain | string |
findings | Finding[] |
hosts | SmtpHostResult[] |
status | "pass" | "warn" | "fail" | "error" |
curl -s -H "Authorization: Bearer $CORRECT_EMAIL_API_KEY" \
"https://correct.email/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
| Parameter | Required | Meaning | Example |
|---|---|---|---|
domain | yes | Domain name; international names are accepted and converted. | example.com |
Fields of result (SpfResult)
| Field | Type |
|---|---|
dnssec | boolean |
domain | string |
findings | Finding[] |
lookups | SpfLookupStats |
networks | SpfNetworks |
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"GET /tlsrpt
Checks the _smtp._tls record and its report destinations. Same check on the website
| Parameter | Required | Meaning | Example |
|---|---|---|---|
domain | yes | Domain name; international names are accepted and converted. | example.com |
Fields of result (TlsRptResult)
| Field | Type |
|---|---|
domain | string |
fields | TlsRptField[] |
findings | Finding[] |
name | string |
rua | ReportUriDestination[] |
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"