A batch request runs many inputs through one endpoint in a single HTTP call. Instead of 200 requests each paying for their own connection, authentication and round trip, you send one request carrying 200 sets of parameters and get 200 answers back in order.
POST /v1/<endpoint>/batch
It is per endpoint. There is no global batch route and no way to mix endpoints in one call —
/v1/emailvalidator/batch validates emails and nothing else. That is deliberate: the batch route
lives on the same endpoint as the single call, so it inherits the same routing, the same handler
and the same behaviour, and there is never a question of whether the batch version has drifted
from the real one.
The request
A JSON body with a requests array. Each element is the same parameter object you would send to
the endpoint on its own:
curl -X POST 'https://api.apiverve.com/v1/emailvalidator/batch' \
-H 'x-api-key: YOUR_API_KEY' \
-H 'content-type: application/json' \
-d '{
"requests": [
{ "email": "[email protected]" },
{ "email": "[email protected]" },
{ "email": "not-an-email" }
]
}'The parameters are the endpoint's own — whatever its reference page lists. A GET endpoint's query parameters and a POST endpoint's body fields are written the same way here, as keys on the object, because each item is handed to the handler with its parameters available in both places.
Up to 200 items per call. More than that is refused outright:
batch limited to 200 requests per call — split into chunks
Chunk larger jobs client-side. An empty array is legal and returns immediately with count: 0,
which is convenient for code paths where the list of things to look up occasionally turns out to
be empty.
The response
{
"status": "ok",
"count": 3,
"billed": 2,
"rateLimited": 0,
"results": [
{ "status": "ok", "error": null, "data": { "email": "[email protected]", "isValid": true } },
{ "status": "ok", "error": null, "data": { "email": "[email protected]", "isValid": true } },
{ "status": "error", "error": "email is not a valid email address", "data": null }
]
}| Field | |
|---|---|
count | How many items you sent |
billed | How many actually cost credits |
rateLimited | How many were turned away by this minute's rate window |
results | One entry per item, in the order you sent them |
Each entry in results is an ordinary response envelope — the same
status/error/data shape a single call returns. Whatever handles one response handles a batch
item unchanged.
A batch that ran comes back 200 even when every item inside it failed. The transport succeeded;
what happened to each lookup is in that item's own envelope. Never read the HTTP status as the
verdict on a batch — walk results and check each item's status.
A 4xx on a batch call means the batch itself was malformed: a missing or non-array requests,
more than 200 items, or an authentication failure that would have stopped a single call too.
Order is guaranteed. Items run concurrently inside the call, but results are written back into
their original positions, so results[3] always answers requests[3]. There is no need to thread
an id through and match on it afterwards — the index is the correlation.
What a batch costs
Each item is billed as if you had called it on its own, and the whole batch is settled once at the end. Batching saves you HTTP round trips, not credits.
Two things follow from that, and both are worth knowing before you build against it:
Failed items are not billed. An item rejected by validation — a malformed email, a missing
required parameter — costs nothing, exactly as it would on its own. That is why billed is
frequently lower than count, and it is the number to reconcile against rather than the item
count.
Endpoints that cost more than one credit still cost more here. The batch multiplies the endpoint's own cost by the number of items that ran, so the number of items you can afford is lower on a heavier endpoint. Each reference page states its cost.
The response carries x-api-remaining-credits reflecting the balance after the batch settles,
which is the cheapest way to keep a running meter without a second call.
Running out mid-batch
A batch bigger than your remaining balance does not fail. It runs as far as your credits go, and the items past that point come back individually refused:
{ "status": "error", "error": "insufficient credits", "code": 402, "data": null }Items are filled in the order you sent them, so the earliest ones win. Put the lookups you care about most at the front of the array if the list might exceed what you have left.
This is a deliberate choice: a 200-item batch that runs 187 lookups and tells you about the other
13 is more useful than one that refuses all 200 because of the last one. It also makes the failure
legible — count the 402s and you know exactly how short you were. See
rate limits for where the monthly allowance comes from.
Batching and rate limits
A batch is not a way around the per-minute rate limit, and it does not spend the whole limit either. Before it runs, it looks at how much room is left in this minute's window and takes exactly as many slots as it is going to use.
Three consequences:
A batch bigger than your rate limit partially runs. The items that fit run; the rest come back
as per-item 429s carrying the same message and retryAfter a single call would give you, and
the batch's rateLimited count says how many.
Credit-blocked items do not burn rate budget. Items already refused for credits never reach the window, so a batch that overruns your balance does not also cost you the minute's capacity.
The rate headers reflect the window afterwards. x-rate-limit-limit and
x-rate-limit-remaining come back on the batch response, with retry-after added when anything
was turned away — enough to pace the next chunk without guessing.
Batching also does not hide your volume: usage monitoring is fed the real per-item count, not one tick per batch. A batch of 200 counts as 200.
When batching is worth it
A column of values. The canonical case, and the one the spreadsheet extensions lean on: a thousand rows to validate becomes five calls rather than a thousand.
A nightly reconciliation. Re-checking a stored list — are these domains still resolving, are these addresses still valid — where the whole list is known up front and nothing is waiting on the answer.
An import. Enriching a freshly uploaded CSV before it lands in your database, where the user is already watching a progress bar and one call per row would be visibly slower.
Not worth it for a single lookup in a request path — the envelope is extra work for one answer. Not worth it either when the items are for different endpoints; that is several batches, one per endpoint, which you can run concurrently.
Practical shape
const CHUNK = 200;
async function validateAll(emails) {
const out = [];
for (let i = 0; i < emails.length; i += CHUNK) {
const requests = emails.slice(i, i + CHUNK).map((email) => ({ email }));
const res = await fetch('https://api.apiverve.com/v1/emailvalidator/batch', {
method: 'POST',
headers: {
'x-api-key': process.env.APIVERVE_API_KEY,
'content-type': 'application/json',
},
body: JSON.stringify({ requests }),
});
const body = await res.json();
if (body.status !== 'ok') throw new Error(body.error); // the batch itself failed
out.push(...body.results);
if (body.rateLimited > 0) await new Promise((r) => setTimeout(r, 60_000));
}
return out; // same length and order as `emails`
}Three things are doing the work. Chunking at 200 keeps every call inside the cap. Checking
the batch's own status before reading results separates a malformed call from failed items.
And pausing when rateLimited is non-zero, rather than retrying immediately, is what stops the
next chunk hitting the same wall — see error handling for the general shape.
Note what it does not do: it does not retry individual items. Decide that per endpoint — a 402
will not succeed on retry and a validation error will not either, so only a 429 or a 5xx is
worth sending again.
Limits worth knowing
200 items per call, enforced with a 400.
One endpoint per batch. No mixed batches.
Nothing streams. The response arrives whole, when the last item is done, so a large batch of a slow endpoint takes as long as its slowest items. Size the chunk to the endpoint's latency, not only to the cap.
Premium fields behave normally. Each item is gated against your plan exactly as a single call is, so a batch does not unlock fields your plan does not include.
Next
Rate limits covers the window batching has to live inside, and making requests covers the caching that often removes the need to batch at all. Error handling covers which of the per-item failures are worth retrying.