1. Home
  2. API docs

Email verification and phone validation API

Send one HTTP request with an email address or a phone number and get back a verdict: valid, risky or invalid for emails, and the E.164 form, country and number type for phones. Pro ($39 a month) includes 10,000 calls a month, then $1.50 per 1,000; Business and Agency include more.

See a verdict before you write any code

Type any address. It gets the same check the paid plans run: syntax, the domain’s mail servers and, where the provider allows it, a live mailbox check. Nothing is sent to the address.

Five checks a minute, no account needed. Nothing is sent to the address — we ask its mail server whether the mailbox exists, then disconnect.

Have a list? Sign in with Google and the first 100 addresses are checked free, with results to download. No card.

By continuing you agree to our Terms and Privacy Policy.

Authentication

Create a key on the API keys page and send it in the Authorization header. Keys start with sv_live_ and are shown once. Keep them on your server.

Authorization: Bearer sv_live_...

Verify an email address

GET or POST https://simpleverifier.com/api/v1/verify with email. Syntax, domain, disposable and live mailbox (SMTP) checks run in one call.

curl "https://simpleverifier.com/api/v1/[email protected]" \
  -H "Authorization: Bearer $SIMPLEVERIFIER_KEY"
{
  "email": "[email protected]",
  "status": "valid",
  "reason": "Mailbox exists",
  "send_advice": "safe",
  "reason_detail": "The mail server confirmed this mailbox exists.",
  "catch_all_confidence": null,
  "catch_all_reasons": [],
  "catch_all_signals": [],
  "smtp_available": true,
  "mailbox_probed": true,
  "mailbox_checked": true,
  "action": "send",
  "risk": "low",
  "likely_invented": false,
  "reason_code": "mailbox_exists",
  "score": 100,
  "score_basis": "confirmed"
}

send_advice and reason_detail match the columns of a list download. send_advice is one of safe (mailbox confirmed), care (the domain takes mail but the mailbox could not be confirmed), retry (the server did not answer our check; try later) or do_not_send (it will bounce).

catch_all_confidence, catch_all_reasons and catch_all_signals are new and optional to read. They are set only when reason is Accepts all addresses: the label is higher, medium or lower likelihood that the mailbox is real, and the reasons are plain sentences, and the signals are stable codes (matches_confirmed_format, matches_confirmed_single_word, matches_list_format, differs_from_confirmed_format, name_guess_variants, placeholder_address, disposable_domain, department_inbox, random_looking, free_provider). Every other reply has null, [] and []. The row stays risky with send_advice care; the label is a ranking, not a percentage, and it opens no extra mail connection. How it is built and measured. List downloads carry the same three columns (reasons joined into one cell, signals joined with ;). filter=safe_likely returns safe rows plus higher-likelihood catch-alls, and filter=safe_not_lower returns safe rows plus every catch-all not ranked lower.

{
  "email": "[email protected]",
  "status": "risky",
  "reason": "Accepts all addresses",
  "send_advice": "care",
  "catch_all_confidence": "higher",
  "catch_all_reasons": [
    "Looks like a person's name (first.last).",
    "Same first.last format as 2 confirmed addresses on this domain in earlier checks.",
    "Still unconfirmed: the server accepts every address. Send in small batches and watch bounces."
  ],
  "catch_all_signals": ["matches_confirmed_format"]
}
  • smtp_available: live mailbox (SMTP) checking was available on our side for this call. It says nothing about this particular address.
  • mailbox_probed: this address’s mail server was asked about it, now or in a recent cached answer. It is false for answers that need no probe: bad format, disposable, role address, no mail server, or a provider that never confirms mailboxes.
  • mailbox_checked: the original name for smtp_available, with the same value. It stays in every reply so existing integrations keep working; read mailbox_probed to know whether this address was checked.
statusWhat it meansPossible reasons
validThe mail server confirmed the mailbox exists.Mailbox exists
riskyThe address may work, but delivery isn't confirmed. Send with care or keep it out of cold campaigns.Accepts all addresses · Role-based address · Provider hides mailbox status · Server would not confirm · Mailbox check unavailable
invalidDon't send. It will bounce or it's a throwaway address.Invalid format · No mail server · Typo domain - did you mean gmail.com? · Disposable address · Mailbox not found

