Vouch API
One authenticated GET verifies an email address: syntax, live MX lookup, a real SMTP RCPT TO handshake and catch-all detection, folded into a single verdict with the receipts. No email is ever sent. Base URL: https://vouch.fast — all responses are JSON unless noted.
Overview
Vouch answers one question — will mail to this address land? — by holding a live SMTP conversation with the receiving mail server, the same opening steps a real delivery uses, and quitting before any message is transmitted. Every answer carries:
- a status you can branch on —
deliverable,risky,undeliverableorunknown, - a machine-readable reason (one of 11 fixed codes),
- a 0–100 score for ranking and thresholds,
- per-stage checks and quality flags (disposable, role account, free provider), plus a typo suggestion in
didYouMean.
Most verifications return in about a second; each SMTP step has a five-second timeout and a whole probe is capped at eight seconds, so Vouch fails fast on unresponsive servers.
Quickstart
Grab an API key from the dashboard(it's shown once, at mint time), then:
curl 'https://vouch.fast/v1/[email protected]' \ -H 'Authorization: Bearer vch_live_YOUR_KEY'
const res = await fetch(
`https://vouch.fast/v1/verify?email=${encodeURIComponent(email)}`,
{ headers: { Authorization: `Bearer ${process.env.VOUCH_API_KEY}` } },
);
const result = await res.json();
if (result.status === "deliverable") {
// send with confidence
} else if (result.didYouMean) {
// offer the corrected address back to the user
}import os, requests
res = requests.get(
"https://vouch.fast/v1/verify",
params={"email": "[email protected]"},
headers={"Authorization": f"Bearer {os.environ['VOUCH_API_KEY']}"},
timeout=15,
)
result = res.json()
print(result["status"], result["reason"], result["score"])Want to see it run before signing in? The Try it box on the homepage calls the same engine, no key required.
Authentication
Every /v1 endpoint takes your API key — format vch_live_… — in the Authorization header:
Authorization: Bearer vch_live_YOUR_KEY
Where headers are awkward (spreadsheet importers, quick tests) a ?apikey= query parameter is also accepted. Prefer the header: query strings end up in logs.
Keys are credentials, not wallets — rotating or deleting a key in the dashboard never touches your credit balance. A request with a missing, unknown or deactivated key returns 401.
Verify one address
GET/v1/verify?email={address}
Runs the full pipeline — syntax → MX → SMTP probe → catch-all — short-circuiting the network when an earlier stage already settles the verdict (bad syntax, no mail hosts, disposable domain).
| Query parameter | Required | Description |
|---|---|---|
email | yes | The address to verify. |
apikey | no | Alternative to the Authorization header. |
Response — 200
{
"email": "[email protected]",
"user": "ada",
"domain": "lovelace.dev",
"status": "deliverable",
"reason": "mailbox_exists",
"score": 97,
"checks": {
"syntax": true,
"domainHasMx": true,
"smtpConnected": true,
"mailboxExists": true,
"catchAll": false
},
"flags": {
"disposable": false,
"roleAccount": false,
"freeProvider": false
},
"didYouMean": null,
"mxHost": "aspmx.l.google.com",
"meta": { "durationMs": 412, "checkedAt": "2026-06-12T09:30:00.000Z" }
}| Field | Type | Meaning |
|---|---|---|
email · user · domain | string | The normalized address and its two halves. |
status | enum | deliverable · risky · undeliverable · unknown. Branch on this. |
reason | enum | Why — one of the 11 codes below. |
score | integer | 0–100 confidence, explained below. |
checks | object | Per-stage booleans: syntax, domainHasMx, smtpConnected, mailboxExists, catchAll. |
flags | object | disposable (burner domain), roleAccount (info@, billing@, …), freeProvider (gmail.com, outlook.com, …). Flags inform the score; what to do with them is your policy. |
didYouMean | string · null | A corrected address when the domain looks like a typo ([email protected] → [email protected]). Ideal for signup-form hints. |
mxHost | string · null | The mail host that answered (or the highest-priority one found). |
meta | object | durationMs and checkedAt (ISO 8601). |
Statuses & reason codes
status is the verdict; reason is the evidence. The pairing is fixed — every reason always maps to the same status:
| Reason | Status | What happened |
|---|---|---|
mailbox_exists | deliverable | The server accepted RCPT TO for this exact mailbox. |
catch_all | risky | The domain accepts any local-part, so acceptance proves little. Mail usually lands, but the address may not exist. |
role_account | risky | Mailbox exists but is a shared role address (info@, support@) — weak for outreach, fine for receipts. |
invalid_syntax | undeliverable | Not a well-formed mailbox address. Nothing was probed. |
no_mx_records | undeliverable | The domain has no MX (or fallback A) records — nowhere to deliver. |
disposable_domain | undeliverable | A known burner/temp-mail domain. Short-circuits before any network call. |
mailbox_not_found | undeliverable | The server rejected the recipient with a 5xx. Sending would bounce. |
greylisted | unknown | The server said "try again later" (4xx). Re-verify after 15–30 minutes. |
connection_failed | unknown | No SMTP conversation could be established. |
timeout | unknown | The probe ran out of its time budget mid-conversation. |
unexpected_error | unknown | Something else went sideways. Retry, then treat as unknown. |
Treat unknownas "retry later", not as a verdict — servers greylist, rate-limit and hiccup.
The score
score compresses the verdict and the flags into one 0–100 number: a base by status — deliverable 100, risky 55, unknown 30, undeliverable 0 — multiplied by penalties: disposable forces 0, catch-all ×0.8, role account ×0.8, free provider ×0.9.
Use status for hard branching and score for soft decisions — list cleaning thresholds, lead ranking, or "accept above 80, review 40–80, reject below".
Errors & rate limits
Handlers never throw HTML at you: every API-level problem is JSON shaped { "error": "<code>", "message": "<human text>" }.
| HTTP | error | When |
|---|---|---|
| 400 | missing_email | No ?email= on /v1/verify. |
| 401 | missing_api_key · invalid_api_key | No key, unknown key, or a deactivated key. |
| 402 | insufficient_credits | The account's credit balance is empty (header X-Credits-Remaining: 0). |
| 429 | rate_limited | Per-key limit exceeded — wait Retry-After seconds (also in the body as retryAfter). |
| 500 | server_error | Vouch itself failed. Never charged a credit; safe to retry. |
Rate limits
Verification calls are limited per key — 60 per minute by default. Every /v1/verify response carries X-RateLimit-Limit and X-RateLimit-Remaining. Batch status polls and result downloads don't count against the limit, so poll freely.
Need sustained throughput beyond the default? Don't hammer the single endpoint — upload a batch, which runs server-side in parallel, or write [email protected].
Bulk verification
Upload a CSV or Excel file; Vouch scans every cell for addresses, lowercases and de-duplicates them, verifies each unique address in parallel as a background job, and hands back a results CSV. The de-dup report comes back immediately — you only ever pay for unique addresses.
POST/v1/batches
multipart/form-data with the file in a file field. Accepts .csv and .xlsx, up to 20 MB and 10,000 unique addresses per job (the report says if the list was capped).
curl -X POST 'https://vouch.fast/v1/batches' \ -H 'Authorization: Bearer vch_live_YOUR_KEY' \ -F '[email protected]'
{
"jobId": "684aa1b2c3d4e5f60718",
"status": "pending",
"total": 4810,
"report": {
"candidatesFound": 5214,
"duplicatesRemoved": 404,
"uniqueCount": 4810,
"capped": false
}
}GET/v1/batches
Your recent jobs, newest first, as { "batches": [ … ] } — each entry shaped like the single-job response below.
GET/v1/batches/{id}
Status and progress. Poll until status is complete (it moves pending → processing → complete, or failed with an error message).
{
"id": "684aa1b2c3d4e5f60718",
"filename": "subscribers.csv",
"status": "processing",
"total": 4810,
"processed": 1932,
"report": { "candidatesFound": 5214, "duplicatesRemoved": 404, "uniqueCount": 4810, "capped": false },
"counts": { "deliverable": 1410, "undeliverable": 361, "risky": 102, "unknown": 59, "skipped": 0 },
"hasResults": false,
"error": null,
"createdAt": "2026-06-12T09:14:03.000Z",
"finishedAt": null
}GET/v1/batches/{id}/results
The results CSV (text/csv) once the job is complete — 409 not_ready before that. Columns: email, status, reason, score, disposable, roleAccount, freeProvider, catchAll, didYouMean, mxHost.
Free list health check
A structural-only diagnosis of a whole list — syntax, disposable domains, MX records, role accounts — with no SMTP probe and no credit charge. The response is aggregate-only: counts and percentages per problem category, never per-address verdicts. Every API key gets one free check.
POST/v1/health-check
JSON body with an emails array — up to 50,000 addresses, de-duplicated on intake. Returns 202 with a background job. A repeat POST replays your existing job (alreadyUsed: true) instead of starting a new one; a failed job doesn't burn the allowance.
curl -X POST 'https://vouch.fast/v1/health-check' \
-H 'Authorization: Bearer vch_live_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{ "emails": ["[email protected]", "[email protected]"] }'{
"jobId": "6a63f1c200a1b2c3d4e5",
"status": "pending",
"total": 4810,
"intake": { "candidatesFound": 5214, "duplicatesRemoved": 404, "uniqueCount": 4810, "capped": false },
"alreadyUsed": false,
"message": "Your free List Health Check is running — poll GET /v1/health-check until status is 'complete'."
}GET/v1/health-check
Poll until status is complete — then the aggregate report appears: a per-category breakdown, independent quality flags, and roll-ups. summary.unhealthy is the headline number. Structurally clean addresses stay okUnverified — only the full SMTP verification can confirm a mailbox exists.
{
"jobId": "6a63f1c200a1b2c3d4e5",
"status": "complete",
"total": 4810,
"processed": 4810,
"intake": { "candidatesFound": 5214, "duplicatesRemoved": 404, "uniqueCount": 4810, "capped": false },
"report": {
"total": 4810,
"checkedAt": "2026-06-12T09:30:00.000Z",
"breakdown": {
"invalidSyntax": { "count": 96, "pct": 2.0 },
"disposable": { "count": 231, "pct": 4.8 },
"noMxRecords": { "count": 185, "pct": 3.8 },
"roleAccount": { "count": 231, "pct": 4.8 },
"okUnverified": { "count": 4067, "pct": 84.6 }
},
"flags": {
"freeProvider": { "count": 1920, "pct": 39.9 },
"typo": { "count": 27, "pct": 0.6 }
},
"summary": {
"undeliverable": { "count": 512, "pct": 10.6 },
"problematic": { "count": 231, "pct": 4.8 },
"unhealthy": { "count": 743, "pct": 15.4 },
"deliverableUnverified": { "count": 4067, "pct": 84.6 }
},
"intake": { "candidatesFound": 5214, "duplicatesRemoved": 404, "uniqueCount": 4810, "capped": false }
},
"error": null,
"createdAt": "2026-06-12T09:29:41.000Z",
"finishedAt": "2026-06-12T09:30:00.000Z",
"message": "15.4% of your 4810 addresses look undeliverable or problematic. Run a full verification to confirm which of the rest are real, reachable mailboxes."
}Credits
Vouch is pre-paid: verifications draw down a credit balance that lives on your account, not on any API key.
- Every new account starts with 250 free credits — granted automatically at signup, no card required.
- 1 credit per completed verification, whatever the verdict — a definitive
undeliverableis exactly as useful as adeliverable. - If Vouch itself errors (a 5xx), you are not charged.
- Batches charge per unique address as it completes — duplicates are removed before verification, free. If the balance runs out mid-job, remaining rows are marked
skippedand aren't charged. - When enforcement applies and the balance is empty, requests return
402 insufficient_credits; successful/v1/verifyresponses carryX-Credits-Remaining.
Check your balance and top up from the dashboard.
MCP server
@datapad-nl/vouch-mcp gives any MCP client — Claude Desktop, Claude Code, Cursor, Cline — a verify_email tool backed by this API. The server runs locally via npx; your key is sent only to the Vouch API over HTTPS.
{
"mcpServers": {
"vouch": {
"command": "npx",
"args": ["-y", "@datapad-nl/vouch-mcp"],
"env": { "VOUCH_API_KEY": "vch_live_YOUR_KEY" }
}
}
}Or one command in Claude Code:
claude mcp add vouch --env VOUCH_API_KEY=vch_live_YOUR_KEY -- npx -y @datapad-nl/vouch-mcp
The tool takes { "email": "…" } and returns a one-line summary plus the same JSON verdict as /v1/verify — each call is a normal verification (1 credit). Ask your assistant “is this address real?” and it does the rest.
OpenAPI
The full machine-readable spec lives at https://vouch.fast/openapi.json (OpenAPI 3.1) — point your generator, Postman, or agent at it. There's also a plain-text overview for LLMs at /llms.txt.