Message Batches API: limits, results and custom_id
The Message Batches API runs many Messages requests asynchronously at 50% of standard prices. Most batches finish within an hour, a batch expires if it is not done in 24 hours, and you match each result to its request by custom_id, because results can come back in any order.
Limits
A batch holds up to 100,000 requests or 256 MB, whichever comes first. Each request needs a custom_id of 1 to 64 letters, digits, hyphens or underscores, plus the usual Messages params. Those params are validated asynchronously, so a malformed field shows up as errors after the batch ends, not at creation. Results stay downloadable for 29 days. Batches are scoped to a workspace. Rate limits apply both to Batches API HTTP requests and to the number of batch requests waiting in the processing queue.
Almost any Messages request can be batched, including vision, tool use, server tools and extended thinking. stream: true, speed and max_tokens: 0 are rejected.
Status and results
A new batch has processing_statusin_progress. It becomes ended when every request has finished. Poll for ended; succeeded is a per-request result type, not a batch status.
Each result has one of four types. succeeded carries the message. errored means no message was created. canceled means you canceled before that request reached the model. expired means the 24-hour limit hit first. Only succeeded requests are billed.
Results may not match input order. Join each result back to its source record by custom_id, never by position.
To stop a batch, call the cancel endpoint. Its status reads canceling, then ended, and it may hold partial results for requests processed before the cancel.
Caching inside a batch
Batches often run longer than 5 minutes, so a shared prefix behind a default breakpoint can expire mid-run. Use cache_control: {"type": "ephemeral", "ttl": "1h"} on shared context for better hit rates. Cache hits in a batch are best-effort, because requests run concurrently and in any order.
Where the exam catches people
Believing results arrive in submission order: they can come back in any order, so join on custom_id.
Believing a finished batch reports succeeded: the batch status is ended, and succeeded belongs to each result.
Believing expired or errored requests are billed: neither is charged.
Believing the default 5-minute cache survives a long batch: use the 1-hour TTL for shared prefixes.