Skip to content

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.

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

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 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.

The five verdicts before you write the branch that decides what to do with one.