Submit a single email for validation
Download OpenAPI specSubmit a single email address for validation.
Synchronous mode (no callback_url): the request blocks until validation
completes or the timeout elapses.
- Completes within timeout →
200 OKwith the full result. - Timeout exceeded →
202 Acceptedwith aresult_urlto poll.
Asynchronous mode (with callback_url): the request returns 202 Accepted
immediately. When validation completes the full result is delivered to your URL
as a signed webhook.
Idempotency — send an Idempotency-Key and a retry replays the original response
instead of spending a second credit. The request identity is email + timeout +
callback_url; the same key with a different request is a 422.
Requires the verify scope.
On this page
Parameters
header · parameters
| Parameter | Type | Description |
|---|---|---|
| Idempotency-Key | string |
Client-generated key (up to 255 characters, one per logical operation) that makes a retry safe. A repeat with the same request replays the stored response verbatim and adds Idempotency-Replayed: true; a repeat with a different request is a 422; a repeat while the first is still in flight is a 409. Records expire after 24 hours and are scoped to the account, the endpoint and the key's mode (test keys and live keys are independent namespaces). If the first request charged you and then died before answering, the retry gets 202 carrying the Verification that charge created, in whatever state it is in at that moment — never a second charge and never a dead 409. Omit the header and behaviour is unchanged: every call is new work.
example: 00000000-0000-4000-8000-000000000001
|
Request body · application/json · required
| Field | Type | Description |
|---|---|---|
| email required | string | Email address to validate. example: alice@example.com |
| timeout | integer 3–60 · default 30 |
Seconds to wait for a synchronous result (3–60). When the validation completes within this window the response is 200 OK with the full result. When the timeout elapses first the response is 202 Accepted with a result_url to poll.
example: 15
|
| callback_url | string · uri |
HTTPS URL to receive the result as a signed webhook. When supplied the request returns 202 Accepted immediately and the result is delivered asynchronously. The delivery carries an X-Signature header signed with your account signing secret (whsec_…, from your dashboard) — pin that one secret rather than storing the per-job callback_secret of every request.
example: https://yourapp.example.com/hooks/email
|
Responses
Verification with status: complete and a populated result.
· application/json
Accepted — async callback mode, sync timeout exceeded, or idempotency recovery. The body
is the SAME Verification object a 200 returns. On the first two it is status: pending, result and recovery null, and a result_url to poll; in async mode it also
carries the one-time callback_secret.
Idempotency recovery is the same shape reached a different way: if a previous request with
this Idempotency-Key charged you and then died before it could answer (a crash, a
deploy, a dropped connection), the retry is handed the verification that charge already
created, with Idempotency-Replayed: true. The work is never re-run and you are never
charged twice.
A recovery reports the resource's CURRENT state, not the state it was in when your
original request died. There is no earlier response to replay here — the request that
owed you one never wrote it — so retrying an hour later returns status: complete with a
populated result if that is what the verification now is, rather than a pending
snapshot you would have to poll past. The status code stays 202 either way: it answers
"your request was accepted", while status answers "how far has it got".
callback_url.
· application/json
code: insufficient_scope). It is a 403 rather than a 401 on purpose — the credential is valid, so retrying with it can never help; a key holding the verify scope is needed.
· application/json
The resource a charged Idempotency-Key paid for can no longer be read (code: idempotency_resource_gone). The charge committed and the thing it created has since
become unreadable — an account erasure, or the 7-day retention delete landing inside the
key's 24-hour life.
It carries its OWN code rather than the generic not_found, because the two mean opposite
things: not_found is an ordinary miss you may have caused, while this is a server-side
fault worth reporting. Retrying the same key cannot succeed.
Idempotency-Key is still in flight (code: idempotency_in_progress). Retry shortly — once the first request finishes, the same key replays its response.
· application/json
Idempotency-Key was already used for a DIFFERENT request (code: idempotency_key_reuse). The original resource is untouched and nothing was charged.
· application/json
Response schema · 200 · Verification
A single-email verification. This ONE object is what every representation of the resource
returns: the synchronous 200, the 202 that accepts an async or timed-out request, both
states of GET /api/v1/verify/{id}, every row of GET /api/v1/verify, and the
verify.completed webhook payload.
Two axes. status is the LIFECYCLE (pending or complete) and never carries a verdict.
The verdict is result.outcome, in the same vocabulary the CSV export and the dashboard use.
result and recovery are both null while pending, because a check that has not finished has
reached no conclusion — the terminal unknown is a conclusion, not an absence of one.
Test and live are separate namespaces. livemode says which one this verification belongs
to, and a key only ever sees resources of its own mode: a sandbox (ev_test_) key cannot list
or fetch a live verification, and a live key never sees a canned sandbox one.
| Field | Type | Description |
|---|---|---|
| object | string |
Always verification — the discriminator that marks this resource type.
verification
example: verification
|
| id | string · uuid |
The verification's ID, present on every representation. Pass it to GET /api/v1/verify/{id}.
example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
|
| created_at | string · date-time | UTC timestamp when the verification was accepted. It does not change when the verification completes, which is what makes the list's cursor stable while checks finish. example: 2025-06-10T14:02:09.180Z |
| livemode | boolean |
true when the verification was submitted with a LIVE key; false when it came from an ev_test_ sandbox key and the verdict is therefore a canned test result rather than a real probe. Test and live are separate namespaces — a key only ever sees resources of its own mode — so this is constant for any one key, and it is what makes a stored response, a log line or a webhook body self-describing after the fact.
example: true
|
| status | string |
The verification's LIFECYCLE — and only its lifecycle:
It never carries a verdict word. A client can switch on it without knowing whether the check happened to finish inside the request. pending
complete
example: complete
|
| string | The address as submitted. A recovery never replaces it. example: alice@example.com | |
| result | object · null |
The verdict. null while status is pending.
|
| recovery | object · null |
A probe-confirmed alternative address, when contact recovery found one. null while pending, and null when no recovery was surfaced.
|
| completed_at | string · date-time · null |
UTC timestamp when the verification finished. null while pending.
example: 2025-06-10T14:02:11.432Z
|
| credits_remaining | integer · null |
Credit balance after this request. null for system/demo accounts, and null on list rows, webhook payloads and SSE frames — a balance is a per-request figure, so read it from GET /api/v1/credits.
example: 4820
|
| result_url | string |
Where to fetch the finished verification. Present only while status is pending; once complete you are already holding what it would point at.
example: /api/v1/verify/3fa85f64-5717-4562-b3fc-2c963f66afa6
|
| callback_secret | string |
A per-verification override of the account signing secret, revealed EXACTLY ONCE — on the Shape: |
result · VerificationResult · object
null while status is pending.| Field | Type | Description |
|---|---|---|
| outcome | string |
The verification VERDICT, carried by
deliverable
invalid
catch-all
unknown
example: deliverable
|
| sub_status | string |
Additional signal within the top-level status. Empty string when none applies. Derivation is first-match: disposable > role_based > accept_all > no_dns_entries > mailbox_not_found > greylisted > mail_server_did_not_respond.
""
disposable
role_based
accept_all
no_dns_entries
mailbox_not_found
greylisted
mail_server_did_not_respond
example: ""
|
| risk | string |
Deliverability risk, assessed at PROBE TIME from everything the check observed — not derived from Broadly: Returned for both live and test-mode keys (a test-mode key returns the canned risk the address's local part maps to). OMITTED — not reported as none
low
medium
high
example: none
|
| free_email | boolean |
Whether the domain is a known free-email provider. Derived from the stored probe detail, so it is OMITTED — not reported as false — on the rare row whose stored detail cannot be read; see details.
example: false
|
| account | string |
Local part of the email address (before @).
example: alice
|
| domain | string |
Domain part of the email address (after @).
example: example.com
|
| details | object |
Low-level DNS/SMTP signal. OMITTED on the rare row whose stored probe detail exists but cannot be read — a server-side data fault. The verdict in outcome comes from a separate, intact field and stays trustworthy, so the detail is withheld rather than reported as a row of falses that would read as a confident all-clear. A row that simply has no stored detail is not that case and still returns this object with its default values.
|
details · VerifyDetails · object
outcome comes from a separate, intact field and stays trustworthy, so the detail is withheld rather than reported as a row of falses that would read as a confident all-clear. A row that simply has no stored detail is not that case and still returns this object with its default values.| Field | Type | Description |
|---|---|---|
| role | boolean |
Whether the local part is a role alias (e.g. admin, support, noreply).
example: false
|
| disposable | boolean | Whether the domain belongs to a known disposable/temporary email provider. example: false |
| catch_all | boolean | Whether the domain is configured to accept all addresses. example: false |
| mx_found | boolean | Whether at least one MX record was found for the domain. example: true |
| mx_record | string · null | The highest-priority MX hostname, or null when no MX records exist. example: mail.example.com |
| smtp_provider | string · null |
Detected SMTP provider name (e.g. Google, Microsoft), or null when unknown.
example: Google
|
| smtp_code | integer · null | SMTP response code from the final RCPT TO probe, or null when no SMTP probe was attempted. example: 250 |
recovery · VerificationRecovery · object
null while pending, and null when no recovery was surfaced.| Field | Type | Description |
|---|---|---|
| string |
The recovered address. Never a substitute for email — that is what you sent.
example: alice@gmail.com
|
|
| outcome | string |
deliverable when a probe confirmed a mailbox at the recovered address; catch-all when the fix landed on a domain that accepts everything. Drawn from the same vocabulary as result.outcome and the CSV's Outcome column, and identical to the CSV's RecoveredOutcome cell for the same row.
deliverable
catch-all
example: deliverable
|
Response schema · 202 · Verification
A single-email verification. This ONE object is what every representation of the resource
returns: the synchronous 200, the 202 that accepts an async or timed-out request, both
states of GET /api/v1/verify/{id}, every row of GET /api/v1/verify, and the
verify.completed webhook payload.
Two axes. status is the LIFECYCLE (pending or complete) and never carries a verdict.
The verdict is result.outcome, in the same vocabulary the CSV export and the dashboard use.
result and recovery are both null while pending, because a check that has not finished has
reached no conclusion — the terminal unknown is a conclusion, not an absence of one.
Test and live are separate namespaces. livemode says which one this verification belongs
to, and a key only ever sees resources of its own mode: a sandbox (ev_test_) key cannot list
or fetch a live verification, and a live key never sees a canned sandbox one.
| Field | Type | Description |
|---|---|---|
| object | string |
Always verification — the discriminator that marks this resource type.
verification
example: verification
|
| id | string · uuid |
The verification's ID, present on every representation. Pass it to GET /api/v1/verify/{id}.
example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
|
| created_at | string · date-time | UTC timestamp when the verification was accepted. It does not change when the verification completes, which is what makes the list's cursor stable while checks finish. example: 2025-06-10T14:02:09.180Z |
| livemode | boolean |
true when the verification was submitted with a LIVE key; false when it came from an ev_test_ sandbox key and the verdict is therefore a canned test result rather than a real probe. Test and live are separate namespaces — a key only ever sees resources of its own mode — so this is constant for any one key, and it is what makes a stored response, a log line or a webhook body self-describing after the fact.
example: true
|
| status | string |
The verification's LIFECYCLE — and only its lifecycle:
It never carries a verdict word. A client can switch on it without knowing whether the check happened to finish inside the request. pending
complete
example: complete
|
| string | The address as submitted. A recovery never replaces it. example: alice@example.com | |
| result | object · null |
The verdict. null while status is pending.
|
| recovery | object · null |
A probe-confirmed alternative address, when contact recovery found one. null while pending, and null when no recovery was surfaced.
|
| completed_at | string · date-time · null |
UTC timestamp when the verification finished. null while pending.
example: 2025-06-10T14:02:11.432Z
|
| credits_remaining | integer · null |
Credit balance after this request. null for system/demo accounts, and null on list rows, webhook payloads and SSE frames — a balance is a per-request figure, so read it from GET /api/v1/credits.
example: 4820
|
| result_url | string |
Where to fetch the finished verification. Present only while status is pending; once complete you are already holding what it would point at.
example: /api/v1/verify/3fa85f64-5717-4562-b3fc-2c963f66afa6
|
| callback_secret | string |
A per-verification override of the account signing secret, revealed EXACTLY ONCE — on the Shape: |
result · VerificationResult · object
null while status is pending.| Field | Type | Description |
|---|---|---|
| outcome | string |
The verification VERDICT, carried by
deliverable
invalid
catch-all
unknown
example: deliverable
|
| sub_status | string |
Additional signal within the top-level status. Empty string when none applies. Derivation is first-match: disposable > role_based > accept_all > no_dns_entries > mailbox_not_found > greylisted > mail_server_did_not_respond.
""
disposable
role_based
accept_all
no_dns_entries
mailbox_not_found
greylisted
mail_server_did_not_respond
example: ""
|
| risk | string |
Deliverability risk, assessed at PROBE TIME from everything the check observed — not derived from Broadly: Returned for both live and test-mode keys (a test-mode key returns the canned risk the address's local part maps to). OMITTED — not reported as none
low
medium
high
example: none
|
| free_email | boolean |
Whether the domain is a known free-email provider. Derived from the stored probe detail, so it is OMITTED — not reported as false — on the rare row whose stored detail cannot be read; see details.
example: false
|
| account | string |
Local part of the email address (before @).
example: alice
|
| domain | string |
Domain part of the email address (after @).
example: example.com
|
| details | object |
Low-level DNS/SMTP signal. OMITTED on the rare row whose stored probe detail exists but cannot be read — a server-side data fault. The verdict in outcome comes from a separate, intact field and stays trustworthy, so the detail is withheld rather than reported as a row of falses that would read as a confident all-clear. A row that simply has no stored detail is not that case and still returns this object with its default values.
|
details · VerifyDetails · object
outcome comes from a separate, intact field and stays trustworthy, so the detail is withheld rather than reported as a row of falses that would read as a confident all-clear. A row that simply has no stored detail is not that case and still returns this object with its default values.| Field | Type | Description |
|---|---|---|
| role | boolean |
Whether the local part is a role alias (e.g. admin, support, noreply).
example: false
|
| disposable | boolean | Whether the domain belongs to a known disposable/temporary email provider. example: false |
| catch_all | boolean | Whether the domain is configured to accept all addresses. example: false |
| mx_found | boolean | Whether at least one MX record was found for the domain. example: true |
| mx_record | string · null | The highest-priority MX hostname, or null when no MX records exist. example: mail.example.com |
| smtp_provider | string · null |
Detected SMTP provider name (e.g. Google, Microsoft), or null when unknown.
example: Google
|
| smtp_code | integer · null | SMTP response code from the final RCPT TO probe, or null when no SMTP probe was attempted. example: 250 |
recovery · VerificationRecovery · object
null while pending, and null when no recovery was surfaced.| Field | Type | Description |
|---|---|---|
| string |
The recovered address. Never a substitute for email — that is what you sent.
example: alice@gmail.com
|
|
| outcome | string |
deliverable when a probe confirmed a mailbox at the recovered address; catch-all when the fix landed on a domain that accepts everything. Drawn from the same vocabulary as result.outcome and the CSV's Outcome column, and identical to the CSV's RecoveredOutcome cell for the same row.
deliverable
catch-all
example: deliverable
|
Error responses
400 401 402 403 404 409 413 422 429 500 · ApiProblem · object
| Field | Type | Description |
|---|---|---|
| status required | integer | HTTP status code mirrored in the body. example: 400 |
| code required | string |
Stable, machine-readable error code. Branch your code on this — never on the error text, which is prose and may be reworded at any time.
example: invalid_email
|
| error required | string | Human-readable error message. example: Invalid email format. |
| param | string | The request field the failure is attributable to. Omitted when the failure is not about one specific field. example: email |
| details | array of string · null | Optional per-field validation messages. Null when not applicable. example: ["email: required"] |
| doc_url | string | Link to the documentation for this error code. example: https://emailvalidator.ai/docs/get-started/errors |
| request_id | string | Correlation id for this request. Quote it in a support request — it is how a single call is found in the server logs. example: 0HNCV1TQ3PLRK:00000003 |
Example request · application/json
Synchronous (no callback)
curl -X POST https://api.emailvalidator.ai/api/v1/verify \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "alice@example.com",
"timeout": 15
}'
{
"email": "alice@example.com",
"timeout": 15
}
Asynchronous (with callback)
curl -X POST https://api.emailvalidator.ai/api/v1/verify \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "alice@example.com",
"callback_url": "https://yourapp.example.com/hooks/email"
}'
{
"email": "alice@example.com",
"callback_url": "https://yourapp.example.com/hooks/email"
}