Message Batches Intro
Asking for Many Answers Without Waiting for Each One
Every request so far has been synchronous: you send it, wait, and receive the answer. That suits chat, forms, and interactive tools. Many jobs do not need that. Classifying 50,000 product reviews, summarizing a year of support tickets, or generating descriptions for a catalog can wait a few hours without anyone noticing.
The Message Batches API is designed for that kind of work. You submit a list of Messages requests in one call. The API processes them asynchronously, and you collect the results later. Most batches complete within an hour, and every batch finishes within 24 hours.
synchronous: request → wait → answer (one at a time, immediate)
batch: submit 10,000 requests → … → results file (processed in the background)
Batching has one large advantage: the price. According to the batch processing documentation, all batch usage is charged at 50% of standard prices.
Key Facts
| Aspect | Current guidance |
|---|---|
| Size of one batch | Up to 100,000 requests, or 256 MB, whichever limit is reached first |
| Timing | Most batches finish within 1 hour; results can be retrieved when processing ends, or after 24 hours, whichever comes first |
| Expiry | A batch that does not finish within 24 hours expires |
| Results available | For 29 days after creation |
| Price | 50% of standard input and output prices |
| Order of results | Not guaranteed; match results using custom_id |
| Features | Almost everything the Messages API supports, including vision, tools, server tools, and extended thinking |
Some parameters are not supported in batches. Streaming (stream: true) is the most important: batch results arrive as a file, not a stream. Fast mode, server-side fallbacks, and max_tokens: 0 (the cache pre-warm request from Lesson 130) are also excluded. Including any of them returns a validation error.
Because processing is asynchronous, the batch's own validation of each request's content is also asynchronous. Errors appear when the batch ends, not when you submit it. That is why Lesson 132 checks requests locally first.
Which Work Fits a Batch
| Good fit | Poor fit |
|---|---|
| Classifying or labeling large datasets | A chat reply a person is waiting for |
| Summarizing many documents overnight | A form submission that needs an immediate response |
| Generating content for a catalog or course | A step in an agent loop, where each step depends on the previous answer |
| Evaluations over large test sets (Lesson 39) | Tool-using workflows that need results before continuing |
| Moderation and quality checks on stored content | Very small jobs, where the overhead of tracking is not worth the saving |
The test is simple: can the work proceed without this answer right now? If yes, a batch probably saves money. If no, use synchronous requests.
Practical Example: Your First Batch
This example submits two small requests as one batch, and prints the batch's initial state. It uses the typed request classes from the documentation.
"""first_batch.py: submit two requests as one batch and print its initial state."""
from anthropic import Anthropic
from anthropic.types.message_create_params import MessageCreateParamsNonStreaming
from anthropic.types.messages.batch_create_params import Request
client = Anthropic()
batch = client.messages.batches.create(
requests=[
Request(
custom_id="greeting-tamil",
params=MessageCreateParamsNonStreaming(
model="claude-opus-5-5",
max_tokens=200,
messages=[{"role": "user", "content": "Say hello in Tamil."}],
),
),
Request(
custom_id="greeting-hindi",
params=MessageCreateParamsNonStreaming(
model="claude-opus-5-5",
max_tokens=200,
messages=[{"role": "user", "content": "Say hello in Hindi."}],
),
),
]
)
print("id:", batch.id)
print("status:", batch.processing_status)
print("still processing:", batch.request_counts.processing)
print("expires:", batch.expires_at)
Expected output (the ID and times vary):
id: msgbatch_01...
status: in_progress
still processing: 2
expires: 2026-11-10T...
How It Works
- Each
Requesthas acustom_id, which you choose. It is how you will match the result to the input later (Lesson 133). paramsholds a normal Messages request, with the same fields you would use in a synchronous call.MessageCreateParamsNonStreamingmakes explicit that the requests are not streamed, which is the only mode batches support.- The response is a
MessageBatchobject. Itsprocessing_statusstarts asin_progress, and itsrequest_countsshows how many requests are still processing. - The
expires_attime is 24 hours after creation. Results should be collected well before then.
The batch now runs in the background. The script can exit, because the batch is stored by the API, and the batch ID is all you need to come back later.
Common Mistakes
Treating a Batch Like a Synchronous Call
What happens: code expects results immediately, and crashes or reports nothing.
Fix: design the workflow as submit, track, and collect, with separate steps.
Using Streaming in a Batch
What happens: a validation error, because batches return files, not streams.
Fix: use non-streaming request types, as the example does.
Assuming Results Arrive in Order
What happens: results are paired with the wrong inputs.
Fix: always match on custom_id.
Discovering Errors Late
What happens: a malformed request is found only after the batch has finished, which can be hours later.
Fix: validate request shapes locally, and test a few requests with the synchronous API first (Lesson 132).
Forgetting Results Expire
What happens: results cannot be retrieved after 29 days, and the batch cannot be rerun from its stored results.
Fix: download and store results promptly, and keep your own copy.
Best Practices
- Use batches for work that can wait.
- Validate request shapes locally, and dry-run a few with the synchronous API.
- Choose meaningful
custom_idvalues from the start. - Keep the batch ID in your own database, with its purpose.
- Collect results well before the 24-hour expiry, and store them yourself.
- Compare the saving with the waiting cost before choosing batches for a feature.
Exercise
- Run
first_batch.py, then retrieve the batch by ID withclient.messages.batches.retrieve(). How does its status look after a few minutes? - Submit a batch with a
custom_idthat contains a space, such as"greeting tamil". What happens, and where would you expect the error to appear? (Lesson 132 explains the rule.) - Compare the cost of 1,000 short requests sent synchronously and sent as a batch, using the prices in the pricing table, and assume the same token counts. What is the saving?
- Explain why the batch returns
in_progressimmediately rather than waiting for the results.
Step 2: a custom_id must match ^[a-zA-Z0-9_-]{1,64}$, so a space is not allowed. The documentation describes this as a request shape problem, and such problems can be reported when the batch is processed. Checking the rule locally before submitting (as Lesson 132 does) avoids waiting for the batch to discover it.
Step 4: a batch can contain up to 100,000 requests, and processing them takes time. Returning immediately lets the caller continue with other work, and the batch is stored by the API, so the caller can check back later with the batch ID. Waiting inside the submission call would tie up a connection for up to an hour and risk timeouts.