Reason codes & errors
This page documents two distinct things:
- Reason codes — the
reasonfield returned in every successful (200 OK) validation, qualifying why an email was deemed valid or invalid. - HTTP error codes — failures of the API itself (auth, quota, rate limit, etc.).
Reason codes (the reason field in a 200 response)
A 200 OK response always includes a valid boolean and a reason qualifying the result:
reason |
Meaning |
|---|---|
ok |
Valid email |
syntax_invalid |
RFC 5322 format not respected |
mx_missing |
Domain has no MX or A record |
disposable |
Known disposable email provider |
possible_typo |
Domain has no MX AND matches a popular-domain typo — see suggested_email |
smtp_reject |
SMTP rejected the address (550 / 551 / 553) |
smtp_catch_all |
The server accepts any RCPT TO — existence cannot be confirmed |
smtp_anti_probe |
Provider blocks RCPT TO probes (Proton, Tutanota, etc.) |
smtp_silent |
Server drops the connection silently (anti-enumeration strict) |
smtp_unknown |
Unreachable server, timeout, or unknown response |
Using confidence + reason in your logic
valid is a fast binary verdict, but sometimes you want more granularity:
| Use case | Recommended check |
|---|---|
| Anti-typo filtering (web form, fast) | valid === true |
| Critical signup (SaaS, newsletter) | valid === true && confidence === 'verified' |
| Lead scoring, list cleaning | Inspect details.disposable, details.role_based, score |
| Accept unverifiable | Accept reason === 'smtp_unknown' or smtp_anti_probe as "probably OK" |
Why smtp_unknown / smtp_anti_probe happens
Some servers (Proton, Tutanota) systematically reject RCPT TO probes for privacy reasons. Others (strict cPanel, o2switch) drop the TCP connection silently. In these cases, we cannot confirm existence without actually sending a real email. If this is blocking for your business, fall back to MX + disposable + role_based checks and use a double opt-in strategy.
HTTP error codes
These are returned when the API call itself fails (auth, quota, rate limit, etc.) — they're separate from the reason field above.
| Code | Error | Body | Retry? |
|---|---|---|---|
| 400 | Malformed payload | {"error":"bad_request"} |
No |
| 401 | Missing key | {"error":"missing_api_key"} |
No |
| 401 | Invalid / revoked key | {"error":"invalid_api_key"} |
No |
| 402 | Insufficient credits | {"error":"insufficient_credits","balance":0,...} |
No (top up) |
| 422 | Missing email | {"error":"email_required"} |
No |
| 429 | Rate limit exceeded | {"error":"rate_limited"} + Retry-After |
Yes (wait) |
| 5xx | Internal error | {"error":"internal_error"} |
Yes (expo backoff) |
Retry strategy
- 429 — use
Retry-After, never retry without a delay. - 5xx — exponential backoff (1s, 2s, 4s, 8s max, 3 attempts).
- 401, 402, 422 — fix client-side, don't retry.