Decision fields for agents and automations

Every answered check from /api/v1/verify and every row from the MCP tools also carries four fields with a fixed shape and no null, so an AI agent, Zapier step or script can branch without parsing sentences. They are added next to the fields above; nothing existing changed. They are worked out from status and reason and are never more hopeful than status.

  • action: send, send_with_care, retry_later or do_not_send (the machine form of send_advice). Only status valid ever gives send, and invalid always gives do_not_send.
  • risk: low only for a confirmed mailbox, high for do_not_send or any unconfirmed address that looks invented, medium otherwise.
  • likely_invented: true when the address looks made up by an LLM or a guesser. It is never true for a confirmed mailbox, and otherwise only for a placeholder local part (test@, demo@, user@, john.doe@, firstname.lastname@; trailing digits ignored), a reserved or template domain (example.com, .test, yourdomain.com), a domain with no mail server at all (not a typo of a real one), or a sequential or repeated local part (1234@, abcd@, aaaa@). Role inboxes and real-looking names are never flagged by it.
  • reason_code: a stable code for reason (table below). Reason sentences may be reworded; codes do not change. New codes may be added: treat an unknown code like other and go by action. unknown means the result had no status.
statusactionrisk
validsendlow
risky: catch-all, role inbox, provider hides status, othersend_with_caremedium (high if likely_invented)
risky: server refused, timed out, blocked, DNS failed, daily check limitretry_latermedium (high if likely_invented)
invaliddo_not_sendhigh
reasonreason_code
Mailbox existsmailbox_exists
Mailbox not foundmailbox_not_found
Domain's mail server doesn't accept mail for itdomain_not_hosted
No mail serverno_mx
Domain does not accept mailno_mx
Disposable addressdisposable
Invalid formatsyntax
Emptyempty
DNS lookup failed; try again laterdns_error
Role-based addressrole
Provider hides mailbox statusprovider_hidden
Accepts all addressescatch_all
Provider is blocking our checks; not probedprovider_blocked
Server refused our checkserver_refused
Server would not confirmserver_unconfirmed
Mailbox check unavailableserver_unreachable
Domain valid, mailbox unverifiedmailbox_not_checked
No mail server - did you mean ...?no_mx
Typo domain ...typo_domain
Unexpected response (...)unexpected_response
Daily mailbox-check limit reached ...check_limit_reached
Any other reasonother
{
  "email": "[email protected]",
  "status": "invalid",
  "reason": "No mail server",
  "send_advice": "do_not_send",
  "action": "do_not_send",
  "risk": "high",
  "likely_invented": true,
  "reason_code": "no_mx"
}

Deliverability score

Every answered check from /api/v1/verify, every row from the MCP tools and every row in the results table also carries score, a number from 0 to 100, and score_basis. Both are new and optional to read; nothing existing changed. The score is a summary of the fields above (status, reason_code, action, likely_invented, catch_all_confidence, role and disposable), not extra evidence, and it opens no extra mail connection.

  • score_basis is confirmed only when the mail server confirmed the mailbox (status valid, score 90 to 100). Every other score is predicted: a ranking of unconfirmed addresses, not a promise.
  • Catch-all addresses never score above 70, ranked by catch_all_confidence.
  • When we could not check the mailbox (the server did not answer, refused or blocked us, DNS failed, the daily check limit, or a provider that hides mailboxes) score and score_basis are null. We never fill in a number we did not earn. Retry those later.
  • invalid addresses score 0 to 10.
Whenscorescore_basis
valid: the mail server confirmed the mailbox100confirmed
valid, but a role inbox (info@, sales@...)95confirmed
valid, but a disposable domain90confirmed
catch-all, higher likelihood70predicted
catch-all, medium likelihood50predicted
catch-all, no likelihood label40predicted
catch-all, lower likelihood25predicted
risky: role inbox, not probed45predicted
risky: any other unconfirmed reason40predicted
risky: disposable domain or typo domain10predicted
any unconfirmed address that looks invented (likely_invented)at most 10predicted
invalid: unexpected reason10predicted
invalid: mailbox not found, domain not hosted, typo, disposable5predicted
invalid: bad format, empty, no mail server, or looks invented0predicted
could not check: server did not answer, refused, blocked, DNS failed, daily limit, provider hides mailboxes, no statusnullnull

