API reference
Email verification
Written and maintained by The ProspectAPIs team
The email verification API lets you check whether an address can receive mail before you send to it. Send 1 to 25 addresses in one call to POST /v1/email/verify and get a status for each one: valid, invalid, catch-all or unknown, with the reason and the mail server's reply code. A valid or invalid result costs $0.004; catch-all and unknown results are free, so you pay only for a definite answer. Every account starts with $1.00 of credit, enough for about 250 definite checks.
$0.004
per valid or invalid result
Free
catch-all and unknown results
25
addresses per call
Request
POST /v1/email/verify takes your API key and a JSON body with one field, emails: a list of 1 to 25 addresses. An empty list, more than 25 addresses or any other field is a 400, and nothing is charged.
curl -X POST https://api.prospectapis.com/v1/email/verify \
-H "Authorization: Bearer $PROSPECTAPIS_KEY" \
-H "Content-Type: application/json" \
-d '{"emails": ["jane@acme.com", "sales@example.com"]}'Response
A 200 with one result per address in data, plus count (results returned), charged_count (results billed) and amount_usd (what this call cost).
{
"data": [
{
"email": "jane@acme.com",
"status": "valid",
"reason": "mailbox_exists",
"reason_text": "The mail server accepted this address.",
"score": 97,
"recommendation": "send",
"charged": true,
"flags": [],
"smtp_code": 250,
"catch_all_tier": null
},
{
"email": "sales@example.com",
"status": "catch-all",
"reason": "server_accepts_all",
"reason_text": "The domain accepts every address, so this one cannot be confirmed on its own.",
"score": 50,
"recommendation": "send_with_caution",
"charged": false,
"flags": ["role_account"],
"smtp_code": 250,
"catch_all_tier": "catch_all_medium"
}
],
"count": 2,
"charged_count": 1,
"amount_usd": 0.004
}| Field | Meaning |
|---|---|
| The address as you sent it. | |
| status | valid, invalid, catch-all or unknown (see the table below). |
| reason | Why it got that status, as a short code, for example not_checked_insufficient_credits. |
| reason_text | The same reason in plain words. |
| score | 0 to 100: how likely mail to this address is to arrive, from every signal the check returned. |
| recommendation | What to do with the address: send, send_with_caution, retry_later or do_not_send. |
| charged | true when this result was billed. |
| flags | Extra signals about the address, as a list of codes: role_based, free_provider, system_address, random_local_part, mailbox_full, parked_domain, new_domain, provider_accepts_unverified, name_match, cached, reported_bounce and others. |
| did_you_mean | A likely typo of a common mail domain (gmail.com for gmial.com), or null. |
| mail_provider | Who runs the domain's mail: google, microsoft, yahoo, proofpoint, mimecast or other. |
| checked_at | When the mail server was asked; for a cached result, when that check ran. |
| smtp_code | The mail server's reply code when the check reached one, else null. |
| catch_all_tier | For a catch-all domain, how likely the address is to be real; null otherwise. |
Statuses and billing
Each valid or invalid result costs $0.004, drawn from your balance. Results that come back catch-all and unknown are never charged. Which statuses are charged comes from the live price list at GET /pricing (email_verification.charged_statuses), so read it there rather than hard-coding it.
| status | Meaning |
|---|---|
| valid | The mail server accepted the address. Charged, $0.004. |
| invalid | The address cannot receive mail: the mail server said the mailbox does not exist, the domain takes no mail, the address is malformed, or it uses a throwaway (disposable) domain. Charged, $0.004. |
| catch-all | The domain accepts every address, so this one cannot be confirmed on its own. Free. |
| unknown | No definite answer: the server was slow, paced the check or timed out, or the address was not checked. Free. |
When the balance runs low
If your balance cannot pay for even one result, the call answers 402 with a top_up_url, like every other paid route, and nothing is charged. If it covers some of the addresses, the call verifies as many as it can pay for. The rest come back with status unknown and reason not_checked_insufficient_credits, free, and the response carries the header X-Result-Capped: balance. Top up and send those addresses again.
{
"error": "Insufficient credits",
"top_up_url": "https://prospectapis.com/dashboard/billing",
"message": "Out of credits. Top up at https://prospectapis.com/dashboard/billing to continue."
}Results from recent checks
When the same address was checked in the last 30 days and came back valid, or in the last 90 days and came back invalid, that result is returned without asking the mail server again. It carries the flag cached and cache_age_days, and it is billed like a fresh check. This keeps answers fast and protects the mail servers we check against.
Report what bounced
After you send, tell us what happened with POST /v1/email/outcomes: up to 100 {email, outcome} pairs a call, where outcome is bounced or delivered. It is free. A bounce you report makes your later checks of that address come back invalid with reason reported_bounce, so a list you check again never sends twice to a dead mailbox. A delivered report on a catch-all address raises its score. We store a hash of each address, never the address itself.
Bounce guarantee. When an address we reported as valid bounces, report it within 30 days and the price of that check comes back to your balance automatically, once, up to 10% of the valid checks you were charged for in those 30 days. The response says how many checks were credited in bounce_credits and credited_usd.
curl -X POST https://api.prospectapis.com/v1/email/outcomes \
-H "Authorization: Bearer $PROSPECTAPIS_KEY" \
-H "Content-Type: application/json" \
-d '{"outcomes": [{"email": "jane@acme.com", "outcome": "delivered"}]}'Large lists: verification jobs
For more than 25 addresses, start a job with POST /v1/email/verify/jobs: up to 50,000 addresses and an optional https callback_url. Each item is an address, or {"email", "first_name", "last_name"} like the single check, so a row whose address follows the person's name gets the name_match flag. A malformed line in the list does not fail the job: it comes back invalid with reason bad_syntax, free. It answers 202 with a job id and runs in the background, paced per mail provider. A server that greylists or answers slowly is tried again about 5, 15 and 30 minutes later, free, so far fewer addresses end up unknown than in a single call. Each valid or invalid result is charged as it is checked; catch-all and unknown results are free. Poll GET /v1/email/verify/jobs/:id for progress, and read results 1,000 a page from GET /v1/email/verify/jobs/:id/results, or as CSV with format=csv. When the job completes, the callback gets one POST with a Prospect-Signature header, t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 with the job's secret over <t>.<raw body> (the same scheme as watchlist deliveries). Each account runs up to 5 jobs at once and shares the checker fairly with other accounts: jobs check a few hundred addresses an hour per account, paced on purpose so the mail servers we ask keep answering, so a list of tens of thousands takes days. For an answer now, use the single-check route above. A job that makes no progress for 3 days settles its unanswered addresses as unknown, free. Results are kept 7 days.
curl -X POST https://api.prospectapis.com/v1/email/verify/jobs \
-H "Authorization: Bearer $PROSPECTAPIS_KEY" \
-H "Content-Type: application/json" \
-d '{"emails": [{"email": "jane@acme.com", "first_name": "Jane", "last_name": "Doe"}, "sales@example.com"], "callback_url": "https://your-app.com/hooks/verify"}'Sample a list. Add "sample": 500 (10 or more) and only a random 500 of the list are checked; the rest come back unknown with reason not_sampled, free, and GET /v1/email/verify/jobs/:id reports the sample's shares as an estimate for the whole list, so you can decide whether a list is worth cleaning before paying for all of it.
Monitor a list. Add "monitor_days": 30 (7 to 90) and the whole list is checked again that many days after each run, billing each definite result again; the callback and the job's summary.changed_since_last_run say how many addresses changed status. A monitored list keeps its addresses while monitoring is on. POST /v1/email/verify/jobs/:id/stop turns it off, or cancels a job that is still running.
Stream results. format=ndjson on the results route streams every result from the cursor on, one JSON object a line, with no page limit.
Clean a list in one call
POST /v1/email/clean takes up to 1,000 addresses and sorts them with quick checks, so no mail server is contacted: keep (the domain takes mail), review (role, free-provider, parked, new or likely-typo addresses, each with a reason), remove (cannot receive mail) and malformed (not an email address, as sent, free). It costs $0.001 per distinct well-formed address. Run a full check on what you keep before you send.
Your verification stats
GET /v1/email/verify/stats?days=30 returns your own mix over 1 to 90 days: totals by status, how many were charged, how many came from a recent check, and your catch-all and unknown shares. It is free.
Quick check
Send "depth": "quick" with the same request and nothing contacts the mail server: syntax, the disposable and free-provider lists, DNS (MX, SPF, DMARC), parked and new-domain flags, typo suggestions, reported bounces and a catch-all verdict we measured recently. It costs $0.001 per address, whatever the status. An address that needs its mailbox checked comes back unknown with reason not_probed. Use it to clean a signup form or a large list cheaply, then run a full check on what you will send to.
Domain lookup
GET /v1/email/domain/:domain tells you whether a domain exists and takes mail, its MX hosts and mail provider, SPF and DMARC, whether it is disposable, a free provider or parked, how old it is, and catch_all: true or false only when our own check measured it recently, otherwise null. It is free, up to 300 lookups an hour per account.
curl https://api.prospectapis.com/v1/email/domain/acme.com -H "Authorization: Bearer $PROSPECTAPIS_KEY"Find an address
POST /v1/email/find with first_name, last_name and domain checks the common company patterns against the mail server, most common first, and returns the first that is valid with its pattern and score. It costs $0.02 only when an address is found. On a catch-all domain it returns the most likely address as best_guess, unconfirmed and free.
curl -X POST https://api.prospectapis.com/v1/email/find -H "Authorization: Bearer $PROSPECTAPIS_KEY" -H "Content-Type: application/json" -d '{"first_name": "Jane", "last_name": "Doe", "domain": "acme.com"}'What a score means
GET /v1/email/verify/calibration returns the bounce rate customers actually reported per score band over the last 90 days. A band with fewer than 30 reports shows null rather than a guess. It is free.
Safe retries
Send an Idempotency-Key header on any POST and a retry with the same key and body gets the first answer back, with Idempotent-Replayed: true, and is never charged twice. Keys last 24 hours. The same key with a different body is refused with 422.
SDKs and integrations
- JavaScript (Node 18 or later, no dependencies):
npm install prospectapis, thenconst { ProspectApis } = require("prospectapis")andnew ProspectApis().verifyEmails([...]); or save the one-file client (types: prospectapis.d.ts) into your project. - Python (standard library only, one file): save the Python client into your project as a module named
prospectapis, thenfrom prospectapis import ProspectApisandProspectApis().verify_emails([...]). A PyPI package follows. - Google Sheets: paste this Apps Script into Extensions, Apps Script, add your key as a script property, and a ProspectAPIs menu verifies the selected cells.
- Zapier and Make: use their HTTP or Webhooks step with
POST https://api.prospectapis.com/v1/email/verify, headerAuthorization: Bearer your keyand body{"emails": ["{{email}}"]}; mapdata[0].statusanddata[0].recommendationto your next step.
Form widget
Check an email field as a visitor types. Create a key, open its Controls in the dashboard, choose Form widget and list your sites. Such a key can only run quick checks of up to 5 addresses, and browsers can only use it from those sites. A copied key can still be used outside a browser, so its daily limit (and a monthly spend cap, which we recommend setting) is what bounds it. Then add:
<script src="https://prospectapis.com/widget/v1.js" data-key="pa_live_..." defer></script>
<input type="email" name="email" data-prospectapis>Keys for a team
Each key can be read only (free GET routes, never spends) or carry a monthly spend cap, past which its paid calls answer 402 KEY_CAP_REACHED. Set both from the key's Controls in the dashboard.
From an agent
The MCP server exposes this route as email_verify, with an emails list of 1 to 25 addresses, the same key and the same balance. email_outcomes_report and email_verify_stats cover the two routes above, and email_find, email_domain_check and email_verify_calibration cover finding, domain lookup and calibration.
Next steps
Create a free account to get a key and the free starting balance. The quickstart covers authentication, and the errors page lists every status code. Pair it with prospect research, which can start from a work email, and read our guide to researching prospects with an AI agent for where a checked address fits in an outreach run.
Was this page helpful?