Developer docs
Email verification API reference.
Two-pass email verification over a REST API. Bearer auth, predictable JSON, bulk endpoints up to 10,000 addresses per call, and HMAC-signed webhooks. Generate a key in the dashboard to get started.
Quickstart
Three steps to your first verification.
- 1. Generate an API key in your dashboard. Copy the full key when shown. You will not see it again.
- 2. Make a verification request:
curl https://www.leadlistverifier.com/api/v1/verify?email=jeremy@elevateclientsinc.com \
-H "Authorization: Bearer llv_live_..."3. Read the JSON response. result is deliverable, undeliverable, risky, or unknown. One credit is deducted on success.
Authentication
All requests require an API key passed in the Authorization header as a bearer token. Keys are stored as SHA-256 hashes; we cannot recover a lost key. Manage keys in the dashboard.
Authorization: Bearer llv_live_<32 hex chars>Revoked or missing keys return a 401 with the standardized error shape (see Error codes).
Base URL
https://www.leadlistverifier.com/api/v1Rate limits
Every API key is limited to 60 requests per minute. These headers are returned on every response (success and error):
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1745510400When you exceed the limit, the response is 429 rate_limit_exceeded with a Retry-After header indicating how long to wait (seconds).
Single verification
/api/v1/verify/api/v1/verifyRun the two-pass engine on a single address. Deducts 1 credit on success. If the verification engine fails, the credit is refunded automatically.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| string | Yes | Address to verify. Use ?email= for GET or { email } in the JSON body for POST. |
Examples
curl "https://www.leadlistverifier.com/api/v1/verify?email=alex@orbital.tech" \
-H "Authorization: Bearer $LLV_API_KEY"import os, requests
res = requests.get(
"https://www.leadlistverifier.com/api/v1/verify",
params={"email": "alex@orbital.tech"},
headers={"Authorization": f"Bearer {os.environ['LLV_API_KEY']}"},
)
res.raise_for_status()
print(res.json())const res = await fetch(
"https://www.leadlistverifier.com/api/v1/verify?email=alex@orbital.tech",
{ headers: { Authorization: `Bearer ${process.env.LLV_API_KEY}` } }
);
if (!res.ok) throw new Error(`Status ${res.status}`);
const body = await res.json();
console.log(body);Success response (200)
{
"email": "alex@orbital.tech",
"result": "deliverable",
"score": 91,
"engine": "two_pass",
"is_role": false,
"is_free": false,
"is_disposable": false,
"is_catch_all": true,
"did_pass_two": true,
"credits_remaining": 4932
}Result values
| Result | Description |
|---|---|
| deliverable | Email exists and accepts mail. |
| undeliverable | Email does not exist or the domain is invalid. |
| risky | Catch-all domain or low-confidence verification. |
| unknown | Could not be determined by the two-pass engine. |
Engine
primary indicates the result was returned by the primary SMTP verification engine alone. two_pass means the proprietary catch-all resolution engine ran a second pass to produce a definitive verdict on a catch-all or unknown address.
Bulk verification
/api/v1/verify-bulkSubmit up to 10,000 addresses per call. Credits are deducted up front; the engine runs asynchronously and you receive a job_id to poll for status or to subscribe to via the bulk.completed webhook.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| emails | string[] | No | Array of addresses (max 10,000). Required unless list_id is provided. |
| list_id | string | No | Re-run verification on an existing list. Mutually exclusive with emails. |
Examples
curl -X POST https://www.leadlistverifier.com/api/v1/verify-bulk \
-H "Authorization: Bearer $LLV_API_KEY" \
-H "Content-Type: application/json" \
-d '{"emails": ["one@example.com", "two@example.com"]}'import os, requests
res = requests.post(
"https://www.leadlistverifier.com/api/v1/verify-bulk",
headers={"Authorization": f"Bearer {os.environ['LLV_API_KEY']}"},
json={"emails": ["one@example.com", "two@example.com"]},
)
res.raise_for_status()
job = res.json()
print("job_id:", job["job_id"])const res = await fetch(
"https://www.leadlistverifier.com/api/v1/verify-bulk",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LLV_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ emails: ["one@example.com", "two@example.com"] }),
}
);
const job = await res.json();
console.log("job_id:", job.job_id);Success response (200)
{
"job_id": "clxyz123abc",
"status": "queued",
"count": 2,
"credits_used": 2,
"credits_remaining": 4930,
"webhook_url": "https://your-app.example.com/webhooks/llv"
}Job status
/api/v1/verify-bulk/:job_idPoll for the status of a bulk job. When status is completed, results_url is a CSV export endpoint you can fetch.
Example
curl https://www.leadlistverifier.com/api/v1/verify-bulk/clxyz123abc \
-H "Authorization: Bearer $LLV_API_KEY"Response shape
{
"job_id": "clxyz123abc",
"status": "completed",
"total": 10000,
"completed": 10000,
"deliverable": 7800,
"undeliverable": 1200,
"risky": 800,
"unknown": 200,
"results_url": "https://www.leadlistverifier.com/api/v1/lists/clxyz123abc/export?filter=all",
"webhook_delivered": true,
"created_at": "2026-05-13T14:22:01.000Z",
"completed_at": "2026-05-13T14:38:44.000Z"
}status is one of queued, processing, completed, or failed.
List management
Inspect and export lists created by previous bulk jobs or via the web dashboard.
List index
/api/v1/listsCursor-paginated. Up to 200 lists per page. ?limit= (1-200, default 50), ?cursor= for the next page.
curl "https://www.leadlistverifier.com/api/v1/lists?limit=20" \
-H "Authorization: Bearer $LLV_API_KEY"List detail
/api/v1/lists/:id{
"id": "clxyz123abc",
"file_name": "api-bulk-1747000000.csv",
"status": "completed",
"raw_row_count": 10000,
"total": 9842,
"completed": 9842,
"duplicates_removed": 158,
"credits_charged": 9842,
"deliverable": 7680,
"undeliverable": 1180,
"risky": 790,
"unknown": 192,
"created_at": "2026-05-13T14:22:01.000Z",
"completed_at": "2026-05-13T14:38:44.000Z",
"error_message": null
}Per-result paging
/api/v1/lists/:id/resultsCursor-paginated, up to 1,000 results per page. Filter with ?status=deliverable|undeliverable|risky|unknown.
CSV export
/api/v1/lists/:id/exportReturns CSV. Required: ?filter=all|deliverable|undeliverable|risky. List must be in completed status.
curl "https://www.leadlistverifier.com/api/v1/lists/clxyz123abc/export?filter=deliverable" \
-H "Authorization: Bearer $LLV_API_KEY" \
-o deliverable.csvCredits balance
/api/v1/creditsReturns the caller’s current credit balance and total credits consumed in the last 30 days.
curl https://www.leadlistverifier.com/api/v1/credits \
-H "Authorization: Bearer $LLV_API_KEY"Response
{
"credits_remaining": 4932,
"credits_used_30d": 1068
}Webhooks
Configure a webhook in the dashboard to receive HMAC-SHA256 signed event notifications. One webhook URL per account. Up to 5 retries with exponential backoff on non-2xx responses.
Event types
| Event | When it fires |
|---|---|
| verification.completed | Fires after every successful single verification via /api/v1/verify. |
| bulk.completed | Fires when a bulk job finishes. Payload includes final counts and a results_url for CSV export. |
| bulk.failed | Fires when a bulk job fails. Credits are refunded automatically before this event is delivered. |
Delivery headers
Content-Type: application/json
User-Agent: LeadListVerifier-Webhook/1
X-LLV-Event: bulk.completed
X-LLV-Timestamp: 1745510400
X-LLV-Signature: sha256=8f4e2c...Payload (bulk.completed)
{
"event": "bulk.completed",
"timestamp": "2026-05-13T14:38:44.000Z",
"data": {
"job_id": "clxyz123abc",
"list_id": "clxyz123abc",
"total": 10000,
"deliverable": 7800,
"undeliverable": 1200,
"risky": 800,
"unknown": 200,
"results_url": "https://www.leadlistverifier.com/api/v1/lists/clxyz123abc/export?filter=all"
}
}Verifying signatures
Compute HMAC-SHA256 of the raw request body using your webhook signing secret, then compare to the value after sha256= in X-LLV-Signature. Use a timing-safe comparison.
import hmac, hashlib
def verify(secret: str, body: bytes, sig_header: str) -> bool:
received = sig_header.removeprefix("sha256=")
expected = hmac.new(
secret.encode(),
body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(received, expected)import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, body, sigHeader) {
const received = sigHeader.startsWith("sha256=")
? sigHeader.slice(7)
: sigHeader;
const expected = createHmac("sha256", secret).update(body).digest("hex");
const a = Buffer.from(received, "hex");
const b = Buffer.from(expected, "hex");
if (a.length !== b.length) return false;
return timingSafeEqual(a, b);
}Sign the raw request body bytes. Re-serializing the JSON before signing will break the comparison.
Error codes
All errors return a JSON body with this shape:
{
"error": {
"code": "insufficient_credits",
"message": "Not enough credits to verify this address."
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Request body or query parameters are missing or malformed. |
| 401 | unauthorized | Missing, invalid, or revoked API key. |
| 402 | insufficient_credits | Not enough credits to perform the requested operation. |
| 403 | forbidden | Authenticated key is not allowed to access this resource. |
| 404 | not_found | Resource does not exist or does not belong to this account. |
| 429 | rate_limit_exceeded | Exceeded 60 requests per minute. Wait until Retry-After. |
| 500 | internal_server_error | Unexpected server error. Safe to retry with backoff. |