Overnight jobs — nightly document summarization, daily ticket classification, weekly report generation — trade latency you don't need for a 50% discount and simpler operations. This playbook covers Anthropic's Message Batches API, available on the first-party Claude API and Claude Platform on AWS. It is not available on Amazon Bedrock, Google Vertex AI, or Microsoft Foundry — but Bedrock offers its own S3-based batch inference and Vertex AI has BigQuery/GCS-based batch prediction at 50% batch pricing, so the playbook's structure transfers even where the mechanism differs. On Foundry there is no batch endpoint; run a rate-limited queue of real-time calls instead.
Step 1: Split the workload
A single batch holds up to 100,000 requests or 256 MB total, whichever comes first, and each request needs a unique custom_id (1–64 characters, letters/digits/underscore/hyphen). Make the custom_id deterministic from the work item — ticket-2026-07-07-18342 — because results return keyed by it, and a deterministic ID is what makes retries idempotent. Splitting is deterministic code: chunk the day's items, stay comfortably under both caps (dense PDF workloads hit the size cap long before the count cap), and record which items went into which batch. Mind the queue, too: the number of requests you can have in processing at once is tiered — 200,000 (Start), 300,000 (Build), 500,000 (Scale) — so a very large night's work may need staged submission.
Shared context — the same instructions and reference material in every request — belongs behind a prompt-cache breakpoint with the 1-hour TTL, since batch processing can easily outlive the default 5-minute cache window. A few request features are rejected inside batches: stream: true, max_tokens: 0 pre-warming, and fast mode among them; every request needs max_tokens of at least 1.
Step 2: Submit and track progress
Create the batch (POST /v1/messages/batches), store the returned batch ID next to your manifest, then poll GET /v1/messages/batches/{id} on a widening interval. processing_status moves from in_progress to ended; most batches finish in under an hour. There is no per-request progress signal, so treat the batch as the unit of tracking and size batches to match your reporting granularity — ten batches of 10,000 give you a coarse progress bar; one batch of 100,000 gives you a spinner. Batches are scoped to a workspace, and any API key in that workspace can view them, which is convenient for ops tooling and worth knowing for governance.
Step 3: Recover from failures
When the batch ends, fetch results_url — a .jsonl file streaming one result per line, in arbitrary order. Each line carries your custom_id and a result type, and the four types define your recovery matrix:
| Result type | Billed? | Action |
|---|---|---|
succeeded | Yes | Validate, then deliver |
errored | No | Inspect; fix and resubmit, or route to dead-letter queue |
expired | No | Resubmit in the next batch (batch hit the 24-hour limit) |
canceled | No | Resubmit if the cancel wasn't intentional |
The recovery loop is: diff the manifest against succeeded IDs, resubmit the remainder as a follow-up batch, repeat until the remainder is empty or consists of items that fail deterministically — those go to human review. Because only succeeded requests are billed, this loop costs nothing beyond the successful work. Validation still applies to successes: check stop_reason per result, since max_tokens truncation in a batch is silent until you look.
Step 4: Deliver results promptly
Results stay downloadable for 29 days after batch creation; after that the batch is viewable but results are gone. Do not treat the platform as your archive — the delivery stage writes parsed results to your own store the same night, then notifies downstream consumers. If a run must be stopped, cancellation flips the batch to canceling then ended, and partial results for already-processed requests may still be present in the results file — harvest them.
Where to go next
See Message Batches in depth, the batch discount economics, and batch vs realtime for deciding which workloads belong here.