Message Batches Intro

Su Suriya Ravichandran Updated 10 Nov 2026
6 min read ·Lesson 131 of 200

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

AspectCurrent guidance
Size of one batchUp to 100,000 requests, or 256 MB, whichever limit is reached first
TimingMost batches finish within 1 hour; results can be retrieved when processing ends, or after 24 hours, whichever comes first
ExpiryA batch that does not finish within 24 hours expires
Results availableFor 29 days after creation
Price50% of standard input and output prices
Order of resultsNot guaranteed; match results using custom_id
FeaturesAlmost 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 fitPoor fit
Classifying or labeling large datasetsA chat reply a person is waiting for
Summarizing many documents overnightA form submission that needs an immediate response
Generating content for a catalog or courseA 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 contentVery 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

  1. Each Request has a custom_id, which you choose. It is how you will match the result to the input later (Lesson 133).
  2. params holds a normal Messages request, with the same fields you would use in a synchronous call.
  3. MessageCreateParamsNonStreaming makes explicit that the requests are not streamed, which is the only mode batches support.
  4. The response is a MessageBatch object. Its processing_status starts as in_progress, and its request_counts shows how many requests are still processing.
  5. The expires_at time 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_id values 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

  1. Run first_batch.py, then retrieve the batch by ID with client.messages.batches.retrieve(). How does its status look after a few minutes?
  2. Submit a batch with a custom_id that contains a space, such as "greeting tamil". What happens, and where would you expect the error to appear? (Lesson 132 explains the rule.)
  3. 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?
  4. Explain why the batch returns in_progress immediately rather than waiting for the results.
Show answers for steps 2 and 4

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.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Message Batches Intro and get answers drawn from it.

Signed-in readers only.