Address validation
POST /email/validate says whether an address is likely to receive: syntax, mail route, disposable, role account, typo, and your own suppression list. One address, a batch of a hundred, or a background job of fifty thousand.
Validate an address
Synchronous, because you call it from your own signup form and need the answer while the person is still typing. It needs a token with the email:validate scope.
curl -X POST https://api.mailer.quathos.com/api/v1/email/validate \
-H "Authorization: Bearer $QUATHOS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "email": "contato@gmial.com" }'{
"email": "contato@gmial.com",
"domain": "gmial.com",
"verdict": "risky",
"score": 45,
"checks": {
"valid_syntax": true,
"has_mail_exchange": true,
"disposable": false,
"role_account": true,
"suppressed": false
},
"did_you_mean": "contato@gmail.com",
"reason": "role_account"
}A list in one call
POST /email/validate/batch takes up to 100 addresses and answers in the same request, one result per address, in the shape of the single call. Duplicates are dropped first — the same address twice in one list is one validation, not two — so results can be shorter than what you sent, and it keeps the order in which each address first appeared. The response also carries usage, and every reply carries X-Validation-Quota-Remaining.
curl -X POST https://api.mailer.quathos.com/api/v1/email/validate/batch \
-H "Authorization: Bearer $QUATHOS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "emails": ["ana@example.com", "info@example.com", "ana@example.com"] }'
{
"results": [
{ "email": "ana@example.com", "verdict": "deliverable", "score": 100, "reason": "ok" },
{ "email": "info@example.com", "verdict": "risky", "score": 60, "reason": "role_account" }
],
"usage": { "used": 314, "quota": 5000, "remaining": 4686 }
}The mail route is resolved once per DOMAIN, not once per address, so a list where half the addresses are at the same provider costs half the DNS. Above 100 addresses the call is refused with a message pointing at the job endpoint below — the answer would not fit in the time any HTTP client is willing to wait.
Lists too big for one request
POST /email/validate/jobs accepts up to 50,000 addresses and answers immediately with a job id. The work runs in the background, in slices, and the counters advance as it goes — so a long job is something you watch, not something you wait on. It takes lists of any size, including small ones: if you integrate through a queue, an id is easier than a wait.
curl -X POST https://api.mailer.quathos.com/api/v1/email/validate/jobs \
-H "Authorization: Bearer $QUATHOS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "emails": ["ana@example.com", "...", "zoe@example.com"] }'
{ "id": "01a07f...", "status": "queued", "total": 42310, "processed": 0 }GET /email/validate/jobs/{id} returns the status (queued, running, done, failed), the counters, and — once done — result_url: a signed link to a CSV with one row per address, in the order you sent them, with the verdict, the score, the reason and every individual check. The link lasts an hour and the file itself is kept for 7 days; ask for the job again to get a fresh link while the file is still there. GET /email/validate/jobs lists your jobs, most recent first, with the same cursor as every other listing.
curl https://api.mailer.quathos.com/api/v1/email/validate/jobs/$JOB_ID \
-H "Authorization: Bearer $QUATHOS_TOKEN"
{
"id": "01a07f...",
"status": "done",
"total": 42310,
"processed": 42310,
"valid_count": 36104,
"risky_count": 2571,
"invalid_count": 3552,
"unknown_count": 83,
"result_url": "https://.../validation-jobs/....csv?X-Amz-Signature=..."
}Each address in a job counts one validation, and the allowance for the whole list is anticipated when the job is created, exactly like the synchronous batch: a job that does not fit is refused at creation, not abandoned halfway. A job interrupted by a restart resumes where it stopped instead of validating anything a second time, and a job that fails says so in error_message — without ever echoing an address back at you.
The four verdicts
| Verdict | What it means |
|---|---|
| deliverable | Valid syntax, the domain accepts mail, and no risk signal. |
| undeliverable | Invalid syntax, no MX or A record, or already suppressed for you. |
| risky | It should receive, but something is off: disposable domain, role account, or a likely typo. |
| unknown | We could not determine it: a DNS timeout. Neither a pass nor a fail. |
checks carries the individual signals so you can write your own policy instead of inheriting ours. has_mail_exchange is deliberately nullable: null means the DNS lookup did not answer, which is not the same as the domain having no mail route.
What it does not do
It does not open an SMTP connection to the destination to ask whether the mailbox exists. Probing mailboxes from a sending IP is one of the behaviours receivers classify as spammer activity, and it would burn the reputation the rest of the platform spends months building.
It does look at YOUR suppression list. An address that hard bounced or unsubscribed for you comes back undeliverable even when the domain is perfectly healthy, because that is the address we will refuse to send to anyway.
Quota
Each live validation counts against the monthly allowance of your plan; test-mode tokens are exempt and never count. Past the allowance the call is refused rather than charged: this version does not bill the wallet for overage.
curl https://api.mailer.quathos.com/api/v1/email/validate/usage \
-H "Authorization: Bearer $QUATHOS_TOKEN"
{ "used": 312, "quota": 5000, "remaining": 4688 }Every validate response also carries X-Validation-Quota-Remaining, so you can watch the budget without a second call.