How to Verify Email Addresses via API: A Developer's Guide
Verifying email addresses at the point of capture — rather than after bounces accumulate — is the most effective approach to list hygiene. An email verification API lets you check each address as soon as a user enters it, before it ever hits your database.
This guide walks through how to integrate an email verification API into common scenarios, with code examples.
When to use the API vs. bulk verification
| Scenario | Use | |---|---| | Signup form / registration | API (real-time, one address at a time) | | Existing list of addresses | Bulk verification (upload CSV) | | CRM enrichment (automated) | API (per-record enrichment job) | | Pre-send campaign cleanup | Bulk verification | | Form embedded in a mobile app | API |
For real-time scenarios, the API is the right tool. Bulk verification handles historical lists.
Getting an API key
Create an account at stopbouncing.com/register, then navigate to API Keys in your dashboard. Generate a key and store it securely — treat it like a password and never expose it in client-side code.
Basic API call
The verification endpoint accepts a single email address and returns its status.
Endpoint:
GET https://api.stopbouncing.com/v1/verify?email={email}
Required header:
Authorization: Bearer YOUR_API_KEY
Response:
{
"email": "user@example.com",
"status": "valid",
"sub_status": null,
"domain": "example.com",
"mx_found": true,
"smtp_check": true,
"is_disposable": false,
"is_role_based": false,
"is_catch_all": false
}
The status field returns one of three values:
valid— the address is deliverableinvalid— the address does not exist or cannot receive emailunverifiable— the server could not confirm (catch-all, timeout, SPAM block)
JavaScript example
async function verifyEmail(email) {
const response = await fetch(
`https://api.stopbouncing.com/v1/verify?email=${encodeURIComponent(email)}`,
{
headers: {
Authorization: `Bearer ${process.env.STOPBOUNCING_API_KEY}`,
},
}
);
if (!response.ok) {
throw new Error(`Verification failed: ${response.status}`);
}
return response.json();
}
// Usage
const result = await verifyEmail("user@example.com");
if (result.status === "invalid") {
throw new Error("This email address does not exist. Please check and try again.");
}
if (result.is_disposable) {
throw new Error("Please use a permanent email address. Temporary addresses are not accepted.");
}
Python example
import os
import requests
from urllib.parse import quote
def verify_email(email: str) -> dict:
api_key = os.environ["STOPBOUNCING_API_KEY"]
url = f"https://api.stopbouncing.com/v1/verify?email={quote(email)}"
response = requests.get(url, headers={"Authorization": f"Bearer {api_key}"}, timeout=10)
response.raise_for_status()
return response.json()
# Usage
result = verify_email("user@example.com")
if result["status"] == "invalid":
raise ValueError("Email address does not exist.")
if result["is_disposable"]:
raise ValueError("Temporary email addresses are not accepted.")
Integrating into a signup form
The most common use case. Best practice: verify server-side (never client-side), after the user submits the form.
// Next.js API route example
import type { NextRequest } from "next/server";
export async function POST(req: NextRequest) {
const { email, name } = await req.json();
// Basic format check first (fast, free)
if (!email?.includes("@")) {
return Response.json({ error: "Invalid email format" }, { status: 400 });
}
// SMTP verification (costs one credit)
const verification = await fetch(
`https://api.stopbouncing.com/v1/verify?email=${encodeURIComponent(email)}`,
{
headers: { Authorization: `Bearer ${process.env.STOPBOUNCING_API_KEY}` },
}
).then((r) => r.json());
if (verification.status === "invalid") {
return Response.json(
{ error: "That email address doesn't appear to exist. Please double-check it." },
{ status: 422 }
);
}
if (verification.is_disposable) {
return Response.json(
{ error: "Please use a permanent email address — temporary addresses are not accepted." },
{ status: 422 }
);
}
// Proceed with account creation
// ...
}
Handling unverifiable addresses
Some addresses return unverifiable — typically because the domain uses a catch-all configuration or the mail server timed out. These addresses might be valid or invalid; there's no way to know from outside.
The right approach depends on your context:
- High-value conversions (enterprise, long sales cycles): accept
unverifiableand monitor engagement later - Marketing list hygiene: treat
unverifiablecautiously — consider sending one email and suppressing on first bounce - Free trial signups (fraud-sensitive): reject
unverifiableto reduce risk of throwaway addresses
if (verification.status === "unverifiable") {
// Option 1: Accept but flag for monitoring
await createUser({ email, verificationStatus: "unconfirmed" });
// Option 2: Reject for high-risk flows
return Response.json(
{ error: "We couldn't verify this email address. Please try a different one." },
{ status: 422 }
);
}
Rate limiting and credits
Each API call consumes one credit. The API is rate-limited per your plan — see your dashboard for current limits. For large-scale enrichment jobs, bulk verification is more efficient than calling the API in a loop.
Error handling
Always handle API errors gracefully — don't block user registration if the verification API is temporarily unavailable.
async function safeVerifyEmail(email: string): Promise<"valid" | "invalid" | "unknown"> {
try {
const result = await verifyEmail(email);
if (result.status === "invalid" || result.is_disposable) return "invalid";
return "valid";
} catch {
// API unavailable — fail open to avoid blocking legitimate signups
return "unknown";
}
}
Summary
Integrating email verification at the point of capture is straightforward: one API call per submission, check the status field, handle the three cases (valid, invalid, unverifiable). For full API documentation, see the API reference. Start with 100 free credits — no credit card required.
Ready to clean your email list?
Verify thousands of addresses in minutes. No subscription — pay only for what you use.