Email Verification API Documentation
Complete reference for the MailValid Email Verification API. Verify email addresses in real-time with syntax, domain, MX, and SMTP checks.
Building with an AI coding agent?
Copy a single self-contained API guide and paste it into Cursor, ChatGPT, Claude, or any other agent.
Fast & Reliable
Average response time under 500ms
95%+ Accuracy
Provider-optimized verification
Bulk Support
Up to 10,000 emails per request
https://mailvalid.io/api/v1
Authentication
All API requests require authentication using an API key. Include your
API key in the
X-API-Key
header.
Get your API key
Create a free account at mailvalid.io/register and generate an API key from your dashboard.
Step-by-Step Setup
-
1
Create an account
Sign up at mailvalid.io/register — 100 free credits included.
-
2
Generate an API key
Go to Dashboard → API Keys and click "Create New Key".
-
3
Add the header to your requests
Include
X-API-Key: mv_live_your_key_herein all API requests.
curl -X POST https://mailvalid.io/api/v1/verify/single \
-H "X-API-Key: mv_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com"}'
Quickstart
Get started verifying emails in under 2 minutes. Choose your preferred language:
# Verify a single email
curl -X POST https://mailvalid.io/api/v1/verify/single \
-H "X-API-Key: mv_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"email": "john@company.com"}'
import requests
API_KEY = "mv_live_your_key_here"
response = requests.post(
"https://mailvalid.io/api/v1/verify/single",
headers={"X-API-Key": API_KEY},
json={"email": "john@company.com"}
)
result = response.json()
print(f"Status: {result['result']['status']}") # valid, invalid, catch_all, unknown, do_not_mail
print(f"Valid: {result['result']['is_valid']}")
print(f"Confidence: {result['result']['confidence_score']}")
print(f"Reason: {result['result']['status_reason']}")
const API_KEY = "mv_live_your_key_here";
const response = await fetch(
"https://mailvalid.io/api/v1/verify/single",
{
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({ email: "john@company.com" })
}
);
const data = await response.json();
console.log(`Status: ${data.result.status}`); // valid, invalid, catch_all, unknown, do_not_mail
console.log(`Valid: ${data.result.is_valid}`);
console.log(`Confidence: ${data.result.confidence_score}`);
<?php
$apiKey = "mv_live_your_key_here";
$ch = curl_init("https://mailvalid.io/api/v1/verify/single");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . $apiKey,
"Content-Type: application/json"
],
CURLOPT_POSTFIELDS => json_encode(["email" => "john@company.com"])
]);
$result = json_decode(curl_exec($ch), true);
echo "Status: " . $result['result']['status']; // valid, invalid, catch_all, unknown, do_not_mail
echo "Valid: " . ($result['result']['is_valid'] ? 'Yes' : 'No');
echo "Confidence: " . $result['result']['confidence_score'];
?>
Single Email Verification
Verify a single email address in real-time. Returns comprehensive verification results including syntax, domain, MX records, and SMTP checks.
/api/v1/verify/single
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
email |
string | Required | The email address to verify |
Example Request
curl -X POST https://mailvalid.io/api/v1/verify/single \
-H "X-API-Key: mv_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"email": "john@company.com"}'
Example Response
{
"success": true,
"credits_used": 1,
"result": {
"email": "john@company.com",
"status": "valid",
"is_valid": true,
"syntax_valid": true,
"domain": "company.com",
"domain_valid": true,
"has_mx": true,
"mx_records": [
{"priority": 10, "host": "mx1.company.com"}
],
"smtp_checked": true,
"smtp_response_code": 250,
"is_disposable": false,
"is_role_based": false,
"is_catch_all": false,
"is_free_provider": false,
"confidence_score": 95,
"status_reason": "mailbox_confirmed",
"provider": "corporate",
"verification_time_ms": 234,
"cached": false
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
status |
string | Verification status: valid, invalid, catch_all, unknown, do_not_mail |
is_valid |
boolean | Whether the email is safe to send to |
confidence_score |
integer | Confidence in the result (0–100) |
status_reason |
string | Machine-readable sub-status reason (see Status Values) |
provider |
string | Detected email provider (google, microsoft, yahoo, apple, corporate, etc.) |
is_disposable |
boolean | Whether the domain is a disposable/temporary email provider |
is_role_based |
boolean | Whether the address is role-based (info@, support@, admin@) |
is_catch_all |
boolean | Whether the domain accepts all email addresses |
is_free_provider |
boolean | Whether the domain is a free email provider (gmail.com, yahoo.com, etc.) |
cached |
boolean | Whether MailValid returned a recent server-side result. cached: true uses 0 credits. |
Fair billing
credits_used is the final charge. Fresh valid, invalid,
catch_all, and do_not_mail results use 1 credit.
unknown results and cached: true recent repeats use 0 credits.
Bulk Email Verification
Verify up to 10,000 emails in a single request. Jobs are processed asynchronously. You can poll for status or receive a webhook notification when complete.
/api/v1/verify/bulk
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
emails |
array | Required | Array of email addresses (max 10,000) |
webhook_url |
string | Optional | URL to receive completion webhook |
Submit Bulk Job
curl -X POST https://mailvalid.io/api/v1/verify/bulk \
-H "X-API-Key: mv_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"emails": [
"john@company.com",
"jane@example.org",
"info@business.net"
],
"webhook_url": "https://yourapp.com/webhooks/mailvalid"
}'
Response (Job Created)
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"total_emails": 3,
"credits_reserved": 3,
"webhook_url": "https://yourapp.com/webhooks/mailvalid"
}
credits_reserved is the maximum possible charge and is reserved at submit time.
Unused credits are released after processing. The final charge is returned as
credits_used when the job completes.
Check Job Status
curl https://mailvalid.io/api/v1/verify/bulk/550e8400-e29b-41d4-a716-446655440000 \
-H "X-API-Key: mv_live_your_key_here"
Completed Job Response
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"total_emails": 3,
"processed_emails": 3,
"valid_count": 2,
"invalid_count": 1,
"credits_used": 3,
"results": [
{
"email": "john@company.com",
"status": "valid",
"is_valid": true,
// ... full result for each email
}
]
}
Webhooks
Get notified when your bulk verification jobs complete instead of
polling. Provide a
webhook_url
when submitting a bulk job.
Verify webhook signatures
Every webhook includes an
X-Mailvalid-Signature
header: an HMAC-SHA256 of the raw request body,
signed with your account's webhook secret. Find your secret in
your dashboard (it's also returned as
webhook_secret
from GET /api/auth/stats).
Recompute the HMAC over the exact bytes you receive and compare —
do not re-serialize the JSON first, or it won't match.
Webhook Payload
{
"event": "job.completed",
"timestamp": "2026-01-30T10:30:00Z",
"data": {
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"total_emails": 100,
"processed_emails": 100,
"valid_count": 85,
"invalid_count": 15,
"credits_used": 100,
"started_at": "2026-01-30T10:28:00Z",
"completed_at": "2026-01-30T10:30:00Z",
"error_message": null,
"results_summary": {
"total": 100,
"results_url": "https://mailvalid.io/api/v1/verify/bulk/550e8400-e29b-41d4-a716-446655440000"
}
}
}
Webhook Headers
| Header | Description |
|---|---|
X-Mailvalid-Signature |
HMAC-SHA256 signature of the payload |
X-MailValid-Event |
Event type: job.completed, job.failed, or job.cancelled |
Content-Type |
application/json |
Verifying the signature
Compute an HMAC-SHA256 of the raw request body using your
webhook_secret
and compare it to the X-Mailvalid-Signature
header. Sign the bytes exactly as received — don't parse and re-serialize the JSON.
import hmac, hashlib
from flask import request, abort
WEBHOOK_SECRET = "your_webhook_secret" # from GET /api/auth/stats
def handle_webhook():
raw = request.get_data() # raw bytes — do NOT use request.json
expected = hmac.new(WEBHOOK_SECRET.encode(), raw, hashlib.sha256).hexdigest()
sig = request.headers.get("X-Mailvalid-Signature", "")
if not hmac.compare_digest(expected, sig):
abort(401) # signature mismatch — reject
event = request.json
# ... trust event and process ...
return "", 200
Bounce Webhook
Feed delivery and bounce data back to MailValid to improve verification accuracy over time. This is especially valuable for providers that block SMTP verification (Microsoft, Apple) where we can't confirm mailboxes directly.
Improve accuracy automatically
The more bounce data you send, the more accurate future verifications become. Historical data is checked before SMTP verification, so known addresses return results instantly.
/api/v1/webhooks/bounce
API Key Required
Submit Bounce/Delivery Events
Send up to 1,000 bounce or delivery events per request. Hard bounces automatically invalidate cached verification results.
Event Types
| Event Type | Effect | Description |
|---|---|---|
delivered |
Marks valid | Email was successfully delivered to inbox |
hard_bounce |
Marks invalid | Permanent bounce — mailbox doesn't exist (550) |
soft_bounce |
Recorded | Temporary bounce — mailbox full, server down (452) |
complaint |
Marks do_not_mail | Recipient marked email as spam |
unsubscribe |
Recorded | Recipient unsubscribed from emails |
Request Body
{
"events": [
{
"email": "user@example.com",
"event_type": "hard_bounce",
"bounce_code": "550",
"bounce_message": "User unknown",
"timestamp": "2026-03-14T10:30:00Z"
},
{
"email": "valid@example.com",
"event_type": "delivered"
}
]
}
Example
curl -X POST https://mailvalid.io/api/v1/webhooks/bounce \
-H "X-API-Key: mv_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"events": [
{"email": "bounced@example.com", "event_type": "hard_bounce", "bounce_code": "550"},
{"email": "good@example.com", "event_type": "delivered"}
]
}'
Response
{
"success": true,
"processed": 2,
"errors": 0,
"message": "Processed 2/2 events"
}
Job Management
List, download results from, and cancel your bulk verification jobs.
/api/v1/verify/bulk
List Your Jobs
Get a paginated list of all your bulk verification jobs.
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
int | 1 | Page number |
page_size |
int | 20 | Items per page (max 100) |
status |
string | all | Filter by status |
curl "https://mailvalid.io/api/v1/verify/bulk?page=1&status=completed" \
-H "X-API-Key: mv_live_your_key_here"
/api/v1/verify/bulk/{job_id}/download
Download Results
Download verification results as JSON or CSV.
# Download as CSV
curl "https://mailvalid.io/api/v1/verify/bulk/{job_id}/download?format=csv" \
-H "X-API-Key: mv_live_your_key_here" \
-o results.csv
# Download as JSON
curl "https://mailvalid.io/api/v1/verify/bulk/{job_id}/download?format=json" \
-H "X-API-Key: mv_live_your_key_here" \
-o results.json
/api/v1/verify/bulk/{job_id}
Cancel a Job
Cancel a pending or processing job. Completed billable rows use credits; unused reserved credits are released.
curl -X DELETE https://mailvalid.io/api/v1/verify/bulk/{job_id} \
-H "X-API-Key: mv_live_your_key_here"
MCP Server (Model Context Protocol)
Connect AI agents and assistants — Claude, Cursor, and any MCP-compatible client — directly to MailValid. The MCP server is a hosted remote endpoint over the Streamable HTTP transport. Its tools run through the same verification engine and credit system as the REST API, so billing is identical.
| Endpoint | https://mailvalid.io/mcp/ |
| Transport | Streamable HTTP (use the http transport in your client) |
| Authentication | Your API key in the X-API-Key header (or Authorization: Bearer mv_live_...) |
| Billing | Identical to the REST API — single verification costs 1 credit, bulk reserves credits, reads are free |
Two tools — verify_email_demo and
get_api_discovery — work without a key, so an agent can
try MailValid before you connect an account. Every other tool
requires the X-API-Key header and bills your account.
Follow the instructions for your client below.
Claude Code
Run this command in your terminal:
claude mcp add --transport http mailvalid https://mailvalid.io/mcp/ \
--header "X-API-Key: mv_live_your_key_here"
Then run /mcp in Claude Code to confirm the connection
and manage installed servers, and /context to see token
usage per server.
Cursor
- Open Cursor Settings → MCP → Add new global MCP server
- Paste the configuration below, then save and restart Cursor
{
"mcpServers": {
"mailvalid": {
"url": "https://mailvalid.io/mcp/",
"headers": { "X-API-Key": "mv_live_your_key_here" }
}
}
}
The verify_email tool and others are available
immediately — no separate OAuth step, since auth is your API key.
VS Code (GitHub Copilot)
- Create a
.vscode/mcp.jsonfile in your workspace with the config below - Open the Command Palette (
Cmd+Shift+P/Ctrl+Shift+P) and run MCP: List Servers - Start the MailValid server — tools are available immediately
{
"servers": {
"mailvalid": {
"type": "http",
"url": "https://mailvalid.io/mcp/",
"headers": { "X-API-Key": "mv_live_your_key_here" }
}
}
}
Claude Desktop
Claude Desktop only supports local (stdio) MCP servers, so use the
@mailvalid/mcp launcher instead of connecting directly
over HTTP:
- Open Settings → Developer → Edit Config
- Add the MailValid server below to your config
- Restart Claude Desktop
{
"mcpServers": {
"mailvalid": {
"command": "npx",
"args": ["-y", "@mailvalid/mcp"],
"env": { "MAILVALID_API_KEY": "mv_live_your_key_here" }
}
}
}
The launcher reads your key from MAILVALID_API_KEY and
bridges to the hosted server — there's no URL or header to manage by
hand.
Windsurf
- Open Windsurf Settings (
Cmd+,on Mac) and search for MCP - Select View raw config to open
mcp_config.json - Add the MailValid server below, then save and restart Windsurf
{
"mcpServers": {
"mailvalid": {
"serverUrl": "https://mailvalid.io/mcp/",
"headers": { "X-API-Key": "mv_live_your_key_here" }
}
}
}
Other MCP clients
If your client isn't listed above, use one of these transports:
| Transport | URL | Notes |
|---|---|---|
| Streamable HTTP (recommended) | https://mailvalid.io/mcp/ |
Include the trailing slash to avoid a redirect |
@mailvalid/mcp launcher |
via npx -y @mailvalid/mcp |
For stdio-only clients (Claude Desktop and similar) |
{
"mcpServers": {
"mailvalid": {
"url": "https://mailvalid.io/mcp/",
"headers": { "X-API-Key": "mv_live_your_key_here" }
}
}
}
{
"mcpServers": {
"mailvalid": {
"command": "npx",
"args": ["-y", "@mailvalid/mcp"],
"env": { "MAILVALID_API_KEY": "mv_live_your_key_here" }
}
}
}
Field names vary slightly between clients (some use
servers instead of mcpServers) — check
your client's MCP documentation for the expected key names.
Available tools
Two tools work with no authentication at all, so an agent can try
MailValid before you connect an account:
verify_email_demo (syntax, disposable, role, and
free-provider checks — no SMTP, rate-limited per IP) and
get_api_discovery (machine-readable discovery URLs).
The rest require your API key:
| Tool | Auth | Cost | Description |
|---|---|---|---|
verify_email |
API key | 1 credit* | Verify one address — syntax, DNS/MX, SMTP mailbox, disposable/role/catch-all, confidence. Args: email. |
submit_bulk_verification |
API key | Reserves N | Queue a list for asynchronous verification. Args: emails (array), optional webhook_url. |
get_bulk_job |
API key | Free | Status, progress, and (when complete) results of a bulk job. Args: job_id. |
list_bulk_jobs |
API key | Free | List your recent bulk jobs (paginated). Args: page, page_size, status. |
cancel_bulk_job |
API key | Free | Cancel a pending/processing job and release reserved credits. Args: job_id. |
get_credit_balance |
API key | Free | Your current credit balance and lifetime usage. |
verify_email_demo |
None | Free | Demo validation — syntax, disposable, role, and free-provider checks only (no SMTP). Rate-limited per IP. Args: email. |
get_api_discovery |
None | Free | MailValid machine-readable discovery URLs (OpenAPI, docs, well-known metadata). |
* verify_email is free for unknown results and
recent cached repeats — the same fair-billing rule as the REST API.
Troubleshooting
My MCP client doesn't support remote HTTP servers
Use the @mailvalid/mcp launcher instead — it works over stdio in any client, including Claude Desktop. See the config under Other MCP clients.
I'm getting a 307 redirect or connection errors
Make sure your endpoint URL has a trailing slash: https://mailvalid.io/mcp/, not https://mailvalid.io/mcp.
401 / authentication errors
Confirm your API key is passed as X-API-Key (or Authorization: Bearer mv_live_...) and that it hasn't been revoked in your dashboard. Try the two no-auth tools (verify_email_demo, get_api_discovery) first to confirm the connection itself works before debugging auth.
My MCP client isn't listed here
Use the generic Streamable HTTP configuration under Other MCP clients. If your client needs a different config shape (some use servers instead of mcpServers), check that client's own MCP documentation for the expected key names.
FAQ
Does billing work the same as the REST API?
Yes — the MCP tools call the same verification engine and draw from the same credit balance as the REST API and web dashboard. A verify_email call costs exactly what a POST /api/v1/verify/single call costs.
What's the difference between the launcher and connecting directly?
The @mailvalid/mcp launcher runs locally over stdio and is the only option for clients that don't support remote HTTP servers (like Claude Desktop). If your client supports Streamable HTTP directly (Claude Code, Cursor, VS Code), connecting straight to https://mailvalid.io/mcp/ skips the extra process and is the simpler setup.
Is there a discovery endpoint for agents that want to find this automatically?
Yes — /.well-known/mcp.json and /.well-known/mcp/server-card.json, or call the no-auth get_api_discovery tool.
See the REST endpoints above for the underlying API these tools call, or the full agent integration guide for a self-contained reference covering both REST and MCP.
HTTP Response Codes
| Code | Status | Description |
|---|---|---|
| 200 | OK | Request successful |
| 201 | Created | Resource created (bulk job submitted) |
| 400 | Bad Request | Invalid request format or parameters |
| 401 | Unauthorized | Missing or invalid API key |
| 402 | Payment Required | Insufficient credits |
| 404 | Not Found | Resource not found |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Server Error | Internal server error |
Email Status Values
Industry-standard status codes (same as ZeroBounce, NeverBounce). The
status
field indicates the verification result. The
status_reason
field gives the specific machine-readable reason.
Status Codes
| Status | is_valid | Description | Action |
|---|---|---|---|
valid |
true | SMTP confirmed mailbox exists. Safe to send. (<2% bounce rate) | Safe to send |
invalid |
false | Address will bounce — bad syntax, no domain, or mailbox not found | Do not send |
catch_all |
false | Domain accepts all addresses. Cannot confirm individual mailbox | Send with caution |
unknown |
false | Could not determine validity (provider blocked SMTP, timeout). Free — 0 credits charged | Retry later or use bounce webhook |
do_not_mail |
false | Address may work but is risky — disposable, role-based (info@, support@), or spam complaint | Do not send marketing |
Sub-Status Reasons
The status_reason
field gives the specific reason behind the status:
| status_reason | Status | Meaning |
|---|---|---|
mailbox_confirmed |
valid |
SMTP server confirmed the mailbox exists (250 response) |
historical_delivery_confirmed |
valid |
Email was previously delivered successfully (from bounce webhook data) |
mailbox_not_found |
invalid |
SMTP server rejected the recipient (550 response) |
failed_syntax_check |
invalid |
Email address format is invalid |
no_dns_entries |
invalid |
Domain does not exist (no DNS records) |
no_mx_records |
invalid |
Domain exists but has no MX records configured |
historical_hard_bounce |
invalid |
Email previously hard-bounced (from bounce webhook data) |
accept_all |
catch_all |
Domain accepts all addresses — individual mailbox cannot be confirmed |
failed_smtp_connection |
unknown |
Provider blocks SMTP verification (Microsoft, Apple) |
antispam_system |
unknown |
Anti-spam system blocked our verification attempt |
greylisted |
unknown |
Server uses greylisting — temporarily rejected |
mail_server_temporary_error |
unknown |
Mail server returned a temporary error |
disposable |
do_not_mail |
Disposable/temporary email domain (e.g. mailinator.com) |
role_based |
do_not_mail |
Role-based address (info@, support@, admin@) |
historical_complaint |
do_not_mail |
Recipient previously filed a spam complaint (from bounce webhook data) |
Confidence Score
Every result includes a
confidence_score
(0–100) indicating how confident we are in the result:
| Range | Meaning |
|---|---|
| 90–100 | Very high confidence — SMTP confirmed, historical hard bounce, or syntax/domain failure |
| 70–89 | High confidence — historical delivery data, trusted provider response |
| 40–69 | Medium confidence — catch-all domain, DNS-only verification |
| 0–39 | Low confidence — provider blocked verification, timeout |
Rate Limits
API requests are rate limited to ensure fair usage and platform stability.
| Plan | Verification requests | Read requests | Max emails per bulk job |
|---|---|---|---|
| Free (default) | 10 / min | 60 / min | 100 |
| Starter | 50 / min | 200 / min | 2,000 |
| Growth | 100 / min | 400 / min | 5,000 |
| Scale | 200 / min | 800 / min | 10,000 |
Limits are per API key. Verification requests covers writes
(POST /verify/single, POST /verify/bulk);
read requests covers polling endpoints
(GET /verify/bulk/{id}, GET /credits), so status
polling never eats into your verification budget. The keyless demo endpoint
(POST /api/demo/verify) is limited to 30 requests per minute per IP.
When rate limited, the API returns 429 Too Many Requests.
Back off and retry after the time specified in the Retry-After header.
Need higher limits? Contact us at sachin@mailvalid.io for custom rate limits on higher volume plans.
Rate Limit Headers
All responses include rate limit information:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests allowed in the current window |
X-RateLimit-Remaining |
Remaining requests in the current window |
Retry-After |
Seconds until rate limit resets (when limited) |
Error Responses
When an error occurs, the API returns a consistent error format:
{
"detail": "Error message describing what went wrong"
}
Common Errors
| Error | Code | Solution |
|---|---|---|
| Invalid API key | 401 | Check your API key is correct and active |
| Insufficient credits | 402 | Purchase more credits from your dashboard |
| Rate limit exceeded | 429 | Wait for the Retry-After period |
| Invalid email format | 422 | Check the email address format |
| Bulk job too large | 400 | Maximum 10,000 emails per request |
Need Help?
Can't find what you're looking for? Get in touch with our support team.