Email verification
Bulk Email Verification API: A Practical Batch Job Workflow
Bulk email verification API workflow for FairValid: test in the sandbox, submit jobs of up to 5,000 addresses, handle 402 and 503 errors, and page results.

Bulk Email Verification API: A Practical Batch Job Workflow
The question: You have a large list of addresses in your own system. How do you send it through the FairValid API in batches, handle errors safely and read the results back without losing track of credits?
Short answer: Start in the sandbox with a Test key. Then split the list into jobs of up to 5,000 addresses and give each job its own idempotency key. Submit each job with a Live key, check its status and page through the results by outcome. Keep pending checks, technical failures and Unknown results as three separate situations.
Evidence checked November 13, 2026, against FairValid's API reference, results guide and credit rules.
Step 1: Build against the sandbox first
The API reference says to create a Test key under API keys, keep it on your server, and set it in your environment along with the base URL. Test keys work with reserved addresses on the example.com domain. They make no provider calls and use no credits. Test keys access their own simulated jobs.
Test jobs return deterministic states: completed, pending, failed or cancelled. Check the API reference for the exact sandbox addresses, and use them to confirm that your code handles these states. These cases show only the outcomes they were designed for. They don't cover every branch of your integration, so review your own error paths separately.
Step 2: Move to a Live key with the right scopes
For production, the API reference says you need a Live key with submit scope to create jobs. Add read scope to read job state and results. Live keys can also have a balance scope. Full key secrets appear only once, so store them securely on your server when you create them. A revoked key stops new requests.
Live jobs use your workspace's verification queue and credit balance. Key scopes, key caps, workspace access and processing limits still apply. A Live key doesn't give you unlimited access.

Step 3: Split the list and submit jobs
A single job accepts 1 to 5,000 addresses in JSON, up to 2 MiB. Here's one practical approach:
- Chunk your list. As an illustration, 12,000 addresses would become three jobs of 5,000, 5,000 and 2,000.
- Give each chunk its own stable Idempotency-Key, and record which of your rows went into which chunk. Keeping that mapping on your side makes it easier to join results back to your records.
- Submit to the jobs endpoint. A new job returns HTTP 202 with a UUID job_id. A matching retry returns HTTP 200 with the same job. That means you can resend the same request with the same key after a timeout and get the existing job back.
Step 4: Handle the two errors that look alike
Both of these responses can block a submission, but they mean different things:
- HTTP 503 live_verification_unavailable can come back during an operational pause. According to the API reference, this happens without holding credits. Retry later with the same Idempotency-Key.
- HTTP 402 key_cap_reached means a new request would go over the key's lifetime spending cap. That cap counts credits already charged plus credits reserved for accepted unique identities. No job or hold is created. A lifetime cap doesn't refill over time, so waiting won't fix it. Review the key's cap in API key settings, or use a key with enough remaining cap for the work. A key cap also can't add credits to the workspace. It only limits what that key can spend.
Log these two responses separately from permission errors and processing limits, so the right person gets the right alert.
Step 5: Track job state, then page the results
The job endpoint returns a job's current state and summary. Job status can be queued, running, paused, canceling, canceled or completed. If you request cancellation, work that's already in flight may still finish.
The results endpoint returns up to 50 results at a time, along with a signed next_cursor. Pass that value as the cursor query parameter. It carries over your original filter and search. The filters are all, valid, invalid, catch_all, unknown, still_checking and not_checked. You can use them to pull only the rows that need a decision, such as everything marked unknown.
Step 6: Turn outcomes into decisions
The results guide describes results as evidence, not a delivery guarantee:
- Valid indicates a usable positive check. It never guarantees inbox delivery.
- Invalid means the address didn't pass.
- Catch-all means the domain may accept many addresses, so a conclusion about the specific mailbox may be less certain. Read the detail before you use the address.
- Unknown means the check couldn't return a dependable answer.
- Still checking means there's no final answer yet. Keep polling, and don't treat it as Unknown.
Technical failures and unprocessed items are a separate category. FairValid's credit rules say they aren't relabeled as Unknown.

Step 7: Understand credit reservations
The API reference says workspace credits are reserved as checks are admitted for processing. Results follow the same charging and return rules as the dashboard. Under the credit rules, a completed Valid or Invalid result costs one credit. Unknown and unresolved Catch-all results have zero net charge, because held credits are released or returned. Pending isn't a terminal result. Technical failures and unprocessed work also have no net charge. Returned credits keep the lot and expiry rules they started with.
The balance endpoint reports available and held credits for the live workspace. When its free_allowance_held field is true, the free allowance is on hold and isn't counted in available_credits or free_available. Paid credits stay available. The original account receives 300 free credits each month on its signup date, and unused free credits don't roll over. Additional workspaces don't get an extra free allowance. Free credits are used first, and paid credits don't expire while the account stays open.
A simple checklist
- Test with a Test key and the documented sandbox cases.
- Use a Live key with submit and read scopes, and a cap that fits the planned work.
- Keep each chunk to 5,000 addresses or fewer, with one stable idempotency key per chunk.
- Retry a 503 later with the same key. Don't wait out a 402; deal with the cap instead.
- Page through results with the signed cursor, filtered by outcome.
- Keep pending, technical failure and Unknown separate in your data.
If you'd like to try this workflow, you can create a Test key and run a sandbox job without using credits before your first live submission. The API reference has the full endpoint details.


