Clean your email list
A bulk email verification API takes a list you already own, an export from your CRM, a webinar signup sheet, a years-old newsletter database, and returns a per-address verdict so you stop mailing dead ends. It exists for the moment before a send, not for scraping or buying lists.
The list you already have, cleaned before it costs you
Most email lists rot quietly. People change jobs, abandon inboxes, or typed their address wrong on a form three years ago, and none of that shows up until a campaign lands with a spike in bounces and a warning from your sending platform. This endpoint is built for that specific situation: you already own the addresses, you just need to know which ones are still worth mailing before your sender reputation absorbs the damage.
How a batch job runs
You submit an array of addresses to /verify/email-batch and immediately get back a task_id representing the whole job, not one per address. The task processes every entry through format, domain and deliverability signals internally, and when it finishes you receive one consolidated result set, either pushed to your webhook or available at a signed link valid for 24 hours, with a per-address status so you can filter the list programmatically instead of eyeballing a spreadsheet.
What a verdict actually tells you
Each address in the result comes back with a status such as deliverable, undeliverable, risky or unknown, reflecting real signals like domain existence and mailbox acceptance rather than a guess. Risky addresses, catch-all domains that accept everything without confirming a real mailbox, are flagged separately so you can decide whether to mail them cautiously or drop them, rather than lumping them in with clearly dead addresses.
Pricing that scales with the list, not with guesswork
Every address checked is billed at 0.002 dollars, the same per-unit rate as a single verification call, so a batch of ten thousand addresses costs exactly what ten thousand individual calls would, just without ten thousand round trips. A failed check within the batch is never charged, and the whole job retries automatically up to three times before surfacing a clear error, so partial failures do not silently corrupt your results.
Fitting it into a send pipeline
Run it as a scheduled job before every campaign, quarterly on a dormant list, or triggered whenever a CRM export changes, and pipe the deliverable segment straight into your sending tool while risky and undeliverable addresses get suppressed or routed to a re-engagement flow. Because results arrive asynchronously through a webhook, the batch can run in the background while the rest of your automation continues uninterrupted.
What you can do with it
Pre-campaign list scrub
Run an entire newsletter list through batch verification the night before a send so bounces stay low and sender reputation stays intact.
CRM database maintenance
Re-verify a customer database quarterly to catch addresses that went stale since the last import, without touching each contact by hand.
Post-migration cleanup
Validate every address after merging two contact databases from an acquisition, catching duplicates and dead mailboxes in one pass.
Event follow-up accuracy
Check a conference signup sheet in bulk before sending the follow-up sequence, so speaker slides and recap links actually land.
FAQ
How many emails can I submit in one batch?
The endpoint accepts an array of addresses per request; very large lists are processed as a single async job regardless of size, with results delivered once the full batch completes.
Is bulk email verification free?
There's no free tier — free tiers get abused and slow everyone down. Access runs on a prepaid ForHosting KIT balance: top up from $10.00 (it never expires) and each request is charged at its published price, so a call with no balance returns HTTP 402. No subscription, no tokens, no invented credits, and a failed task is never charged.
What does it cost to verify a list?
Each address costs 0.002 dollars, the same per-unit price as a single check, so a list of any size costs exactly its per-address total.
How do I get results for a large list?
You receive a task_id right away, and the full result set for every address arrives via your signed webhook or a signed link valid for 24 hours.
What is a risky or catch-all result?
It means the domain accepts mail for any address without confirming a specific mailbox exists, so deliverability cannot be fully guaranteed and you should treat it as a caution flag, not a hard bounce.
Can I upload a purchased or scraped list?
The endpoint verifies lists you already own and have a legitimate relationship with; it is a hygiene tool for your own data, not a discovery or acquisition service.
Am I charged for addresses that fail to process?
No, failed checks within a batch are retried automatically up to three times and are never charged if they ultimately fail.
How is this different from a single email check?
The underlying verification logic is the same; batch processing simply lets you submit many addresses in one job and get one consolidated result set instead of managing individual calls.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/verify/email-batch \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"items":["valor-1","valor-2"]}'const res = await fetch("https://api.kit.forhosting.com/verify/email-batch", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"items": [
"valor-1",
"valor-2"
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/verify/email-batch",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"items": [
"valor-1",
"valor-2"
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/verify/email-batch", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"items":["valor-1","valor-2"]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"items":["valor-1","valor-2"]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/verify/email-batch", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"items": [
"valor-1",
"valor-2"
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "verify.email_batch",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |