OptimaVerifierv1
Back to app
v1 · stable · operational

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.

Use your Optima API key for every request. Send it in theAuthorization 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.

  1. Create or copy an API key from the dashboard.
  2. Send a POST request to https://app.optimaverifier.com/api/v1/verifications/single with your key in the Authorization header.
  3. Read status from the response. It will be one of valid, invalid, unknown, or catch-all.
Once it works, try bulk verification for lists, or read about error shapes before putting code into production.
# 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.

Never expose an API key to a browser. Anything that authenticates your account belongs in a server-side environment, behind your own auth layer.

Key format

All keys use the sk_live_ prefix. Treat them as secrets and never commit them to source control.

Authorization header
Authorization: Bearer <api_key>
Content-Type:  application/json
If the key is missing or invalid401 Unauthorized
{
  "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:

https://app.optimaverifier.com/api/v1

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.

URL anatomy
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.

POSThttps://app.optimaverifier.com/api/v1/verifications/single~800 ms p5015 req / min1 credit

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

200Verification complete (regardless of result - invalid is still 200).
401Missing or invalid API key.
422Email syntactically invalid or empty.
429Rate 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"}'
Valid · delivery confirmed200 OK
{
  "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.

POSThttps://app.optimaverifier.com/api/v1/verifications/bulkasync1M max1 credit / unique email

Workflow

01 · Submit
POST your list
POST /verifications/bulk
Get back a job id with status queued. Deduplication happens before queueing - you're only billed for unique emails.
02 · Poll
Watch progress
GET /verifications/job/{id}
Check every 5–15s. Status goes queued → processing → completed.
03 · Download
Export results
GET /verifications/job/{id}/export?format=csv
Stream the per-row CSV. Results stay available for 30 days.

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"
  }'
Response · job queued201 Created
{
  "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.

POSThttps://app.optimaverifier.com/api/v1/verifications/bulk/uploadmultipart/form-data1M rowsTXT only

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.

GEThttps://app.optimaverifier.com/api/v1/verifications/job/{job_id}lightweightfree to poll

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>"
Response · in progress200 OK
{
  "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.

GEThttps://app.optimaverifier.com/api/v1/verifications/job/{job_id}/resultspaginatedoptional status filter

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>"
Response · page of results200 OK
{
  "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.

GEThttps://app.optimaverifier.com/api/v1/verifications/job/{job_id}/exportstreamingCSV

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.

GEThttps://app.optimaverifier.com/api/v1/verifications/historypaginated

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.

GEThttps://app.optimaverifier.com/api/v1/creditsfree to call

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>"
Response200 OK
{
  "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.

validThe mailbox exists and accepts mail. Safe to send to. ~94% of typical lists.
invalidThe mailbox does not exist (SMTP server rejected the recipient) or the domain has no mail exchanger. Do not send.
unknownThe mail server did not give a definitive answer in time (greylisting, temporary failure, anti-bot). Retry later or treat as risky.
catch-allThe 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.

400Validation error not caught by the schema (e.g. unsupported upload format).
401Missing or invalid API key.
403API key is valid but the account does not have API access enabled.
404The requested resource (job, verification) doesn't exist or belongs to a different user.
422Pydantic schema validation failed. Body field-level details are in detail.
429Rate limit. Retry-After header tells you when to retry.
500Unexpected server error. Safe to retry once with backoff.
503Upstream MX server unreachable during a verification. Safe to retry.
Example error body422 Unprocessable
{
  "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.