Quickstart
The API has two operations. POST /v1/verifications asks about an address;
GET /v1/verifications/{id} retrieves an answer that was not ready in time.
Everything else on this site explains what comes back.
curl -sS https://api.emvero.tech/v1/verifications \ -H "Authorization: Bearer emv_your_credential" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: signup-8f21c0" \ -d '{"email":"ada@example.com"}'email is the only required field. variant chooses how deep
to check and defaults to the most complete depth — omit it unless you have read
that page and want the cheaper one.
{ "id": "5f0b6f3e-1a4c-4a1f-9d2e-8f7b3c2a1d90", "email": "ada@example.com", "normalized": { "domain": "example.com", "ascii_domain": "example.com" }, "status": "deliverable", "confidence": 0.92, "action": "allow", "checks": { "syntax": "valid", "domain": "valid", "mail_routing": "valid", "smtp": "accepted", "accept_all": "no", "disposable": false, "role_account": false, "smtp_utf8_required": false }, "reason_codes": ["MX_FOUND", "SMTP_RCPT_ACCEPTED"], "freshness": { "dns_checked_at": "2026-09-05T09:14:22Z", "smtp_checked_at": "2026-09-05T09:14:23Z" }}Three fields carry the answer, and they are deliberately separate:
status— what the evidence says. One ofdeliverable,undeliverable,risky,unknown,pending.action— what we recommend doing about it in a registration flow:allow,allow_with_email_confirmationorreject. Your product may weigh the same evidence differently; nothing aboutactionis binding.reason_codes— why, from a closed vocabulary that is added to but never repurposed. Branch on these rather than on prose.
confidence is a rule-based score and not a probability.
checks reports each question’s outcome separately, so you can see which one
failed rather than only the verdict.
Handle the two cases that are not a verdict
Section titled “Handle the two cases that are not a verdict”status: "pending" means recipient evidence is still being gathered. The
response carries retry_after_ms; wait that long and GET /v1/verifications/{id}. There are no callbacks. See
Pending results.
A negative verdict is a 200. An address that fails verification is a
successful request. The 4xx and 5xx responses mean the request was malformed or
the service could not answer — never that the address is unusable. See
Errors.
A minimal integration
Section titled “A minimal integration”async function verify(email, idempotencyKey) { const post = await fetch('https://api.emvero.tech/v1/verifications', { method: 'POST', headers: { Authorization: `Bearer ${process.env.EMVERO_TOKEN}`, 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey, }, body: JSON.stringify({ email }), });
if (!post.ok) throw new Error(`emvero: ${post.status}`);
let result = await post.json();
// Poll only while the answer is genuinely outstanding, and honour the hint: // it is how long the service expects the outstanding work to take. while (result.status === 'pending') { await new Promise((r) => setTimeout(r, result.retry_after_ms ?? 2000)); const get = await fetch( `https://api.emvero.tech/v1/verifications/${result.id}`, { headers: { Authorization: `Bearer ${process.env.EMVERO_TOKEN}` } }, ); if (!get.ok) throw new Error(`emvero: ${get.status}`); result = await get.json(); }
return result;}Two things this deliberately does not do. It does not treat a thrown error as a
bad address — if the service cannot answer, the registration should proceed
behind a confirmation message, not fail. And it does not poll forever; give the
loop a deadline of your own and treat expiry as unknown.
Then read
Section titled “Then read”The five verdicts before you write the branch that decides what to do with one.