Skip to content

Quickstart

POST /v1/verifications asks about an address, and GET /v1/verifications/{id} retrieves an answer that was not ready in time. That is the whole of an integration at a signup form, and this page is about it.

Beside it: GET /v1/domain/{domain} reports how a domain receives mail and what it publishes about sending it, for one credit (two with the domain’s registration date); and three free lookups each answer one narrow question about a domain - throwaway provider, catch-all and public mailbox provider. Everything else on this site explains what comes back.

Terminal window
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"}'

Make the credential at emvero.io/account/api-keys/ - it is shown once, so copy it before leaving the page. Authentication has the rest.

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,
"public_provider": 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 of deliverable, undeliverable, risky, unknown, pending.
  • action - what we recommend doing about it in a registration flow: allow, allow_with_email_confirmation or reject. Your product may weigh the same evidence differently; nothing about action is 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.

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.

Verdicts and pending before you write the branch that decides what to do with one.