The Verifier
API
Verify a single address in under a second, or submit up to a million emails per job and poll until done. One auth model. Predictable JSON.
Authorization header as a bearer token. Keep API keys server-side and rotate them if they are exposed.Quickstart
Verify your first address in 60 seconds.
- Create or copy an API key from the dashboard.
- Send a
POSTrequest tohttps://app.optimaverifier.com/api/v1/verifications/singlewith your key in theAuthorizationheader. - Read
statusfrom the response. It will be one ofvalid,invalid,unknown, orcatch-all.
# Verify a single email
curl -X POST https://app.optimaverifier.com/api/v1/verifications/single \
-H "Authorization: Bearer <api_key>" \
-H "Content-Type: application/json" \
-d '{"email": "ada.lovelace@example.com"}'Authentication
Authenticate requests with an Optima API key in the standard bearer-token header.
API keys
API keys identify the account that owns the request and determine the credit balance, rate limits, and job history used by the call.
Server-side only
Send API requests from your backend, worker, or command-line tooling. Do not embed API keys in frontend JavaScript, mobile apps, or public repositories.
Key format
All keys use the sk_live_ prefix. Treat them as secrets and never commit them to source control.
Authorization: Bearer <api_key> Content-Type: application/json
{
"detail": "Could not validate credentials"
}Base URL
A single versioned host serves every customer. Your API key identifies the account.
The production API base is:
All endpoint paths in these docs are relative to this base URL.
Versioning
The path is versioned (/api/v1/…). Breaking changes get a new major version. v1 will be supported for at least 12 months after v2 ships.
https://app.optimaverifier.com/api/v1/{resource}
// e.g.
https://app.optimaverifier.com/api/v1/verifications/single
https://app.optimaverifier.com/api/v1/verifications/bulk
https://app.optimaverifier.com/api/v1/verifications/job/{job_id}Verify one email
Synchronous; returns in under a second for most addresses. Use this for signup forms, account changes, and real-time validation.
Body parameters
| emailrequired string | The email address to verify. Lowercased server-side. Plus-addressing is preserved (e.g. ada+lists@example.com is checked as-is). |
Returns
An EmailVerifyResponse with the resolved status, an ISO-8601 verified_attimestamp, and the user's updated credits_remaining. The same email submitted within the cache window is served from cache and does not consume a credit.
Status codes
| 200 | Verification complete (regardless of result - invalid is still 200). |
| 401 | Missing or invalid API key. |
| 422 | Email syntactically invalid or empty. |
| 429 | Rate limit or monthly usage limit exceeded. See Retry-After header. |
curl -X POST https://app.optimaverifier.com/api/v1/verifications/single \
-H "Authorization: Bearer <api_key>" \
-H "Content-Type: application/json" \
-d '{"email": "ada.lovelace@example.com"}'{
"email": "ada.lovelace@example.com",
"status": "valid",
"verified_at": "2026-05-25T10:30:00.123456+00:00",
"credits_remaining": 4999
}Bulk verify · submit a JSON array
Submit up to 1 million emails per request as a JSON array. We deduplicate, queue, and return a job id immediately.
Workflow
queued. Deduplication happens before queueing - you're only billed for unique emails.queued → processing → completed.Body parameters
| emailsrequired array<string> | List of addresses to verify. Min 1, max 1,000,000 per request. Duplicates are removed before billing - you only pay for unique addresses. |
| job_namerequired string · 1–255 chars | Label shown in the dashboard and search. |
| row_metadataoptional object | Per-email metadata keyed by email address. Echoed back in CSV export so you can join results to your source rows. |
| extra_columnsoptional array<string> | Ordered list of metadata keys to include as columns in the CSV export. |
Returns
A BulkJobResponse with status queued, the deduplicated total_rows, and duplicate_count showing how many duplicates were removed before queueing.
curl -X POST https://app.optimaverifier.com/api/v1/verifications/bulk \
-H "Authorization: Bearer <api_key>" \
-H "Content-Type: application/json" \
-d '{
"emails": ["ada@example.com", "marcus@brand.co", "rachel@nope.invalid"],
"job_name": "May newsletter sweep"
}'{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"job_name": "May newsletter sweep",
"status": "queued",
"total_rows": 2964,
"original_count": 3001,
"duplicate_count": 37,
"processed_rows": 0,
"valid_count": 0,
"invalid_count": 0,
"unknown_count": 0,
"catch_all_count": 0,
"created_at": "2026-05-25T10:30:00+00:00",
"completed_at": null,
"credits_reserved": 2964
}Bulk verify · upload a file
For lists too large to JSON-encode. Send a multipart upload with a .txt file - one email per line.
Form fields
| filerequired file · .txt | One email address per line. Only .txt files are accepted. For CSV input, extract the email column client-side and use the JSON endpoint instead. |
| job_namerequired string | Label shown in the dashboard. |
Returns
Same BulkJobResponse shape as the JSON endpoint. Track with the polling endpoint.
curl -X POST https://app.optimaverifier.com/api/v1/verifications/bulk/upload \ -H "Authorization: Bearer <api_key>" \ -F "file=@my-list.txt" \ -F "job_name=Q2 contacts"
Poll a job
Lightweight status endpoint - safe to call every few seconds.
Returns
A BulkJobStatus with progress_percentage and the per-status counts as they accumulate.
curl https://app.optimaverifier.com/api/v1/verifications/job/<job_id> \ -H "Authorization: Bearer <api_key>"
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"progress_percentage": 47.3,
"total_rows": 2964,
"processed_rows": 1402,
"valid_count": 1284,
"invalid_count": 95,
"unknown_count": 17,
"catch_all_count": 6
}Read paginated results
Per-row results for a completed (or in-progress) bulk job.
Query parameters
| pageoptional integer · default 1 | 1-indexed. |
| per_pageoptional integer · default 50 | Page size. Hard cap at 100. |
| status_filteroptional string | Filter to valid, invalid, unknown, or catch-all. |
curl "https://app.optimaverifier.com/api/v1/verifications/job/<job_id>/results?page=1&per_page=50" \ -H "Authorization: Bearer <api_key>"
{
"id": "a1b2c3d4-...",
"job_name": "May newsletter sweep",
"total_count": 2964,
"page": 1,
"per_page": 50,
"results": [
{ "email": "ada@example.com", "status": "valid" },
{ "email": "rachel@nope.invalid", "status": "invalid" }
]
}Export CSV
Stream the full per-row result set as a CSV file. Echoes back any row_metadata columns you submitted.
Query parameters
| formatoptional string · default csv | Currently only csv. JSONL export is planned. |
CSV shape
One row per submitted email. Default columns: email, status. If extra_columns was supplied at submit time, those keys are appended in the order given.
curl "https://app.optimaverifier.com/api/v1/verifications/job/<job_id>/export?format=csv" \ -H "Authorization: Bearer <api_key>" \ -o results.csv
Verification history
All single verifications the user has run, newest first. Bulk results live on each job's /results endpoint instead.
Query parameters
| pageoptional integer · default 1 | 1-indexed. |
| per_pageoptional integer · default 50 | Page size. |
curl "https://app.optimaverifier.com/api/v1/verifications/history?page=1&per_page=50" \ -H "Authorization: Bearer <api_key>"
Check balance
Returns the current credit balance and the 20 most recent ledger entries.
Returns
| balance integer | Current credit balance. |
| recent array | Up to 20 most recent ledger entries. Each entry has delta (positive = credit, negative = debit), reason, balance_after, optional note, and created_at. |
curl "https://app.optimaverifier.com/api/v1/credits" \ -H "Authorization: Bearer <api_key>"
{
"balance": 4999,
"recent": [
{
"delta": -1,
"reason": "consume",
"balance_after": 4999,
"note": null,
"created_at": "2026-05-25T10:30:00+00:00"
}
]
}Status vocabulary
Every verification resolves to exactly one of these four states.
| valid | The mailbox exists and accepts mail. Safe to send to. ~94% of typical lists. |
| invalid | The mailbox does not exist (SMTP server rejected the recipient) or the domain has no mail exchanger. Do not send. |
| unknown | The mail server did not give a definitive answer in time (greylisting, temporary failure, anti-bot). Retry later or treat as risky. |
| catch-all | The domain accepts every address, so we can't tell whether the specific mailbox exists. Treat as risky; send with care. |
Errors
Errors come back as JSON with a detail field. The HTTP status code is the source of truth.
| 400 | Validation error not caught by the schema (e.g. unsupported upload format). |
| 401 | Missing or invalid API key. |
| 403 | API key is valid but the account does not have API access enabled. |
| 404 | The requested resource (job, verification) doesn't exist or belongs to a different user. |
| 422 | Pydantic schema validation failed. Body field-level details are in detail. |
| 429 | Rate limit. Retry-After header tells you when to retry. |
| 500 | Unexpected server error. Safe to retry once with backoff. |
| 503 | Upstream MX server unreachable during a verification. Safe to retry. |
{
"detail": [
{
"type": "value_error",
"loc": ["body", "emails"],
"msg": "List should have at most 1000000 items",
"input": [...]
}
]
}Quotas & limits
Per-user limits enforced server-side.
| Single verify rate 15 / minute / user | Sliding window. Returns 429 with Retry-After when exceeded. |
| Bulk JSON size 1,000,000 emails | Per request. Split larger lists into multiple jobs. |
| Bulk upload size ~200 MB | Approximate; depends on average line length. Use the JSON endpoint for tighter framing. |
| Concurrent bulk jobs No hard cap | The queue processes jobs in submission order; you can have many in flight at once. |
| Result retention 30 days | Per-row results and CSV exports for a bulk job. Aggregate counts are kept indefinitely. |