---
title: "Bulk email verification (up to 100 unique rows safe / 25 full)"
method: POST
path: "/verify/batch"
tags: ["verify"]
---

# Bulk email verification (up to 100 unique rows safe / 25 full)

`POST /verify/batch`

## Request

- Base URL: `https://dash.mailsocket.app/api/v1`
- URL: `https://dash.mailsocket.app/api/v1/verify/batch`
- Auth: HTTP bearer

Every email goes through the EXACT same verification path as
`POST /verify`; this is a charged, capped, deadline-bounded loop
over that path, NOT a separate/lighter verification. Bulk is NOT a
rate-limit bypass: each row consumes the standard per-key/per-account
`/verify` hourly budget (the SAME buckets single `/verify` draws
from; singles and batches share one budget).

Input: exactly one of `emails` (JSON array), `text` (a paste blob:
newline/comma/semicolon/whitespace-separated), or a multipart `file`
upload (`.csv`/`.txt`, max 64KB). Tokens are trimmed, quote-stripped,
capped at 320 chars, and exact-match deduplicated (order-preserving)
before verification; `N` (unique count) is what gets rate-charged,
metered, and returned as `results`. Safe mode caps at 100 unique
rows; full mode (not generally available) caps at 25. `N == 0` or
`N > cap` is a whole-batch `422`; a row that
merely fails verification (bad syntax, disposable, a CSV header
line) is never a batch-level error, only a per-row verdict.

Unlike single `/verify` (which consumes its rate-limit charge even
on the request that gets refused), a batch's charge is CONDITIONAL:
if the full `N`-sized charge does not fit the remaining hourly
budget, NOTHING is consumed and the whole batch returns `429`
(so retrying a large batch against a near-full budget never drains it).

The batch runs under a 60-second wall-clock deadline. Rows not
started before the deadline
return `deliverable: "unknown"`, `reason: "batch_budget_exhausted"`.
The rate charge and usage meter still count them (no refund race).

Full mode applies the same checks as single `/verify` full mode:
availability and plan once per batch (a 403 happens before any row
work), rate limits and per-domain cooldowns per row.

## Request body

- union: Exactly one of `emails` or `text` (both or neither -> `422 validation_error`). After tokenizing and de-duplicating there must be at least one address and at most 100 (safe) / 25 (full), else `422 validation_error` / `422 batch_too_large`.
  - object
    - `emails` string[], required: Mutually exclusive with `text`.
    - `text` string: A paste blob; tokenized on newline/comma/semicolon/ whitespace. Mutually exclusive with `emails`.
    - `mode` 'safe' | 'full'
  - object
    - `emails` string[]: Mutually exclusive with `text`.
    - `text` string, required: A paste blob; tokenized on newline/comma/semicolon/ whitespace. Mutually exclusive with `emails`.
    - `mode` 'safe' | 'full'

## Response `200`

Per-row results in input order (first-occurrence position for
deduplicated rows) plus a verdict summary. Each row is EXACTLY
the single-verify `data` object shape (including the `smtp`
sub-object in full mode); no new verdict vocabulary.

- object
  - `data` object, required
    - `mode` 'safe' | 'full', required
    - `results` object[], required
    - `summary` object, required
      - `total` integer, required
      - `deliverable` integer, required
      - `risky` integer, required
      - `undeliverable` integer, required
      - `unknown` integer, required

## Other responses

- `401`: Missing or invalid API key.
- `403`: Account forbidden, full-mode SMTP verify disabled (smtp_verify_disabled), or the plan lacks the full-mode entitlement (verify_full_entitlement_required); checked once per batch, before any row work.
- `422`: Structural batch problems only (never a per-row verdict): `validation_error` (both/neither of `emails`/`text` supplied, or zero unique rows), `batch_too_large` (over the mode's cap), `unsupported_file_type` (the uploaded filename does not end in `.csv` or `.txt`, e.g. `file.png`), `malformed_file` (not valid UTF-8, or contains a NUL byte), `file_too_large` (over 64KB, checked via Content-Length and re-checked against the actual parsed size).
- `429`: Rate limit exceeded.

---

[API](https://skmtc.dev/mailsocket/apis/mailsocket-api.md) · [All operations](https://skmtc.dev/mailsocket/apis/mailsocket-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mailsocket/apis/mailsocket-api/revisions/e579500dfb7e?raw)
