# Optima Verifier API Integration Guide Use this guide to integrate Optima email verification into an application, backend service, worker, or automation. ## Core rules - Base URL: https://app.optimaverifier.com/api/v1 - Authentication header: Authorization: Bearer - API key format: sk_live_ prefix followed by 48 alphanumeric characters. - Recommended environment variable: OPTIMA_API_KEY - Keep API keys server-side only. - Do not embed API keys in frontend JavaScript, mobile apps, client-distributed code, or public repositories. - Send JSON requests with Content-Type: application/json unless the endpoint uses multipart/form-data. - Verification statuses are: valid, invalid, unknown, catch-all. - A completed single verification returns HTTP 200 even when the email status is invalid. - File uploads (/verifications/bulk/upload) only accept .txt files (one email per line). For CSV input, extract the email column client-side and POST to /verifications/bulk. ## Status vocabulary - valid: The mailbox exists and accepts mail. - invalid: The mailbox does not exist, or the domain cannot receive mail. - unknown: The mail server did not provide a definitive answer in time. - catch-all: The domain accepts every address, so the exact mailbox cannot be confirmed. ## Authentication example HTTP header: Authorization: Bearer Content-Type: application/json ## POST /verifications/single Verify one email address synchronously. Request body: { "email": "ada.lovelace@example.com" } Response body: { "email": "ada.lovelace@example.com", "status": "valid", "verified_at": "2026-05-25T10:30:00.123456+00:00", "credits_remaining": 4999 } cURL: curl -X POST https://app.optimaverifier.com/api/v1/verifications/single \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"email": "ada.lovelace@example.com"}' JavaScript: const apiKey = process.env.OPTIMA_API_KEY const res = await fetch('https://app.optimaverifier.com/api/v1/verifications/single', { method: 'POST', headers: { 'Authorization': 'Bearer ' + apiKey, 'Content-Type': 'application/json', }, body: JSON.stringify({ email: 'ada.lovelace@example.com' }), }) const data = await res.json() Python: import os import requests api_key = os.environ["OPTIMA_API_KEY"] res = requests.post( "https://app.optimaverifier.com/api/v1/verifications/single", headers={"Authorization": f"Bearer {api_key}"}, json={"email": "ada.lovelace@example.com"}, ) data = res.json() Notes: - Typical use: signup forms, account updates, lead capture, real-time validation. - Read data["status"] and branch on valid, invalid, unknown, or catch-all. ## POST /verifications/bulk Submit a bulk verification job as JSON. Required fields: - emails: array of email strings. Min 1, max 1,000,000. - job_name: human-readable label for this job (1-255 chars). Optional fields: - row_metadata: object keyed by lowercased email. Values are metadata objects (values capped at 500 chars each). - extra_columns: ordered list of metadata keys to include as columns in the CSV export. Request body: { "emails": ["ada@example.com", "marcus@brand.co", "rachel@nope.invalid"], "job_name": "May newsletter sweep", "row_metadata": { "ada@example.com": { "crm_id": "123" } }, "extra_columns": ["crm_id"] } Response body: { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "job_name": "May newsletter sweep", "status": "queued", "total_rows": 3, "original_count": 3, "duplicate_count": 0, "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": 3 } cURL: curl -X POST https://app.optimaverifier.com/api/v1/verifications/bulk \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "emails": ["ada@example.com", "marcus@brand.co", "rachel@nope.invalid"], "job_name": "May newsletter sweep" }' JavaScript: const apiKey = process.env.OPTIMA_API_KEY const res = await fetch('https://app.optimaverifier.com/api/v1/verifications/bulk', { method: 'POST', headers: { 'Authorization': 'Bearer ' + apiKey, 'Content-Type': 'application/json', }, body: JSON.stringify({ emails: ['ada@example.com', 'marcus@brand.co', 'rachel@nope.invalid'], job_name: 'May newsletter sweep', }), }) const job = await res.json() Python: import os import requests api_key = os.environ["OPTIMA_API_KEY"] job = requests.post( "https://app.optimaverifier.com/api/v1/verifications/bulk", headers={"Authorization": f"Bearer {api_key}"}, json={ "emails": ["ada@example.com", "marcus@brand.co", "rachel@nope.invalid"], "job_name": "May newsletter sweep", }, ).json() ## POST /verifications/bulk/upload Upload a TXT file for bulk verification. Only .txt files are accepted (one email per line). For CSV input, extract the email column client-side and POST to /verifications/bulk instead. Form fields: - file: required. Must be a .txt file. - job_name: required label. cURL: curl -X POST https://app.optimaverifier.com/api/v1/verifications/bulk/upload \ -H "Authorization: Bearer " \ -F "file=@my-list.txt" \ -F "job_name=Q2 contacts" JavaScript: const apiKey = process.env.OPTIMA_API_KEY const fd = new FormData() fd.append('file', txtFile) fd.append('job_name', 'Q2 contacts') const res = await fetch('https://app.optimaverifier.com/api/v1/verifications/bulk/upload', { method: 'POST', headers: { 'Authorization': 'Bearer ' + apiKey }, body: fd, }) const job = await res.json() Python: import os import requests api_key = os.environ["OPTIMA_API_KEY"] with open("my-list.txt", "rb") as f: job = requests.post( "https://app.optimaverifier.com/api/v1/verifications/bulk/upload", headers={"Authorization": f"Bearer {api_key}"}, files={"file": ("my-list.txt", f, "text/plain")}, data={"job_name": "Q2 contacts"}, ).json() ## GET /verifications/job/{job_id} Poll a bulk job until it completes. Response body: { "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 } cURL: curl https://app.optimaverifier.com/api/v1/verifications/job/ \ -H "Authorization: Bearer " JavaScript: const apiKey = process.env.OPTIMA_API_KEY const res = await fetch('https://app.optimaverifier.com/api/v1/verifications/job/' + jobId, { headers: { 'Authorization': 'Bearer ' + apiKey }, }) const status = await res.json() Python: import os import time import requests api_key = os.environ["OPTIMA_API_KEY"] while True: status = requests.get( f"https://app.optimaverifier.com/api/v1/verifications/job/{job_id}", headers={"Authorization": f"Bearer {api_key}"}, ).json() if status["status"] in ("completed", "error"): break time.sleep(8) ## GET /verifications/job/{job_id}/results Read paginated results for a bulk job. Query parameters: - page: optional integer, default 1. - per_page: optional integer, default 50, maximum 100. - status_filter: optional string: valid, invalid, unknown, or catch-all. Response body: { "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" } ] } cURL: curl "https://app.optimaverifier.com/api/v1/verifications/job//results?page=1&per_page=50" \ -H "Authorization: Bearer " JavaScript: const apiKey = process.env.OPTIMA_API_KEY const res = await fetch('https://app.optimaverifier.com/api/v1/verifications/job/' + jobId + '/results?page=1&per_page=50', { headers: { 'Authorization': 'Bearer ' + apiKey }, }) const page = await res.json() Python: import os import requests api_key = os.environ["OPTIMA_API_KEY"] page = requests.get( f"https://app.optimaverifier.com/api/v1/verifications/job/{job_id}/results", headers={"Authorization": f"Bearer {api_key}"}, params={"page": 1, "per_page": 50}, ).json() ## GET /verifications/job/{job_id}/export Export bulk results as CSV. Only csv format is supported. Query parameters: - format: optional string, must be csv (default). cURL: curl "https://app.optimaverifier.com/api/v1/verifications/job//export?format=csv" \ -H "Authorization: Bearer " \ -o results.csv Python: import os import requests api_key = os.environ["OPTIMA_API_KEY"] with requests.get( f"https://app.optimaverifier.com/api/v1/verifications/job/{job_id}/export", headers={"Authorization": f"Bearer {api_key}"}, params={"format": "csv"}, stream=True, ) as r: r.raise_for_status() with open("results.csv", "wb") as f: for chunk in r.iter_content(8192): f.write(chunk) CSV notes: - Default columns are email and status. - If extra_columns were supplied at job creation, matching metadata values are appended as columns. ## GET /verifications/history Read single-verification history, newest first. Bulk job results are not included here. Query parameters: - page: optional integer, default 1. - per_page: optional integer, default 50, maximum 100. cURL: curl "https://app.optimaverifier.com/api/v1/verifications/history?page=1&per_page=50" \ -H "Authorization: Bearer " ## GET /credits Check the current credit balance and the 20 most recent ledger entries. Works with both API keys and session JWTs. Response body: { "balance": 4999, "recent": [ { "delta": -1, "reason": "consume", "balance_after": 4999, "note": null, "created_at": "2026-05-25T10:30:00+00:00" } ] } cURL: curl "https://app.optimaverifier.com/api/v1/credits" \ -H "Authorization: Bearer " JavaScript: const apiKey = process.env.OPTIMA_API_KEY const res = await fetch('https://app.optimaverifier.com/api/v1/credits', { headers: { 'Authorization': 'Bearer ' + apiKey }, }) const { balance } = await res.json() Python: import os import requests api_key = os.environ["OPTIMA_API_KEY"] data = requests.get( "https://app.optimaverifier.com/api/v1/credits", headers={"Authorization": f"Bearer {api_key}"}, ).json() print(data["balance"]) ## Error codes - 400: Validation error not caught by the schema (e.g. non-.txt file uploaded, unsupported export format). - 401: Missing or invalid API key. - 403: API key is valid but the account does not have API access enabled. - 404: Requested job or verification was not found or is not available for this account. - 422: Request schema validation failed. Inspect the detail field. - 429: Rate limit or monthly usage limit exceeded. Respect Retry-After when present. - 500: Unexpected server error. Retry once with exponential backoff. Error body shape: { "detail": "Could not validate credentials" } or field-level validation details: { "detail": [ { "type": "value_error", "loc": ["body", "emails"], "msg": "List should have at most 1000000 items", "input": [] } ] } ## Recommended workflows Single verification: 1. POST /verifications/single with one email. 2. Read status. 3. Treat valid as safe, invalid as do-not-send, unknown and catch-all as risky. Bulk verification: 1. POST /verifications/bulk or POST /verifications/bulk/upload. 2. Store the returned job id. 3. Poll GET /verifications/job/{job_id} every 5-10 seconds. 4. When status is completed, fetch pages from /results or download /export?format=csv. Pagination: - Start at page=1. - Use per_page=50 by default. - Maximum per_page is 100. - Continue until the number of accumulated results reaches total_count or a page returns no results. Production guidance: - Load OPTIMA_API_KEY from environment or a secrets manager. - Add timeout handling around API calls. - Retry 429 only after Retry-After. - Retry 500 once or twice with exponential backoff. - Do not retry 401, 403, or 422 without changing credentials, account state, or request payload.