# Batch checks

> POST /v1/check/batch checks up to 100 emails, domains or website URLs in one request. Each item counts toward your quota. Limits, response and retry advice.

Source: https://isbusinessemail.com/docs/batch-endpoint

```text
POST https://api.isbusinessemail.com/v1/check/batch
```

Checks up to **100** items in one request. Use it for imports, CRM clean-ups and back-filling verdicts on existing users. For a live signup form, use the single [`/v1/check`](https://isbusinessemail.com/docs/check-endpoint): it's faster for one address.

Secret keys only.

## Request

```json
{
  "items": [
    "jane@acme.io",
    "bob@gmail.com",
    "contoso.com"
  ],
  "policy": "b2b"
}
```

| Field | Required | Notes |
|---|---|---|
| `items` | yes | Array of 1–100 strings. Each is an email address (has `@`), a website URL (has `https://` or a path, like `acme.io/about`) or a bare domain. |
| `policy` | no | `b2b` (default), `strict` or `lenient`. Applies to every item. |

Limits:

- More than 100 items: `413 batch_too_large`. Split into chunks of 100.
- Request body over 256 KB: `413 payload_too_large`.
- **Every item counts** as one check toward your per-second and daily limits. Duplicates in the same batch are still counted, so de-duplicate first.

## Response

The response contains one result per item, **in the same order as `items`**. Each result has the same shape as a single [`/v1/check`](https://isbusinessemail.com/docs/response-fields) response.

```json
{
  "results": [
    { "email": "jane@acme.io", "domain": "acme.io", "category": "business", "recommendation": "allow", "…": "…" },
    { "email": "bob@gmail.com", "domain": "gmail.com", "category": "personal", "recommendation": "block", "…": "…" },
    { "email": null, "domain": "contoso.com", "category": "business", "recommendation": "allow", "…": "…" }
  ]
}
```

An item that isn't a valid address or domain doesn't fail the batch. It comes back with `category: "invalid"` and a reason such as `invalid_syntax`.

## Example: check a CSV column in chunks

```javascript
import { readFile } from 'node:fs/promises';

const emails = [...new Set(
  (await readFile('signups.csv', 'utf8'))
    .split('\n').slice(1)               // skip header
    .map((line) => line.split(',')[0]?.trim().toLowerCase())
    .filter(Boolean),
)];

const results = [];
for (let i = 0; i < emails.length; i += 100) {
  const res = await fetch('https://api.isbusinessemail.com/v1/check/batch', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.IBE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ items: emails.slice(i, i + 100) }),
  });
  if (res.status === 429) {
    const wait = Number(res.headers.get('Retry-After') ?? 1);
    await new Promise((r) => setTimeout(r, wait * 1000));
    i -= 100; // retry this chunk
    continue;
  }
  if (!res.ok) throw new Error(`batch failed: ${res.status}`);
  const { results: chunk } = await res.json();
  results.push(...chunk);
}

console.table(results.map((r) => ({ email: r.email, category: r.category, recommendation: r.recommendation })));
```

## Tips

- **Mind your daily quota.** A tier 0 account has 100 checks a day, which is one full batch. Need a one-off import above that? [Ask for higher limits](https://isbusinessemail.com/contact).
- **Respect `Retry-After`** on `429 rate_limited` and back off; `429 quota_exceeded` means the daily quota is used up until 00:00 UTC.
- **Prefer domains for CRM data.** If you only care about the company, de-duplicate to domains first. 10,000 contacts often collapse to a few hundred domains.
- No code? The [bulk CSV checker](https://isbusinessemail.com/tools/bulk-email-checker) does the same from a CSV upload.
