Certopolis

Claude Certified Architect – Foundations

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_status in_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.

Written from Anthropic's documentation

Try 3 questions on this

Pick an answer to see why each option is right or wrong.

  1. You submit a batch job, and your worker process must wait until every request in it has finished before downloading results. Your worker breaks out of that wait when processing_status reads succeeded. Every request has finished, but the download step after the wait has not run. What is the most likely root cause?

    Right. In the game that’s Renown for your Context Management and Reliability building.

    Not that one: D is right. In the game your Context Management and Reliability building would lose a little Renown, and this question would come back later.

    Show the full explanation and doc quotes

    A worker comparing processing_status against a per-result value is comparing against a string the batch object never emits, so the loop's exit condition cannot become true however long it waits.

    Written from platform.claude.com/docs/en/build-with-claude/batch-processing

  2. You submit a Message Batch of 50,000 requests, and POST /v1/messages/batches returns 200 with processing_status: "in_progress". Every entry carries the fields your builder wrote into it, so a single malformed field in that builder's template would repeat across every entry. Your team wants such a mistake surfaced as early as it can be, and nobody is sure at what point an entry's shape is checked. Which approach should you take?

    Right. In the game that’s Renown for your Context Management and Reliability building.

    Not that one: A is right. In the game your Context Management and Reliability building would lose a little Renown, and this question would come back later.

    Show the full explanation and doc quotes

    `. The documented way to see a bad request shape immediately is to send it to the Messages API on its own, where validation is synchronous, before committing copies of it to a batch.

    Written from platform.claude.com/docs/en/build-with-claude/batch-processing

  3. Your nightly classification job submits 40,000 requests through the Message Batches API and writes each result to the database row at the same index as the request it submitted. Spot checks show classifications attached to the wrong records, and a subset of rows carry no result at all even though the batch reached processing_status of "ended". What change would most effectively resolve this issue?

    Right. In the game that’s Renown for your Context Management and Reliability building.

    Not that one: C is right. In the game your Context Management and Reliability building would lose a little Renown, and this question would come back later.

    Show the full explanation and doc quotes

    Rows with no result correspond to entries whose result.type is errored, canceled or expired, which the loop must branch on rather than assume success.

    Written from platform.claude.com/docs/en/build-with-claude/batch-processing