Email verification
Email Validation API Workflow: From Sandbox to Production with FairValid
A practical FairValid email validation API workflow: test in the free sandbox, switch to a scoped Live key, submit with idempotency keys, page through results, handle errors by cause, and reconcile credit holds.

Email Validation API Workflow: From Sandbox Test to Production Results
Question: You're a developer adding email verification to your own system. What steps take you from a first test call to dependable production results?
Short answer: Start with a Test key and the free sandbox. Then switch to a Live key that has only the scopes you need. Send every submission with an idempotency key. Read the job state and page through the results. Handle each outcome and each error code separately. The steps below follow the FairValid customer API reference.
Step 1: Build against the sandbox first
Create a Test key on the API keys page and keep it on your server. The reference uses two environment variables, FAIRVALID_API_KEY and FAIRVALID_BASE_URL. Test keys make no provider call and use no credits.
Test keys accept only a small set of reserved addresses on the example.com domain. Those addresses simulate valid, invalid, catch-all, unknown, pending and unavailable outcomes. You'll find the exact addresses in the "Free sandbox" section of the API reference. Test jobs return fixed, repeatable states: completed, pending, failed or cancelled. Bulk test jobs support status, results and cancellation.
The sandbox shows you those specific outcomes. It doesn't cover every path your integration can take, so plan separate handling for the error codes described in Step 7.
Bulk test jobs are limited to 20 new jobs per workspace per hour, or 100 per 30 days. Going over either limit returns HTTP 429. An identical retry still returns the original job.
Step 2: Move to a Live key with the scopes you need
For production, create a Live key with submit scope. Use POST /api/v1/jobs for batches or POST /api/v1/verifications for a single address. Add read scope if the key also needs to read job state and results. Live keys can also have a balance scope. Separately, GET /api/v1/balance reads the workspace's available and held credits. The full secret is shown only once, so store it securely when you create the key.
Live jobs use your workspace's verification queue and credit balance. Key scopes, key caps, workspace access and processing limits all still apply.

Step 3: Submit a job with an idempotency key
A job accepts 1 to 5,000 addresses in JSON, up to 2 MiB. Send an Idempotency-Key header with every job submission and every live single-address submission. The key must be 8 to 128 characters long and can use letters, digits, dots, colons, underscores or hyphens.
- A new job returns HTTP 202 with a UUID
job_id. - A matching retry returns HTTP 200 with the same job.
- Reusing an idempotency key with a changed body returns HTTP 409 idempotency_conflict.
If you can't tell whether a request went through, retry with the same API key, the same idempotency key and the same normalized body. Replay is scoped to the API key, so replacing that credential and resubmitting can create a second run. Use a new idempotency key only when you actually want a fresh run, which may be charged.
Step 4: Read the job and page through results
Read the job with GET /api/v1/jobs/{id}. Its status can be queued, running, paused, canceling, canceled or completed. Read results with GET /api/v1/jobs/{id}/results. Each page returns up to 50 results plus a signed next_cursor. Pass that value as the cursor query parameter. It keeps your original filter and search.
The filters are all, valid, invalid, catch_all, unknown, still_checking and not_checked. To stop a job, POST /api/v1/jobs/{id}/cancel requests cancellation, but work already in progress may still finish.
The reference also describes a signed completion event and links to webhook management.
Step 5: Treat each outcome as its own state
The results guide explains what each result means:
- Valid: a usable positive check. It never guarantees inbox delivery.
- Invalid: the address did not pass.
- Catch-all: the domain may accept many addresses, so a mailbox-level conclusion may be less certain. Read the detail before you use it.
- Unknown: the check could not return a dependable answer.
- Still checking: there is no final answer yet.
The guide describes technical failures and unprocessed items separately from Unknown, and the API offers unknown, still_checking and not_checked as separate filters. Keep these states in separate fields or statuses in your own data rather than merging them into Unknown.
Step 6: Understand credit holds before you reconcile
The API reference says workspace credits are reserved as checks are admitted for processing. Results then follow the same charging and return rules as the dashboard. FairValid pricing says a credit hold keeps room for a running check, and the final result determines the charge:
- Valid or invalid: 1 credit per completed result.
- Unknown or catch-all: no net charge. Held credits are released or returned.
- Still checking: no final charge yet. A hold is not a completed charge.
- Technical failures and unprocessed work: no net charge. Reservations are released or returned.
If you check your balance partway through a job, remember that held credits aren't final charges yet. Reconcile after results are final. Every account includes 300 free credits a month. They're used first and don't roll over. Paid credits carry forward while the account stays open.

Step 7: Handle errors by cause, not with one retry rule
- 401: the key is missing, revoked or lacks the needed scope. Fix the credential or its scopes.
- 402 key_cap_reached: the new request would go over the key's lifetime cap, and no job or hold was created. The cap counts credits already charged plus credits reserved for accepted unique identities. Because it's a lifetime limit, it doesn't refill over time, and waiting won't clear it. Have someone with access review that key's cap setting. A cap can't add credits to the workspace.
- 422: invalid addresses or list limits. Correct the input.
- 503 live_verification_unavailable: during an operational pause, a new live submission can return this without holding credits. Retry later with the same idempotency key. A 503 after an uncertain submission doesn't prove that no job was created, so reuse the original key.
Every error includes a code, a message and a request ID. Log all three so you have them if you contact support.
A sensible order of work
- Build the integration against the sandbox and confirm each simulated outcome ends up where you expect.
- Add explicit handling for the error codes above.
- Create a Live key with the narrowest scopes you need and a cap that fits your budget.
- Start with a small live job. Compare its results and credit states with the dashboard, then scale up.
If you'd like to try this workflow, the free sandbox lets you test the simulated outcomes without using any credits. The API reference has the exact request details.


