This guide covers how to integrate BounceZero’s email verification API in Python - from a simple synchronous requests call to async batch verification with httpx, Flask/Django integration patterns, and error handling for production use.
import requests
API_KEY = "YOUR_BOUNCEZERO_API_KEY"
BASE_URL = "https://api.bouncezero.io/api/v1"
def verify_email(email: str) -> dict:
"""Verify a single email address and return the full result."""
response = requests.post(
f"{BASE_URL}/verify",
json={"email": email},
headers={"X-API-Key": API_KEY},
timeout=10
)
response.raise_for_status()
return response.json()
def is_safe_to_send(email: str) -> bool:
"""Return True only if the email is valid and not an unresolved catch-all."""
result = verify_email(email)
if result["classification"] != "valid":
return False # invalid, disposable, spamtrap, risky or unverifiable
meta = result.get("metadata") or {}
# Unresolved catch-all is reported valid: keep only high-confidence ones
if meta.get("catch_all_unresolved") and result.get("score", 0) < 70:
return False
return True
# Usage
if is_safe_to_send("[email protected]"):
print("Safe to send")
else:
print("Suppress this email")
The classification field is binary: valid or invalid. The detailed verdict (verified, likely_valid, catch_all, invalid, disposable, risky, unknown) is in metadata.internal_classification; metadata.catch_all_unresolved marks catch-all addresses reported as valid, and metadata.refunded marks addresses that could not be verified (credit auto-refunded). The score field (0-100) rates confidence - 70+ is safe to send for catch-all domains.
import asyncio
import httpx
API_KEY = "YOUR_BOUNCEZERO_API_KEY"
BASE_URL = "https://api.bouncezero.io/api/v1"
CONCURRENCY = 10 # max concurrent requests (respect rate limits)
async def verify_email_async(client: httpx.AsyncClient, email: str) -> dict:
response = await client.post(
f"{BASE_URL}/verify",
json={"email": email},
headers={"X-API-Key": API_KEY},
)
response.raise_for_status()
data = response.json()
return {"email": email, **data}
async def verify_batch(emails: list[str]) -> list[dict]:
sem = asyncio.Semaphore(CONCURRENCY)
async def bounded_verify(client, email):
async with sem:
try:
return await verify_email_async(client, email)
except Exception as e:
return {"email": email, "classification": "error", "error": str(e)}
async with httpx.AsyncClient(timeout=15) as client:
tasks = [bounded_verify(client, email) for email in emails]
return await asyncio.gather(*tasks)
# Usage
emails = ["[email protected]", "[email protected]", "[email protected]"]
results = asyncio.run(verify_batch(emails))
for r in results:
status = r.get("classification", "error")
print(f"{r['email']}: {status}")
The semaphore limits concurrency to 10 requests at a time. For lists over 500 emails, the bulk upload endpoint (section 3) is faster and more efficient.
import csv
import asyncio
import httpx
API_KEY = "YOUR_BOUNCEZERO_API_KEY"
async def verify_csv(input_path: str, output_path: str):
# Read emails from CSV
emails = []
with open(input_path, newline="") as f:
reader = csv.DictReader(f)
fieldnames = reader.fieldnames or []
rows = list(reader)
for row in rows:
emails.append(row.get("email", ""))
# Verify all emails
from verify_email_async import verify_batch # import from section 2
results = await verify_batch(emails)
result_map = {r["email"]: r for r in results}
# Write enriched CSV
out_fields = list(fieldnames) + ["bz_result", "bz_score", "bz_internal"]
with open(output_path, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=out_fields)
writer.writeheader()
for row in rows:
email = row.get("email", "")
res = result_map.get(email, {})
row["bz_result"] = res.get("classification", "")
row["bz_score"] = res.get("score", "")
row["bz_internal"] = (res.get("metadata") or {}).get("internal_classification", "")
writer.writerow(row)
print(f"Written {len(rows)} rows to {output_path}")
asyncio.run(verify_csv("leads.csv", "leads_verified.csv"))
This appends three columns to the existing CSV: bz_result, bz_score, and bz_internal. Filter rows where bz_result == "invalid" before loading to your sequencer.
from flask import Flask, request, jsonify
import requests, os
app = Flask(__name__)
API_KEY = os.environ["BOUNCEZERO_API_KEY"]
@app.route("/register", methods=["POST"])
def register():
email = request.json.get("email", "").strip()
# Verify email before creating account
try:
res = requests.post(
"https://api.bouncezero.io/api/v1/verify",
json={"email": email},
headers={"X-API-Key": API_KEY},
timeout=5
).json()
except Exception:
# Fail open: allow registration if API is unavailable
res = {}
internal = (res.get("metadata") or {}).get("internal_classification")
if internal == "disposable":
return jsonify({"error": "Disposable emails are not allowed."}), 422
if res.get("classification") == "invalid" and not (res.get("metadata") or {}).get("refunded"):
return jsonify({"error": "Invalid email address."}), 422
# metadata.refunded = could not be verified: fail open like a timeout
# Create user account...
return jsonify({"status": "ok"}), 201
Note the fail open pattern: if the API call fails (timeout, network error), registration proceeds. This prevents the verification API from becoming a hard dependency that breaks signup. Log failures for monitoring.
| Field | Type | Description |
|---|---|---|
| classification | string | Binary verdict: valid | invalid |
| score | int 0-100 | Confidence score - especially useful for catch-all decisions |
| is_deliverable | boolean | True when classification is valid |
| risk_level | string | low | medium | high |
| checks | object | syntax, domain_valid, not_disposable, not_catch_all, mailbox_verified |
| metadata.internal_classification | string | Detailed verdict: verified | likely_valid | catch_all | invalid | disposable | risky | unknown | spamtrap | error |
| metadata.refunded | boolean | True when the address could not be verified (credit auto-refunded) |
New to verification? Start with the complete email verification guide.
Use the BounceZero API with Python’s requests library: response = requests.post(‘https://api.bouncezero.io/api/v1/verify’, json={‘email’: email}, headers={‘X-API-Key’: ‘YOUR_KEY’}). The result contains ‘classification’ (valid or invalid), ‘score’, ‘is_deliverable’, ‘checks’, and ‘metadata.internal_classification’ with the detailed verdict (verified, catch_all, disposable, unknown and so on). For async code, use httpx.AsyncClient.
For lists under 500 emails, use asyncio + httpx with a semaphore (CONCURRENCY = 10) to make concurrent API calls. For 1,000+ emails, use the bulk upload API endpoint - upload CSV, poll for completion, download enriched results.
For synchronous verification: requests (pip install requests). For async: httpx (pip install httpx) and asyncio (standard library). For CSV handling: pandas or the built-in csv module. No other dependencies needed for BounceZero API integration.
100 free credits on every account. No credit card required. Full API docs available after signup.
Ayoub built BounceZero's 5-stage validation pipeline, its dedicated BGP-announced IP infrastructure, and the Patroni HA PostgreSQL cluster behind every verification. Previously built high-volume email delivery infrastructure. Trained at 1337 Benguerir (École 42 network, 2019). Open-source: bgp_analyzer.
Endpoints, code samples, and language guides
How APIs validate in real time - endpoints + code
net/http, concurrent worker pool, Gin middleware, fail-open signup validation - code examples
Net::HTTP, Faraday, Rails validator, Sidekiq worker, Devise hook - code examples
cURL, Guzzle, Laravel rule, WordPress registration hook - code examples
IHttpClientFactory, ASP.NET Core minimal API, IHostedService bulk queue - code examples
HttpClient, ExecutorService bulk pool, Spring Boot bean wiring, fail-open signup validation - code examples
Continue through related topics