The same table is applied everywhere, so a score in the dashboard matches the API. Branch on action for send decisions; use score to sort within a group.

What the server said

Every answer from /api/v1/verify and the MCP tools also carries three fields, new and optional to read. A list download has the same three as its last columns, after every column it had before, so existing imports keep working.

{
  "status": "invalid",
  "reason": "Mailbox not found",
  "smtp_reply": "550 5.1.1 The email account that you tried to reach does not exist.",
  "checked_at": "2026-10-30T14:05:12Z",
  "previous_status": "valid"
}
  • smtp_reply: the mail server's own reply that the verdict was read from: the code, the enhanced status and the start of its text. The address you checked and our server's IP and name are removed. null when no mail server was asked (bad syntax, no mail server, disposable or role address). For a catch-all it is the reply to a made-up address on the domain.
  • checked_at: when the answer was given, in UTC. An answer reused from an earlier check keeps its original time.
  • previous_status: the status before, when this check changed it (for example valid before and invalid now: the mailbox was removed). null when nothing changed. In a list download that already has a previous_status column, ours is sv_previous_status.

Mail provider and gateway

Every answer from /api/v1/verify and the MCP tools also carries provider and gateway, read from the domain's MX record (and its SPF record behind a gateway). No mail server is contacted for them. A list download has the same two as its last columns, after every column it had before, so existing imports keep working.

{
  "status": "risky",
  "reason": "Provider hides mailbox status",
  "provider": "Microsoft 365",
  "gateway": "Mimecast"
}
  • provider: Google Workspace, Gmail, Microsoft 365, Outlook.com/Hotmail, Yahoo, Zoho, iCloud, Proton, GMX/Web.de, Yandex, Fastmail, a hosting company such as GoDaddy, Other/self-hosted, Unknown (behind gateway) when a gateway hides it, or No mail server.
  • gateway: a security filter in front of the mailbox: Mimecast, Proofpoint, Barracuda, Cisco IronPort, Sophos, Trend Micro, Forcepoint, Hornetsecurity, Symantec and others, or none. Microsoft 365 filters its own mail, so its rows say none unless another gateway sits in front.
  • Both are null (an empty cell in a download) when the domain could not be looked up. In a list download that already has a provider or gateway column, ours are sv_provider and sv_gateway.

Use them to send each group from its own campaign. Microsoft 365 often hides whether a mailbox exists, and gateways can quarantine cold email without a bounce, so send those at a lower daily volume. The results page has a download split by provider: Google, Microsoft, Gateway-protected and Other.

Validate a phone number

GET or POST https://simpleverifier.com/api/v1/phone with phone and an optional country (two letters, default US) used for numbers written without +. With the phone add-on (or the older Email + Phone plan), valid numbers also get a live carrier lookup (5,000 a month; failed lookups don't count). Pass live=false to skip it.

curl "https://simpleverifier.com/api/v1/phone?phone=07911%20123456&country=GB" \
  -H "Authorization: Bearer $SIMPLEVERIFIER_KEY"
{
  "input": "07911 123456",
  "phone_status": "valid",
  "phone_e164": "+447911123456",
  "phone_country": "GG",
  "phone_type": "mobile",
  "phone_carrier": "JT Group Limited",
  "phone_line_type": "mobile",
  "live_lookup": true
}

phone_type comes from the numbering plan and is free on every plan. phone_carrier and phone_line_type appear only when live_lookup is true.

Examples

Prefer a client library? The Node.js and Python SDKs add typed errors, automatic retries on rate limits and a bulk helper. To give Claude, Cursor or another AI agent these checks as tools, connect the SimpleVerifier MCP server with the same key.

Node.js

