Official Python client for the BounceZero email verification API
Project description
The official Python client for the BounceZero email verification API. A single file, zero dependencies (standard library only), and a small, predictable surface designed for production workloads — from a signup form that validates one address in real time to a nightly job that cleans millions.
from bouncezero import BounceZero
client = BounceZero("bz_live_...")
result = client.verify("someone@example.com")
print(result["classification"], result["score"]) # → verified 98
Why BounceZero
Every address is scored across 40+ signals through a five-stage pipeline — syntax, DNS/MX, SMTP mailbox probing, disposable & role detection, and a Bayesian + ML confidence model — so you catch invalid, risky, and low-value addresses before they cost you deliverability.
- ⚡ Real-time single verification in ~500 ms–2 s, or async bulk for large lists
- 🎯 Six clear verdicts (
verified/invalid/catch_all/disposable/risky/unknown) plus threat flags - 🔁 Idempotent bulk submits — a network retry can never double-charge you
- 🧪 Sandbox keys to build & test without spending credits
- 🔒 Signed webhooks with a one-line verifier
- 📦 Zero dependencies, typed errors, automatic retries with backoff
Installation
pip install bouncezero
Requires Python 3.8+. No transitive dependencies.
Authentication
Grab your API key from the dashboard and pass it to the client. Keep live keys server-side — never ship them in client code.
from bouncezero import BounceZero
client = BounceZero("bz_live_...")
Quick start
# 1 — Verify a single address
result = client.verify("jane@example.com")
if result["classification"] == "verified":
add_to_mailing_list(result["email"])
# 2 — Batch up to 100 synchronously
report = client.verify_batch(["a@example.com", "b@example.com"])
print(report["summary"]) # {'verified': 1, 'invalid': 1, ...}
# 3 — Async bulk for large lists (idempotency-safe)
job = client.verify_bulk(emails, idempotency_key="import-2026-07-11")
final = client.wait_for_bulk(job["job_id"])
csv = client.bulk_download(job["job_id"])
Verification result
verify() returns a dict with the full signal breakdown. The fields you'll use most:
| Field | Type | Description |
|---|---|---|
email |
str |
The address that was checked |
classification |
str |
One of the six verdicts below |
score |
int |
Confidence 0–100 |
is_deliverable |
bool | None |
True/False, or None when inconclusive |
risk_level |
str |
low · medium · high |
recommendation |
str |
Human-readable guidance |
Classifications
| Verdict | Meaning | Safe to send? |
|---|---|---|
verified |
Mailbox exists and accepts mail | ✅ |
invalid |
Mailbox does not exist — will hard bounce | ❌ |
catch_all |
Domain accepts everything; mailbox unconfirmable | ⚠️ |
disposable |
Temporary/throwaway provider | ⚠️ |
risky |
Concrete negative signals (full mailbox, poor reputation…) | ⚠️ |
unknown |
Mail server blocked/greylisted the probe — retry later | ⏳ |
Two rare threat verdicts — complainer and spamtrap — may also appear; remove those addresses immediately (details in threat_type).
API reference
| Method | Description |
|---|---|
verify(email, depth="standard") |
Verify one address. depth: standard · deep · ultra |
verify_batch(emails) |
Verify up to 100 addresses synchronously |
verify_bulk(emails, idempotency_key=None) |
Submit an async bulk job |
bulk_status(job_id) |
Poll job progress |
bulk_results(job_id, limit=1000, offset=0) |
Paginated results |
bulk_download(job_id) |
Download completed results as CSV bytes |
wait_for_bulk(job_id, poll_interval=5.0, timeout=3600.0) |
Block until a job finishes |
domain(domain) |
Domain-level intelligence (MX, provider, catch-all…) |
analyze_list(emails) |
Free list-quality analysis — no verification, no credits |
BounceZero.verify_webhook_signature(body, header, secret) |
Validate a webhook signature |
Bulk verification
Bulk jobs run asynchronously. Submit, then either poll or wait:
job = client.verify_bulk(emails, idempotency_key="crm-sync-42")
# Option A — block until done
final = client.wait_for_bulk(job["job_id"], poll_interval=10)
# Option B — poll yourself
status = client.bulk_status(job["job_id"])
print(status["completed"], "/", status["total"])
# Fetch results (paginated) or download the full CSV
page = client.bulk_results(job["job_id"], limit=1000, offset=0)
csv = client.bulk_download(job["job_id"])
Idempotency — pass an idempotency_key (any unique string). Replaying the same key returns the original job instead of creating and charging for a duplicate, so retries after a timeout are always safe.
Error handling
Every failure raises a subclass of BounceZeroError, each carrying .status_code and .payload:
from bouncezero import (
BounceZero, AuthenticationError, InsufficientCreditsError,
RateLimitError, NotFoundError, BounceZeroError,
)
try:
result = client.verify("someone@example.com")
except InsufficientCreditsError:
top_up_credits()
except RateLimitError as e:
time.sleep(e.retry_after or 60)
except AuthenticationError:
alert("Check your API key")
except BounceZeroError as e:
log.error("BounceZero %s: %s", e.status_code, e)
| Exception | HTTP | When |
|---|---|---|
AuthenticationError |
401 / 403 | Missing, invalid, disabled, or IP-restricted key |
InsufficientCreditsError |
402 | Not enough credits |
NotFoundError |
404 | Unknown job or resource |
RateLimitError |
429 | Plan rate limit exceeded (see .retry_after) |
BounceZeroError |
other | Any other API/transport error |
Requests automatically retry on 429 and 5xx with exponential backoff (honoring Retry-After); tune with max_retries.
Rate limits
Limits are per API key, per minute, by plan. Responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset.
| Plan | Requests / min |
|---|---|
| Free | 30 |
| Starter · Growth | 100 |
| Professional · Business · Scale | 300 |
| Ultimate · Enterprise | 1000 |
Sandbox / testing
Create a sandbox key (bz_test_ prefix) in the dashboard. It returns deterministic mock results, never spends credits, and never performs a real probe — the local part of the address picks the verdict:
sandbox = BounceZero("bz_test_...")
sandbox.verify("verified@example.com")["classification"] # → "verified"
sandbox.verify("risky@example.com")["classification"] # → "risky"
Supported on verify(). See the sandbox docs.
Webhooks
BounceZero signs every webhook with X-BounceZero-Signature (HMAC-SHA256). Always verify against the raw request body before parsing:
from bouncezero import BounceZero
@app.post("/webhooks/bouncezero")
def handle(request):
if not BounceZero.verify_webhook_signature(
request.raw_body,
request.headers.get("X-BounceZero-Signature", ""),
signing_secret, # from Dashboard → Webhooks
):
return 400
event = request.json()
...
Configuration
client = BounceZero(
api_key="bz_live_...",
base_url="https://app.bouncezero.io", # override for a dedicated region
timeout=60, # seconds
max_retries=2, # retries on 429 / 5xx
)
Support
- 📖 Documentation — https://docs.bouncezero.io
- 📊 API status — https://status.bouncezero.io
- ✉️ support@bouncezero.io
License
MIT © BounceZero Ltd
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bouncezero-0.1.0.tar.gz.
File metadata
- Download URL: bouncezero-0.1.0.tar.gz
- Upload date:
- Size: 7.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07d376d590673f89473cc4ee2156ebbb45d4c02ccd0fbda3334d3bf578cb23e8
|
|
| MD5 |
c5a69e3f2e8d5f0fa16c8ddf929948a4
|
|
| BLAKE2b-256 |
c88e1e3832af8104f92bdc1ca8f39b9fcd222b6d9040825213da9646ad64d14c
|
Provenance
The following attestation bundles were made for bouncezero-0.1.0.tar.gz:
Publisher:
publish.yml on BounceZero/bouncezero-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bouncezero-0.1.0.tar.gz -
Subject digest:
07d376d590673f89473cc4ee2156ebbb45d4c02ccd0fbda3334d3bf578cb23e8 - Sigstore transparency entry: 2146140061
- Sigstore integration time:
-
Permalink:
BounceZero/bouncezero-python@a0f0af121bc5091904bad82e73f689c10d23287b -
Branch / Tag:
refs/heads/main - Owner: https://github.com/BounceZero
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a0f0af121bc5091904bad82e73f689c10d23287b -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file bouncezero-0.1.0-py3-none-any.whl.
File metadata
- Download URL: bouncezero-0.1.0-py3-none-any.whl
- Upload date:
- Size: 8.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7997c3c1e21d5b524c035bc52fc42c3069f510323fcaeb76c638c92882ceb0ce
|
|
| MD5 |
5a6164726cbda6fdc28469b8fb088c11
|
|
| BLAKE2b-256 |
d7c9d56e11a48dd5a350c21bdaf9e84c8cdf02f9c4b23beff410aa0871dbe458
|
Provenance
The following attestation bundles were made for bouncezero-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on BounceZero/bouncezero-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bouncezero-0.1.0-py3-none-any.whl -
Subject digest:
7997c3c1e21d5b524c035bc52fc42c3069f510323fcaeb76c638c92882ceb0ce - Sigstore transparency entry: 2146140197
- Sigstore integration time:
-
Permalink:
BounceZero/bouncezero-python@a0f0af121bc5091904bad82e73f689c10d23287b -
Branch / Tag:
refs/heads/main - Owner: https://github.com/BounceZero
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a0f0af121bc5091904bad82e73f689c10d23287b -
Trigger Event:
workflow_dispatch
-
Statement type: