API reference
Send an email address, get a verdict with its reason. Plain HTTP, any language. This page is complete: nothing about the API lives anywhere else.
The request
curl -s https://api.verimail.io/v3/verify \
-H "Authorization: Bearer $VERIMAIL_KEY" \
-d "email=emma.wilson@acme-corp.com"POST keeps the address out of URL logs (ours keep request lines for 30 days); GET with ?email= works too. The key goes in an Authorization: Bearer header, or as key in the body or query string.
The response
Verdict first, then the reason, then whether it cost a credit. Everything after that is what a form needs to act. Expand the evidence to see the server's own replies.
| Field | Values | Meaning |
|---|---|---|
verdict | deliverable · undeliverable · risky · unknown | Exactly four. Write your policy on this. |
reason | snake_case name | One level down: mailbox_exists, greylisted, domain_catch_all, relay_blocked, … Full list in result codes. |
billed | boolean | Whether this verdict cost a credit. unknown and syntax errors never do. |
suggested_action | accept · accept_and_confirm · reject | The safe default for a form: accept on deliverable, reject on undeliverable, accept-and-confirm on risky and unknown. A disposable address is reject whatever the mailbox said. |
did_you_mean | string | A corrected address when the domain looks like a mistyped free provider (gmial.com), otherwise empty. |
flags | object | disposable, role_account (admin@, info@, …), free_provider, catch_all (the domain accepts any address). Facts about the address, never verdicts. |
provider | string | The organization behind the domain's mail servers, as the registrable domain of its MX host: google.com, outlook.com, yahoodns.net. |
latency_ms | integer | How long this verification took, end to end. |
evidence | object, on request | The dialogue, see below. Present when you pass evidence=true. |
Also present: the original fields
Every response still carries the fields the API has answered since 2019, unchanged, so nothing you built on them breaks: status (success, or error for a syntax error or an unknown), email, result (the original label: deliverable, catch_all, hardbounce, softbounce, inbox_full, undeliverable, disposable, unknown, syntax_error), deliverable (a boolean that is also true for catch-all and disposable), user, domain, did_you_mean and code (the numeric reason, 100–500). billed is the billing truth; status is kept as it was.
Evidence
Add evidence=true and the response carries what the mail server said, in dialogue order, with the host it said it to and timings. Nothing about our side of the conversation is in it. This is the object a support ticket needs: when a user says "your form rejected my address", the rcpt_to line answers it.
{
"smtp": [
{
"step": "connect",
"reply": "220 mx1.some-bank.ng ESMTP"
},
{
"step": "helo",
"reply": "250 OK"
},
{
"step": "mail_from",
"reply": "250 OK"
},
{
"step": "rcpt_to",
"reply": "451 4.7.1 Greylisted, please try again later"
}
],
"mx_host": "mx1.some-bank.ng",
"timings_ms": {
"dns": 38,
"smtp": 610,
"total": 802
}
} Steps are connect (the banner), helo, mail_from, rcpt_to_random (the catch-all probe, when one ran) and rcpt_to (your address). error, when present, is our classification of a dialogue that never completed: timeout_read, refused, reset, tls.
Timeouts
Some mail servers stall on first contact. Half of all verifications answer within 0.6 s; the slowest 5% take more than 6 s (3,000 calls). If a person is waiting, set your own ceiling:
curl -s https://api.verimail.io/v3/verify \
-H "Authorization: Bearer $VERIMAIL_KEY" \
-d "email=j.okafor@some-bank.ng" -d "timeout=2000" -d "evidence=true" timeout is in milliseconds, between 1000 and 45000; anything outside that range is answered with a 400 that says so. When the time runs out before the mail server answers, the verdict is unknown with the reason timeout, and it is not billed. Without the parameter the API waits up to 45 s, retrying through other relays where a server refuses ours.
Parameters
| Parameter | Type | Description |
|---|---|---|
email | string | The address to verify. Required. A missing or blank value answers 200 with syntax_error, not a 400. |
key | string | Your API key, unless sent as a bearer token. |
timeout | integer, ms | Your ceiling on the wait, 1000–45000. Default 45000. |
evidence | true | Attach the evidence object. |
The codes, as JSON
The full table of reasons, with the verdict and billing of each, is served by the API itself so it can never drift from what the engine answers. The result codes page is generated from it.
curl -s https://api.verimail.io/v3/codes Errors
A 200 means the verification ran, even when the address is bad: look at verdict. Anything else is a request problem and carries { "status": "error", "code": …, "type": …, "message": … }.
| Status | Meaning |
|---|---|
400 | A parameter is malformed, for example a timeout outside 1000–45000. |
401 | The key is missing or not valid. |
402 | Monthly limit reached and no pay-as-you-go credits left. Top up on the billing page. |
403 | The key is inactive, usually an overdue payment. |
500 | Something failed on our side. Rare; please report it. |
A lighter check
/v3/check answers syntax and the existence of a mail server only, with no mailbox dialogue: { "status", "email", "email_valid", "did_you_mean" }. One credit. Use it when you want a synchronous answer in a few milliseconds and will verify properly later.
Bulk verification
For a whole list, upload a CSV in the dashboard rather than looping over the API: the file pipeline handles retries and the second pass for servers that ask us to come back, and you get one download with a verdict and reason on every row. Details on the file verification page.