Verify an email
Submit an address, poll the job, and read its checks, honest verdict, and confidence score: inconclusive checks are refunded automatically.
Submit a job
POST /api/verify reserves the cost, queues the job, and returns 202 Accepted with a job_uuid.
The response sets a Location header with the poll URL and a Retry-After hint while the job runs.
curl -X POST https://api.pricklymails.com/api/verify \
-H "Authorization: Bearer pm_live_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1234" \
-d '{ "email": "someone@example.com" }'
Pass an optional Idempotency-Key header to make retries safe: for 24 hours, the same key replays the original job instead of billing a new one (the response then carries Idempotent-Replayed: true).
Poll the result
GET /api/verify/{uuid} returns the job and its per-check results, scoped to your account.
It keeps setting Retry-After until the job reaches a terminal status (completed, failed, or canceled).
Once completed, verifications holds the result: the verdict (status), its reason, the score, the action recommendation, and the per-check outcome.
The address itself is never stored, so the result identifies it only by keyed hashes.
A check your key does not enable reads { "status": "skipped" }.
Here, an address at a provider that accepts every recipient, where the mailbox probe is skipped:
{
"job_uuid": "0192f3c4-8a6e-7b1d-9c2e-5f4a3b2c1d0e",
"status": "completed",
"verifications": [
{
"email_hash": "3b8f1c0d9e7a",
"domain_hash": "a41e77c2d0b5",
"status": "valid",
"reason": "accept_all_provider",
"score": 76,
"recommendation": "send",
"duration_ms": 412,
"verified_at": "2026-10-01T09:12:45.120+00:00",
"checks": {
"syntax": true,
"mx": { "valid": true, "records": ["gmail-smtp-in.l.google.com", "alt1.gmail-smtp-in.l.google.com"] },
"disposable": false,
"role": false,
"free": true,
"typo": { "detected": false },
"smtp": { "status": "skipped" },
"catchall": { "status": "skipped" }
},
"durations": {
"syntax": 0,
"mx": 38,
"smtp": null,
"disposable": 0,
"role": 0,
"free": 0,
"catchall": null,
"typo": 0
}
}
]
}
recommendation turns the honest verdict into an action for list cleaning: remove, send, or review (see Batches).
Get the result in the same call
A request that can wait a few seconds, such as a signup form, can skip the polling: send Prefer: wait=N, the number of seconds it can wait, 12 at most.
The job is queued and billed exactly as without the header; only the response waits for it.
If the job finishes in time, the response is 200 OK with the result in verification, in the shape of a polled result; if not, it is the usual 202 Accepted, and the job keeps running.
Either way, the response carries Preference-Applied: wait=N.
curl -X POST https://api.pricklymails.com/api/verify \
-H "Authorization: Bearer pm_live_your_key" \
-H "Content-Type: application/json" \
-H "Prefer: wait=4" \
-d '{ "email": "someone@example.com" }'
{
"job_uuid": "0fdfff58-5f5a-4c52-b66c-ee78705db27c",
"status": "completed",
"estimated_cost": 7,
"idempotency_key": "14f4a9b6-9f45-4579-9bba-d4a282a08289",
"replayed": false,
"email": "someone@example.com",
"verification": {
"email_hash": "28ba1aff51a7",
"domain_hash": "4ac0ea9fe911",
"status": "invalid",
"reason": "null_mx",
"score": 0,
"recommendation": "remove",
"duration_ms": 277,
"verified_at": "2026-10-21T20:35:30.938480+00:00",
"checks": {
"syntax": true,
"mx": { "valid": false, "records": ["."] },
"disposable": { "status": "skipped" },
"role": { "status": "skipped" },
"free": { "status": "skipped" },
"typo": { "detected": false },
"smtp": { "status": "skipped" },
"catchall": { "status": "skipped" }
},
"durations": {
"syntax": 0,
"mx": 277,
"smtp": null,
"disposable": null,
"role": null,
"free": null,
"catchall": null,
"typo": 0
}
}
}
A mail server that asks to try again later delays the result by a minute or more.
Where nothing can wait that long, as in a signup hook, take a 202 as no answer yet and let the step go on.
The checks
Each address runs through the checks your key enables:
- syntax: RFC 5322 format.
- mx: the domain has usable mail servers (a missing, null, parked, or inactive mail exchanger fails here).
- smtp: the mailbox accepts mail (deliverability probe).
- disposable: a known throwaway provider, matched on the address domain or its mail exchanger.
- role: a role address such as
info@orsupport@. - free: a free webmail provider.
- catchall: the domain accepts any address. Available from the Pro plan up; it sends one extra random recipient in the same session as the mailbox probe, and only when it is requested. Without it,
checks.catchallreads{ "status": "skipped" }and no extra recipient is ever sent.
Typo detection always runs, regardless of the checks your key enables: when a likely misspelling is found, the response echoes a suggested correction.
To run other checks on one request, send them in checks, for example { "email": "jane@example.com", "checks": ["mx", "smtp", "disposable"] }.
Each one must be allowed on your plan, or the request is a 403; an unknown name is a 400.
Dependencies are added for you: smtp brings mx, and catchall brings both, and the cost follows the checks that run.
Verdict and score
Each result carries a deliverability verdict in its status field (valid, invalid, risky, unknown) and a 0-100 quality score.
When a typo is detected, the submit response echoes a suggestion (for example someone@gmial.com suggests someone@gmail.com); the polled result carries only the corrected domain, in checks.typo.suggested_domain, since the address is never stored.
The score of a mailbox the server accepted tops out at 88 when the catch-all check did not run (Free and Starter), and at 96 when it ran and ruled out a catch-all domain (Pro and up): the extra 8 points reward the one thing only that check can establish.