const res = await fetch(
  "https://simpleverifier.com/api/v1/verify?email=" + encodeURIComponent(email),
  { headers: { Authorization: `Bearer ${process.env.SIMPLEVERIFIER_KEY}` } }
);
const { status, reason } = await res.json();
if (status === "invalid") throw new Error(`Please check your email: ${reason}`);

Python

import os, requests

r = requests.get(
    "https://simpleverifier.com/api/v1/verify",
    params={"email": email},
    headers={"Authorization": f"Bearer {os.environ['SIMPLEVERIFIER_KEY']}"},
    timeout=30,
)
result = r.json()  # {"status": "valid" | "risky" | "invalid", "reason": ...}

Block fake and disposable signups

Call this endpoint where an account is created, block action do_not_send, warn on role and catch-all addresses, and let timeouts through. Signup protection recipes for a Supabase Auth hook, a Clerk webhook, a Firebase blocking function, Next.js, Express, Cloudflare Workers, Zapier, n8n and Webflow or Framer forms.

Guides by language and framework

Complete, copy-ready integrations with a syntax check first, timeouts, and handling for valid, risky and invalid results.

Google Sheets and other integrations

To verify a column of emails without writing code, paste the SimpleVerifier Google Sheets script into your sheet. It calls this API with your key, adds a Verify selected emails menu that writes Status and Reason beside your list, and adds =SV_VERIFY() and =SV_PHONE_FORMAT() formulas. No Marketplace install. Instantly, Smartlead, the SDKs and the MCP server are listed on the integrations page.

Errors and limits

Errors return JSON with an error message. A mailbox check can take a few seconds when a mail server is slow, so set a client timeout of about 30 seconds.

Monthly calls by plan

Each request to /api/v1/verify, /api/v1/phone or an MCP tool call is one call. /api/v1/me and MCP handshakes are free. Months are counted in UTC. Every key allows 120 calls a minute.

PlanIncluded a monthAfter that (monthly billing)
Pro, $39/mo10,000$1.50 per 1,000
Business, $99/mo100,000$1.00 per 1,000
Agency, $249/mo300,000$1.00 per 1,000
400The email or phone parameter is missing.
401The API key is missing, wrong or revoked.
402The key's subscription isn't active, or a yearly plan has used this month's included calls. A quota 402 carries a Retry-After header with the seconds until the 1st (UTC).
429More than 120 calls a minute on one key. The Retry-After header (and retry_after_seconds in the body) says how many seconds to wait.
503The verification service is briefly unavailable. Retry with backoff.

Common questions

How much does the SimpleVerifier API cost?
API access comes with Pro ($39/month, 10,000 calls a month included, then $1.50 per 1,000), Business ($99, 100,000 included, then $1.00 per 1,000) and Agency ($249, 300,000 included, then $1.00 per 1,000). The Starter CSV plan does not include it. Calls past the included ones are added to the next monthly invoice; on yearly billing the included calls are the monthly limit and further calls return 402 until the 1st (UTC). The $20/mo phone add-on adds 5,000 live carrier lookups a month. Pro subscriptions started before 23 October 2026 keep API access with no monthly quota.
Is there a rate limit?
Each key can make 120 calls a minute, counted over a sliding 60-second window. Past that the API answers 429 with a Retry-After header giving the whole seconds until the key can call again (the same number is in the JSON body as retry_after_seconds); wait that long, then retry. For lists of thousands of addresses, upload a CSV in the dashboard instead: it runs the same checks in bulk and returns valid, risky and invalid files.
Can I call the API from the browser?
No. Keep keys on your server. Anyone who can read a key from your page can use your plan. Call the API from your backend and pass the result to your form.
Does the phone endpoint tell me if a number is in service?
No. It confirms the number fits its country's numbering plan, normalises it to E.164 and, with the phone add-on, returns the carrier and real line type (mobile, landline or VoIP). No lookup can confirm someone is using a number today.

Clean your next list before it bounces

100 free email checks when you sign in with Google, no card. Then monthly plans from $9, or one flat price with fair use, never cut off.

100 free checks, no card Cancel anytime Secure payment by Stripe