Bulk email validation
Bulk validation processes a JSON list in the background. Submit up to 10,000 email strings using a write API token and an active subscription. Each completed address validation counts as one API request, including repeated addresses and addresses classified as unsafe. Malformed addresses and rows that fail validation after retries are not billed.
Submit a list
Section titled “Submit a list”curl --request POST 'https://api.unwrap.email/v1/emails/bulk' \ --header "UNWRAP-EMAIL-API-TOKEN: $UNWRAP_EMAIL_API_TOKEN" \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: customer-import-2026-09-08' \ --data '{"emails":["person@example.com","invalid","person@example.com"],"validate_domain":1}'The response is 202 Accepted, with a job object, a status_url, and a matching Location header. Save the job ID. Processing starts asynchronously and does not depend on keeping the request or browser open.
Set validate_domain to 1 to include MX and registry checks. It defaults to 0, matching single-email validation. Existing normalization, blacklist checks, and contact-saving rules apply. Contact saving requires a safe address with valid MX records, so jobs without domain checks do not automatically populate contacts.
The request must contain between 1 and 10,000 strings and must fit within 5 MiB. Unsupported properties, non-string entries, and invalid settings reject the entire request. Empty or malformed email strings inside a valid list instead receive individual errors. After trimming, addresses must be at most 254 characters, with at most 64 characters before the @.
Use a distinct Idempotency-Key for each intended job. Retrying the same key and input returns the original job without starting additional work. Reusing a key with different input returns 409. Keys remain reserved until the job expires. Without a key, each submission creates a separate, billable job. You may have three unfinished jobs at a time; further submissions return 429.
Poll for progress and results
Section titled “Poll for progress and results”curl 'https://api.unwrap.email/v1/emails/bulk/JOB_ID?page=1&take=100' \ --header "UNWRAP-EMAIL-API-TOKEN: $UNWRAP_EMAIL_API_TOKEN"Use the returned status_url to poll every 10 seconds, backing off when requests fail. This single endpoint returns both progress and paginated results as { job, items, total, page, take }. The job contains status, total, processed, success, errors, created_at, updated_at, expires_at, and failure. The items array starts empty and fills as chunks finish. The top-level total counts committed result rows, while job.total counts all submitted addresses.
| Status | Meaning |
|---|---|
pending |
Inputs are saved and waiting for processing. |
processing |
Chunks are running or CSV output is being prepared. |
completed |
All rows have results and the CSV is ready. Individual rows may contain errors. |
failed |
Processing or CSV generation exhausted its retries. Committed JSON results remain available. |
expired |
The 30-day retrieval window has ended. Visible in job history until cleanup removes the job; polling returns 410. |
success counts completed validations, including unsafe addresses. Check each result’s validations.safe_to_send before deciding which addresses to use. Billing, analytics, and eligible contact updates are delivered asynchronously after results are persisted.
Each item has a zero-based index, the original input, and either a result matching single-email validation or an error with code and message. The unused field is null.
Whitespace is trimmed for validation while input preserves the supplied string. Every repeated occurrence keeps its own index, result ID, and usage unit.
Result pages contain committed rows in input order. While a job is processing, chunks may complete out of order and page membership can change. Wait for completed or failed before traversing a stable set of pages on this same endpoint. The default take is 25 and the maximum is 100. A failed job can have fewer committed results than submitted addresses. See the progress and results API reference for the response schema.
List previous jobs with GET /v1/emails/bulk?page=1&take=25. Bulk endpoints use standard API authentication. Read API tokens can retrieve owned job information and results under the existing token authentication rules; another user’s job returns 404.
Download CSV
Section titled “Download CSV”curl 'https://api.unwrap.email/v1/emails/bulk/JOB_ID/download' \ --header "UNWRAP-EMAIL-API-TOKEN: $UNWRAP_EMAIL_API_TOKEN"For a completed job, the response contains download_url and expires_at. Download using that URL as returned, preserving its complete query string. Links use the public CDN, expire after at most one hour, and cannot extend beyond the job’s expiry. Request a fresh link if an earlier one expires.
CSV preserves the original row order. Its first columns are index, input, error.code, and error.message, followed by available validation fields using dotted names. Arrays occupy one JSON-encoded cell. Empty cells represent absent values. Quotes, newlines, and spreadsheet formula prefixes are escaped for safe import.
Job data expires 30 days after submission. Expired polling and download requests return 410 until cleanup removes the job, after which requests return 404. Previously saved contacts follow their usual lifecycle.
See the bulk submission API reference for request and response schemas. This release accepts JSON input and uses polling; it does not include CSV uploads, cancellation, or completion callbacks.