Install and configure
You need Python 3.9 or newer and requests. Export your API key so it never lands in source control.
pip install requests
export VERIDOCK_API_KEY=vd_test_••••••••••••A session with sensible retries
A requests Session reuses connections and lets you mount a retry policy once. The policy retries 429 and 5xx responses, waits for the Retry-After header when VeriDock sends one, and backs off exponentially otherwise. It only retries GET, so a network blip can never create the same bulk job twice.
import os
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
API = "https://app.veridock.io/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['VERIDOCK_API_KEY']}"
session.mount(
"https://",
HTTPAdapter(
max_retries=Retry(
total=4,
backoff_factor=0.5,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods={"GET"},
respect_retry_after_header=True,
)
),
)
def verify(email: str) -> dict:
r = session.get(f"{API}/verify", params={"email": email}, timeout=15)
r.raise_for_status()
return r.json()Upload a CSV as a bulk job
Send the file as multipart form data and name the column that holds the addresses. The response is the job with an id you can poll.
import time
from veridock import API, session
def create_job(path: str, name: str, email_column: str = "email") -> str:
with open(path, "rb") as f:
r = session.post(
f"{API}/bulk",
data={"name": name, "email_column": email_column},
files={"file": (path, f, "text/csv")},
timeout=60,
)
r.raise_for_status()
return r.json()["id"]
def wait_for(job_id: str, interval: float = 5) -> dict:
while True:
r = session.get(f"{API}/bulk/{job_id}", timeout=15)
r.raise_for_status()
job = r.json()
if job["status"] == "completed":
return job
if job["status"] == "failed":
raise RuntimeError(job.get("error", "bulk job failed"))
print(f"{job.get('processed', 0)}/{job['total']} checked")
time.sleep(interval)Keep only the good rows
Results can be streamed as newline-delimited JSON and filtered by status on the server, so you never hold the whole list in memory. This keeps deliverable addresses, and risky ones unless they are disposable, then writes a clean CSV ready to import.
import csv
import json
def write_clean(job_id: str, out_path: str) -> int:
kept = 0
with open(out_path, "w", newline="") as out:
writer = csv.writer(out)
writer.writerow(["email", "status", "reason", "score"])
for status in ("deliverable", "risky"):
r = session.get(
f"{API}/bulk/{job_id}/results",
params={"status": status, "format": "ndjson"},
stream=True,
timeout=60,
)
r.raise_for_status()
for line in r.iter_lines():
if not line:
continue
row = json.loads(line)
if row["reason"] == "disposable":
continue
writer.writerow([row["email"], row["status"], row["reason"], row["score"]])
kept += 1
return kept
if __name__ == "__main__":
job_id = create_job("subscribers.csv", "october-newsletter")
job = wait_for(job_id)
print("summary:", job["summary"])
print("kept", write_clean(job_id, "subscribers.clean.csv"), "addresses")Unknown results are left out of the clean file here but are never billed. Re-run them in a day or two: most come back with a definite answer once a slow server responds.
Reference
Frequently asked questions
Keep reading
Other guides: Next.js · Node.js · React · PHP · Supabase · Zapier, Make and n8n