Docs/Reference/Batch requests

Batch requests

Run up to 200 lookups for one endpoint in a single call, with one authentication, one rate-limit charge and one billing settlement.

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.

code
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:

bash
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:

code
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

json
{
  "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
countHow many items you sent
billedHow many actually cost credits
rateLimitedHow many were turned away by this minute's rate window
resultsOne 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.

The HTTP status describes the batch, not the items

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:

json
{ "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

js
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.

Was this page helpful?

Last updated