API Documentation
The VerifyAnyEmail REST API lets you verify email addresses one at a time or in bulk. Base URL: https://api.verifyany.email. An interactive OpenAPI explorer is available at https://api.verifyany.email/docs.
openapi.json — import into any client generator.Postman collectionImport, set your apiKey variable, and send requests.Interactive referenceTry every endpoint live in the Swagger explorer.Authentication
Create an API key in your dashboard and send it as a bearer token on every request:
Authorization: Bearer vae_your_api_keyTest mode
Build and test your integration without spending credits or probing real mailboxes. Any address at @test.verifyany.email returns a deterministic canned result — no credit is charged and nothing is stored. Use them to exercise each branch of your code:
| Test address | status | subStatus |
|---|---|---|
| deliverable@test.verifyany.email | deliverable | valid_mailbox |
| undeliverable@test.verifyany.email | undeliverable | mailbox_not_found |
| risky@test.verifyany.email | risky | catch_all |
| unknown@test.verifyany.email | unknown | smtp_unavailable |
| disposable@test.verifyany.email | risky | disposable |
| role@test.verifyany.email | risky | role_based |
Verify a single email
POST /v1/verify — costs 1 credit.
curl -X POST https://api.verifyany.email/v1/verify \
-H "Authorization: Bearer vae_your_api_key" \
-H "Content-Type: application/json" \
-d '{"email":"john@example.com"}'Example response · 200 OK
{
"ok": true,
"result": {
"email": "john@example.com",
"normalized": "john@example.com",
"account": "john",
"domain": "example.com",
"status": "deliverable",
"subStatus": "valid_mailbox",
"score": 95,
"checks": {
"syntax": true,
"hasMxRecords": true,
"smtpConnectable": true,
"mailboxExists": true,
"catchAll": false,
"roleBased": false,
"disposable": false,
"freeProvider": false,
"possibleTypo": false,
"gibberish": false
},
"suggestion": null,
"safeToSend": true,
"durationMs": 480,
"checkedAt": "2026-07-18T16:00:00.000Z"
}
}Response format
Every verification returns a result object with these fields:
| Field | Type | Description |
|---|---|---|
| status | string | deliverable · undeliverable · risky · unknown |
| subStatus | string | Fine-grained reason, e.g. valid_mailbox, mailbox_not_found, catch_all |
| score | number | Confidence 0–100 in the status |
| checks | object | Individual signal results (syntax, hasMxRecords, mailboxExists, catchAll…) |
| suggestion | string|null | “Did you mean” domain correction for probable typos |
| safeToSend | boolean | True when the address is safe to send to |
Statuses
deliverableMailbox exists and accepts mail — safe to send.undeliverableInvalid syntax, no MX, or the mailbox was rejected.riskyAccepts mail but low quality: catch-all, role or disposable.unknownCould not determine (greylisting, timeout, or probe disabled).Batch verification
Batch verification is asynchronous. You submit a list, we queue it, and you either poll for status or receive a webhook when it completes. Each address costs 1 credit.
POST /v1/verify/batch— submit up to your plan's max addresses. Returns202with a batch id.GET /v1/verify/batch/{id}— poll for status & live counts.GET /v1/verify/batch/{id}/results— fetch results (paginated:?page=&pageSize=).
1. Submit a batch
curl -X POST https://api.verifyany.email/v1/verify/batch \
-H "Authorization: Bearer vae_your_api_key" \
-H "Content-Type: application/json" \
-d '{"emails":["a@example.com","b@example.com"],"name":"my-list"}'Optional body fields: name (label) and webhookUrl (called on completion).
Response · 202 Accepted
{
"ok": true,
"batch": { "id": "cmrq…", "status": "queued", "total": 2 },
"message": "Batch queued. Poll the status endpoint or configure a webhook."
}2. Check status
GET /v1/verify/batch/{id} · 200 OK
{
"ok": true,
"batch": {
"id": "cmrq…",
"name": "my-list",
"status": "completed", // queued | processing | completed | failed | canceled
"total": 2,
"processed": 2,
"summary": {
"deliverable": 1,
"undeliverable": 1,
"risky": 0,
"unknown": 0
},
"createdAt": "2026-07-18T16:00:00.000Z",
"completedAt": "2026-07-18T16:00:07.000Z"
}
}3. Fetch results
GET /v1/verify/batch/{id}/results · 200 OK
{
"ok": true,
"page": 1,
"pageSize": 100,
"total": 2,
"results": [
{ "email": "a@example.com", "normalized": "a@example.com",
"status": "deliverable", "subStatus": "valid_mailbox",
"score": 95, "suggestion": null,
"createdAt": "2026-07-18T16:00:05.000Z" },
{ "email": "b@example.com", "normalized": "b@example.com",
"status": "undeliverable", "subStatus": "mailbox_not_found",
"score": 92, "suggestion": null,
"createdAt": "2026-07-18T16:00:06.000Z" }
]
}Webhook payload
If you pass webhookUrl, we POST this when the batch finishes (signed with header X-VAE-Signature: sha256=…):
{
"event": "batch.completed",
"data": {
"batchId": "cmrq…",
"total": 2,
"deliverable": 1,
"undeliverable": 1,
"risky": 0,
"unknown": 0
},
"sentAt": "2026-07-18T16:00:07.000Z"
}Suppression list
Maintain an account-level list of emails and whole domains that should be auto-skipped during verification. A suppressed address returns immediately with subStatus: "suppressed" andsuppressed: true — no SMTP probe runs and no credit is charged, whether it comes through /v1/verify or a batch. Send addresses as a plain text array.
# Add emails and/or domains
curl -X POST https://api.verifyany.email/v1/suppression \
-H "Authorization: Bearer vae_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "values": ["john@old.com", "spam-trap.example"], "reason": "unsubscribed" }'
# List entries
curl https://api.verifyany.email/v1/suppression -H "Authorization: Bearer vae_your_api_key"
# Remove entries
curl -X DELETE https://api.verifyany.email/v1/suppression \
-H "Authorization: Bearer vae_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "values": ["john@old.com"] }'values accepts a mix of emails and bare domains. You can also manage the list from your dashboard.
Webhooks
Instead of polling, you can be notified when a batch finishes. There's nothing to register — just include a webhookUrl when you submit the batch, and we POST thebatch.completed event to it on completion.
Specify a webhook
curl -X POST https://api.verifyany.email/v1/verify/batch \
-H "Authorization: Bearer vae_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"emails": ["a@example.com", "b@example.com"],
"name": "my-list",
"webhookUrl": "https://your-app.com/webhooks/vae"
}'You can also add a webhook URL when uploading a list from the dashboard.
Verify the signature
Every delivery includes an X-VAE-Signature: sha256=… header — an HMAC-SHA256 of the raw request body using your webhook signing secret(find it in Settings). Always verify it before trusting a payload:
import crypto from "crypto";
// Express: use a raw body parser for this route so the bytes are unmodified.
app.post("/webhooks/vae", express.raw({ type: "application/json" }), (req, res) => {
const header = req.headers["x-vae-signature"]; // "sha256=…"
const signature = Array.isArray(header) ? header[0] : header;
const expected =
"sha256=" + crypto.createHmac("sha256", process.env.VAE_WEBHOOK_SECRET)
.update(req.body).digest("hex");
// timingSafeEqual throws if the buffers differ in length, so compare lengths
// first (and require a well-formed header) before the constant-time check.
const a = Buffer.from(signature ?? "", "utf8");
const b = Buffer.from(expected, "utf8");
const ok = a.length === b.length && crypto.timingSafeEqual(a, b);
if (!ok) return res.status(401).end();
const { event, data } = JSON.parse(req.body.toString());
// event === "batch.completed", data.batchId, data.deliverable, …
res.sendStatus(200);
});2xx quickly (do heavy work async); we retry failed deliveries with exponential backoff. Verify against the raw bytes — don't re-serialize the JSON first, or the signature won't match.Rate limits
Requests are rate-limited per API key according to your plan. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers.
Errors
Errors return a JSON body with an error code and human-readable message.
| 400 | invalid_input | Malformed request body |
| 401 | unauthorized | Missing or invalid API key |
| 402 | insufficient_credits | Out of verification credits |
| 429 | rate_limited | Too many requests |
Diagnostics API
Every free DNS & deliverability tool is also available as an authenticated API, so you can run the same checks at scale (monitoring, dashboards, bulk domain audits). These endpoints use the same Authorization: Bearer API key and are rate-limited by your plan. Diagnostic calls do not consume verification credits.
| Endpoint | Purpose |
|---|---|
| GET /v1/tools/mx?domain= | MX records, hosts, resolved IPs, provider |
| GET /v1/tools/spf?domain= | SPF record + recursive DNS-lookup count |
| GET /v1/tools/dkim?domain=&selector= | DKIM key by selector (or common-selector discovery) |
| GET /v1/tools/dmarc?domain= | DMARC policy, alignment, reporting |
| GET /v1/tools/bimi?domain= | BIMI record, logo and certificate checks |
| GET /v1/tools/mta-sts?domain= | MTA-STS policy (DNS + HTTPS policy file) |
| GET /v1/tools/tls-rpt?domain= | TLS-RPT record and reporting address |
| GET /v1/tools/smtp?host= | SMTP banner, EHLO, STARTTLS, TLS, certificate |
| GET /v1/tools/reverse-dns?ip= | PTR + forward-confirmed reverse DNS |
| GET /v1/tools/blacklist?target= | IP/domain against 35+ DNS blocklists |
| GET /v1/tools/dns-propagation?domain=&type= | Compare a record across public resolvers |
| GET /v1/tools/health?domain= | Aggregated inbound email-health report + score |
| POST /v1/tools/header-analysis | Parse raw email headers (JSON body { headers }) |
Example
curl "https://api.verifyany.email/v1/tools/spf?domain=example.com" \
-H "Authorization: Bearer vae_your_api_key"Each returns a result object with status (pass / warn / fail / unknown), summary, records and findings (each with a recommendation). The same shape powers the public tool pages.