Claude batch API: how the Message Batches API works, and when batch beats realtime
The Claude batch API (the Message Batches API) runs many Messages requests asynchronously at 50% of the standard price. You send up to 100,000 requests in one batch, each with a custom_id, and collect the results when the batch ends: most finish within an hour, but the only promise is 24 hours. Use it for work nobody is waiting on, and keep realtime calls for anything that blocks a person or the next step.
What the Claude batch API is
The Message Batches API takes a list of ordinary Messages API requests, processes them asynchronously, and returns one result per request. The docs pitch it as "well-suited to tasks that do not require immediate responses, with most batches finishing in less than 1 hour while reducing costs by 50%".
| Realtime (Messages API) | Batch (Message Batches API) | |
|---|---|---|
| Price | Standard | 50% of standard, every token |
| When you get the answer | In the response | When the batch ends: most within 1 hour, at most 24 hours |
| Latency promise | Answers while you wait | None beyond the 24-hour window |
| Streaming | Yes | No: results come back as one file |
| Rate limits | Messages API limits | Its own limits; batch usage doesn't affect Messages API limits |
How a batch works, step by step
- Create the batch with a list of requests. Each has a
custom_idand the sameparamsyou would send to the Messages API. - Poll the batch until its
processing_statuschanges fromin_progresstoended. - Read the results. Each line holds a
custom_idand a result of typesucceeded,errored,canceledorexpired.
```python import time import anthropic
client = anthropic.Anthropic()
batch = client.messages.batches.create( requests=[ { "custom_id": f"email-{email['id']}", "params": { "model": "claude-haiku-5-5", "max_tokens": 1024, "system": DAMAGE_REPORT_PROMPT, "messages": [{"role": "user", "content": email["text"]}], }, } for email in todays_emails ] )
while client.messages.batches.retrieve(batch.id).processing_status != "ended": time.sleep(60)
retry = [] for item in client.messages.batches.results(batch.id): if item.result.type == "succeeded": save_report(item.custom_id, item.result.message) else: retry.append(item.custom_id) # errored or expired: send again ```
Two details in that loop matter. Results "can be returned in any order", so custom_id is the only reliable way to match a result to its input: make it meaningful, such as the email or document ID. And only succeeded results are billed; errored, canceled and expired requests cost nothing, so collecting them for a second pass is cheap.
Limits to know
- Size: up to 100,000 requests or 256 MB per batch, whichever comes first. A batch that is too large gets a 413
request_too_largeerror. - Time: results are available "when all messages have completed or after 24 hours, whichever comes first". Requests still waiting after 24 hours come back as
expired. - Retention: results can be downloaded for 29 days after the batch was created.
- custom_id: 1 to 64 characters, letters, digits, hyphens and underscores, unique within the batch.
- No edits: a submitted batch can't be changed. Cancel it and submit a new one.
- Not allowed in a batch:
stream: true, fast mode'sspeedparameter, andmax_tokens: 0. - Scope: batches belong to a Console workspace, and every active model supports them.
What you can put in a batch
Almost any Messages request: vision, tool use, system prompts, multi-turn conversations, structured output, extended thinking and most beta features. Each request is processed independently, so one batch can mix request types.
Server tools such as web search, web fetch and code execution run inside the batch, with the same server-side loop as a realtime call. Client tools are different. When Claude asks for one of your tools, the result comes back with stop_reason: "tool_use" and the request is over. Your code runs the tool and sends a new request with the result. An agentic loop that waits on your own tools after every step is therefore realtime work, not batch work.
Batch vs realtime: decide from the deadline
The rule is one question: is anyone waiting for this answer?
| Realtime: something is waiting | Batch: nothing is waiting |
|---|---|
| A chat reply to a customer | Overnight reports |
| A check a person stands at the counter for | A weekly review of last week's cases |
| A tool call inside an agent loop | Large evaluations of a prompt or model |
| A check that blocks a merge | A backlog of documents to extract |
Take a bike-rental shop. The same damage-check prompt runs in two places. At the counter, a customer waits for their deposit while the agent checks the return photos: realtime. Overnight, the day's damage emails are classified for the morning team: batch, at half price.
The deadline arithmetic
When results must be ready by a promised time, plan with the worst case, not the typical one:
worst case = time between submissions + 24 hours, plus room to rerun anything that expires.
Say you promise customers a damage report within 36 hours of their email:
- Submit once a night: an email that arrives just after the run waits almost 24 hours for the next one, then up to 24 hours of processing. That's 48 hours, and the promise breaks.
- Every 12 hours: 12 + 24 = 36 hours. It just fits, with no room to rerun anything that expires.
- Every 8 hours: 8 + 24 = 32 hours, which leaves 4 hours to send expired requests through the realtime API.
The trap is planning with "most batches finish within an hour". That is typical, not promised. And if the deadline is under 24 hours, no schedule can guarantee it: use realtime.
Making batches cheaper and more reliable
- Try the prompt on a sample first. Run a few hundred real inputs and fix the prompt before you send 50,000. Every
erroredor wrong result is a resubmission. The docs add: "Dry run a single request shape with the Messages API to avoid validation errors." - Cache the shared prefix. Prompt caching works in batches, on a best-effort basis because requests run concurrently and in any order. Since batches can take longer than 5 minutes, the docs suggest the 1-hour cache duration. The cache and batch discounts stack; see Claude API pricing.
- Split very large jobs into several batches so each is easier to track and retry.
- Validate every result before you use it, as you would a realtime answer.
Batch processing on the Claude Certified Architect exam
Task 4.5 of the CCAR-F exam (Domain 4, prompt engineering and structured output) covers this directly:
- Fit: batch for non-blocking, latency-tolerant work and never for blocking work.
- No promise: there's no guaranteed latency.
- Tool calls: a batch request can't pause for your own tools and carry on: tool loops that wait on your code stay realtime.
- Frequency: how often to submit, worked out from the deadline.
- Sample first: refine the prompt on a sample before a large run.
The usual wrong answers are batching a step that someone waits on and trusting the typical run time.
More in all articles.
Sources: Batch processing, read on 21 October 2026; Pricing; Tool use overview; Claude Certified Architect Foundations exam guide v1.0, task statement 4.5.
FAQ
How much cheaper is the Claude batch API?
All batch usage is charged at 50% of the standard API prices, on input tokens, output tokens and special tokens alike. The discount stacks with prompt caching.
How long does a Claude message batch take?
Most batches finish within 1 hour, but the docs promise only 24 hours: results are available when every request has completed or after 24 hours, whichever comes first, and requests not processed by then expire.
Are batch results returned in order?
No. Results can come back in any order, so give every request a unique custom_id and match results on it.
Can I use tools in a batch request?
Yes. Tool use, vision, system prompts, multi-turn conversations and extended thinking all work, and server tools such as web search run inside the batch. A client tool call still needs your code to run it and send a follow-up request, so an agent loop that waits on your tools is a realtime job.