Batch & File Verification
Verify large lists of emails asynchronously. Submit batches or upload CSV/TXT files. The per-batch cap is plan-dependent: 100 emails on Free, up to 10,000 on Enterprise.
Batch Verification
/v1/verify/batchSubmit an array of emails for asynchronous verification. Returns a job ID to track progress.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
emails | string[] | Yes | Array of email addresses. Plan-dependent cap: 100 (Free), 1,000 (Starter), 5,000 (Professional/Business), 10,000 (Enterprise) |
options | object | No | Verification options (e.g. deep_verify, include_ai) |
webhook_url | string | No | HTTPS URL to receive the completion notification |
webhook_secret | string | No | HMAC key used to sign the completion webhook (X-Kawaa-Signature). Requires webhook_url; max 256 characters |
include_activity | boolean | No | Include activity data in per-email results when available |
Any other field, at the top level or inside options, is rejected with 400 BAD_REQUEST naming the supported fields, so a misspelled option can never be silently ignored and charged for.
Example Request
curl -X POST https://api.kawaa.com/v1/verify/batch \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"emails": [
"user1@example.com",
"user2@example.com",
"user3@example.com"
],
"webhook_url": "https://your-app.com/webhook",
"webhook_secret": "your-signing-secret"
}'Response (202 Accepted)
HTTP/1.1 202 Accepted
{
"success": true,
"data": {
"job_id": "9f8f6d2e-4c1b-4f6e-9a3b-2d1e5c7a8b90",
"status": "processing",
"total_submitted": 3,
"duplicates_removed": 0,
"invalid_emails_skipped": 0,
"unique_emails": 3,
"estimated_time_seconds": 6,
"credits_used": 3,
"credits_remaining": 4997,
"message": "Batch verification started. Use GET /v1/jobs/{job_id} to check status."
}
}File Upload
/v1/verify/fileUpload a CSV or TXT file containing email addresses for verification. File verification is a two-step JSON flow: request a presigned upload URL, PUT your file to it, then start processing with the returned file_key.
Step 1: Request an Upload URL
curl -X POST https://api.kawaa.com/v1/verify/file \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "get_upload_url",
"filename": "emails.csv"
}'
# Response
{
"success": true,
"data": {
"upload_url": "https://kawaa-uploads.s3.amazonaws.com/uploads/...",
"file_key": "uploads/user_abc123/2c9c1f8e-.../emails.csv",
"expires_in_seconds": 900,
"next_step": "Upload file to upload_url using PUT, then call this endpoint with action=process and file_key"
}
}Step 2: Upload the File
curl -X PUT "UPLOAD_URL_FROM_STEP_1" \
--data-binary @emails.csvStep 3: Start Processing
curl -X POST https://api.kawaa.com/v1/verify/file \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "process",
"file_key": "uploads/user_abc123/2c9c1f8e-.../emails.csv",
"webhook_url": "https://your-app.com/webhook"
}'
# Response (202 Accepted)
{
"success": true,
"data": {
"job_id": "9f8f6d2e-4c1b-4f6e-9a3b-2d1e5c7a8b90",
"status": "processing",
"total_submitted": 1000,
"duplicates_removed": 12,
"invalid_emails_skipped": 3,
"unique_emails": 985,
"credits_used": 985,
"credits_remaining": 4015,
"message": "File verification started. 12 duplicate(s) removed. Use GET /v1/jobs/{job_id} to check status."
}
}Supported Formats & Limits
- CSV: Comma-, semicolon- or tab-separated (detected from the first line) with optional headers; the first column containing an
@is used - TXT: One email per line
- Max file size: 50MB (all plans)
- Max emails per file: your plan's batch cap — 100 (Free) up to 10,000 (Enterprise)
- Upload URL expiry: 15 minutes
Check Job Status
/v1/jobs/{job_id}Query Parameters
include_results- Embed per-email results in the response (default:true). Passfalsewhen polling for status only; it is cheaper and faster on large jobslimit- Results per page (default: 100, max: 1,000)next_key- Opaque cursor from the previous page'snext_key
When results are included, the job carries a results[] array of verification results (the same shape as POST /v1/verify), plus has_more and next_key for the next page. Each result also carries job_id and batch_job_id — both the id of this job, so a result can be traced back after it has been copied out of the response. For a full download in one request use GET /v1/jobs/{job_id}/download below.
Response
{
"success": true,
"data": {
"job_id": "9f8f6d2e-4c1b-4f6e-9a3b-2d1e5c7a8b90",
"status": "completed",
"total_emails": 1000,
"processed_emails": 1000,
"progress_percent": 100,
"summary": {
"valid": 800,
"invalid": 100,
"risky": 50,
"unknown": 50
},
"created_at": "2026-02-03T14:30:00.000Z",
"updated_at": "2026-02-03T14:35:42.000Z",
"completed_at": "2026-02-03T14:35:42.000Z",
"credits_used": 1000,
"credits_refunded": 0,
"billing_status": "charged",
"results": [ { "email": "john@example.com", "status": "valid", "sub_status": "mailbox_exists", "quality_score": 91 } ],
"has_more": true,
"next_key": "eyJqb2JfaWQiOi..."
}
}Job Status Values
pending- Accepted and queued; no email has been verified yetprocessing- Verification in progresscompleted- All emails verifiedfailed- Job failed (checkerror_message)cancelled- Stopped before completion; unverified emails are refundedenqueue_unknown- Some emails have unconfirmed queue status; credits are held and reconciled automatically
Download Results
/v1/jobs/{job_id}/downloadQuery Parameters
format-csvorjson(default: csv)filter- Filter by status:all,valid,invalid,risky,unknown,spam_trap,disposable,role,catch_all(default: all)
Response
The response depends on how many results match. Up to 1,000 results are returned in the body: a CSV file (Content-Type: text/csv, sent as an attachment) for format=csv, or for format=json a plain object without the usual success/data wrapper:
{
"job_id": "9f8f6d2e-4c1b-4f6e-9a3b-2d1e5c7a8b90",
"status": "completed",
"result_count": 2,
"results": [
{ "email": "john@example.com", "status": "valid", "sub_status": "mailbox_exists", "quality_score": 91 },
{ "email": "jane@example.com", "status": "valid", "sub_status": "mailbox_exists", "quality_score": 88 }
]
}More than 1,000 results are written to a file instead, and the endpoint answers with a JSON envelope whatever format you asked for. Fetch download_url (a pre-signed link, no API key needed) within expires_in_seconds to get the CSV or JSON file:
{
"download_url": "https://kawaa-uploads.s3.amazonaws.com/downloads/...",
"format": "csv",
"result_count": 4812,
"expires_in_seconds": 3600
}A client that always saves the body to a file will therefore save this envelope as results.csv on large jobs. Check the response Content-Type — text/csv is the file, application/json with a download_url is the envelope.
Example
# Small job: the body is the CSV
curl -X GET "https://api.kawaa.com/v1/jobs/9f8f6d2e-4c1b-4f6e-9a3b-2d1e5c7a8b90/download?format=csv&filter=valid" \
-H "X-Api-Key: YOUR_API_KEY" \
-o results.csv
# Any size: save the body, then follow the envelope if one came back
curl -s "https://api.kawaa.com/v1/jobs/9f8f6d2e-4c1b-4f6e-9a3b-2d1e5c7a8b90/download?format=csv&filter=valid" \
-H "X-Api-Key: YOUR_API_KEY" -o results.csv
if jq -e .download_url results.csv >/dev/null 2>&1; then
curl -s "$(jq -r .download_url results.csv)" -o results.csv
fiList All Jobs
/v1/jobsList all verification jobs for your account with pagination.
Query Parameters
limit- Number of results (default: 50, max: 100)offset- Opaque pagination cursor. Pass thenext_offsetvalue from the previous page; the response also includeshas_morestatus- Filter by job status
Response
{
"success": true,
"data": {
"jobs": [
{
"job_id": "9f8f6d2e-4c1b-4f6e-9a3b-2d1e5c7a8b90",
"filename": "emails.csv",
"total_emails": 1000,
"processed_emails": 1000,
"status": "completed",
"summary": { "valid": 800, "invalid": 100, "risky": 50, "unknown": 50 },
"created_at": "2026-02-03T14:30:00.000Z",
"completed_at": "2026-02-03T14:35:42.000Z"
},
{
"job_id": "063a7a5f-a27c-49ff-ab6b-a29bf3bb81b2",
"filename": null,
"total_emails": 1,
"processed_emails": 1,
"status": "completed",
"summary": { "valid": 0, "invalid": 1, "risky": 0, "unknown": 0 },
"created_at": "2026-02-03T14:20:17.310Z",
"completed_at": "2026-02-03T14:20:17.464Z"
}
],
"total": 1395,
"total_is_approximate": false,
"has_more": true,
"next_offset": "eyJ1c2VyX2lkIjoi..."
}
}filename is the uploaded file's name for file jobs and null for batch and single verifications (single verifications made through POST /v1/verify are listed here too, as one-email jobs). total counts every job matching the listing, not just the page returned; total_is_approximate: true means the count stopped at its scan bound, so total is a floor. When has_more is false, next_offset is null.
Credits
- 1 credit per email in batch
- Credits deducted upfront when job is created
- Emails that cannot be queued are refunded automatically; cached results are billed at 0.5 credits (the discount is reconciled on completion)
- Credits held for emails the workers never process are refunded by job reconciliation