# Batch sends

`POST /batches` sends up to 100 letters from your prepaid balance in one request, with a separate result for each letter. It needs the `letters:write` and `orders:send` scopes and an `Idempotency-Key` header, and works with live keys, OAuth connections and [test keys](/docs/test-mode.md).

## Build a batch

Each item has a `client_reference`, unique within the batch, and one of:

- a `template_id` with `variables`, plus a `recipient` (and optionally a `sender`) or their `_id` forms. See [Templates](/docs/templates.md).
- a `draft_id`. That saved draft is sent as it is. If the item also has a `recipient`, `sender` or `title`, a copy of the draft with those changes is sent instead, so one base draft can go to many recipients.

```json
{"items": [
  {"client_reference": "inv-1001", "template_id": "$template_id", "variables": {"customer": "Sample Recipient", "invoice": "INV-1001", "amount": "$120.00", "due_date": "November 30"}, "recipient": {"name": "Sample Recipient", "line1": "123 Example Avenue", "city": "Exampleville", "state": "CA", "postal_code": "90001", "country": "US"}},
  {"client_reference": "inv-1002", "template_id": "$template_id", "variables": {"customer": "Second Recipient", "invoice": "INV-1002", "amount": "$75.50", "due_date": "December 2"}, "recipient": {"name": "Second Recipient", "line1": "789 Sample Road", "city": "Exampleville", "state": "CA", "postal_code": "90002", "country": "US"}},
  {"client_reference": "inv-1003", "template_id": "$template_id", "variables": {"customer": "Third Recipient"}, "recipient": {"name": "Third Recipient", "line1": "1 Fixture Lane", "city": "Exampleville", "state": "CA", "postal_code": "90003", "country": "US"}}
], "content_policy_acknowledged": true}
```

## Read the result

Each item is drafted (or rendered), proofed, quoted and sent on its own. One item's failure never stops another.

```json
{
  "batch_id": "batch_…",
  "status": "completed",
  "environment": "live",
  "summary": {"requested": 3, "queued": 2, "failed": 1, "pending": 0, "debited_minor": 998},
  "items": [
    {"client_reference": "inv-1001", "status": "queued", "order_id": "order_…", "draft_id": "draft_…", "send_intent_id": "send_…", "debited_minor": 499},
    {"client_reference": "inv-1002", "status": "queued", "order_id": "order_…", "draft_id": "draft_…", "send_intent_id": "send_…", "debited_minor": 499},
    {"client_reference": "inv-1003", "status": "failed", "error": {"code": "VALIDATION_ERROR", "message": "Supply every template variable.", "retryable": false, "details": {"missing": ["amount", "due_date", "invoice"]}}}
  ]
}
```

`queued` means accepted and paid from your balance, not printed or mailed. Failed items carry the same `error` object as single requests, such as `INSUFFICIENT_FUNDS`, `VALIDATION_ERROR` or `ACTION_NEEDED`; see [Errors](/docs/errors.md).

`GET /batches/{batch_id}` (scope `orders:read`) returns the stored result.

## Retries and double-send protection

- **Same key, same body:** returns the stored result. If the earlier request was interrupted, it resumes the unfinished items. Nothing is sent twice.
- **Same key, different body:** `409 IDEMPOTENCY_CONFLICT`.
- **Still running:** a retry while the same batch is in progress gets a retryable `503`.

`client_reference` identifies one mailing permanently. To retry failed items, for example after adding credits, send a **new** batch key with the same items. Items whose `client_reference` already has an order come back as `queued` with `"already_sent": true` and the existing `order_id`. Earlier failures reuse their draft and are attempted again.

Duplicate `client_reference` values in one batch are rejected with `422`.

## Limits

Batches are processed synchronously. Each letter renders an exact print proof, so allow up to about a second per item and set a generous HTTP client timeout (several minutes for a full batch). The maximum is 100 items. A batch counts as one write against the account's rate limit.

Over MCP, use the `create_batch` and `get_batch` tools.
