Message Batches for bulk jobs

Claude API · Advanced · 12 min

For work that doesn't need an instant answer, batches cut cost in half and avoid rate-limit juggling.

Some jobs do not need answers right away: tagging 50,000 product descriptions overnight, or running a test set against a new prompt. The Message Batches API takes many Messages requests in one submission, processes them in the background, and charges about 50 percent of the normal price.

How it works:

  • Build a list of requests. Each has a custom_id (your own label, letters, digits, _ and -) and params, which are exactly the arguments you would pass to messages.create.
  • Submit with client.messages.batches.create(requests=[...]). You get a batch id like msgbatch_....
  • Poll client.messages.batches.retrieve(id) until processing_status is ended. Most batches finish within an hour; anything not done in 24 hours expires.
  • Read client.messages.batches.results(id). Each result has the custom_id and a result whose type is succeeded, errored, canceled or expired.

Results are not in the order you sent them, so always match by custom_id. Only succeeded requests are billed. Errored ones split into invalid requests (fix them) and server errors (resend). Results can be downloaded for 29 days.

A batch can hold up to 100,000 requests or 256 MB. Each request is validated only when it runs, so test the request shape with a normal call first. Batches combine well with prompt caching for shared prefixes. Streaming is not available inside batches.

Example

import time
import anthropic

client = anthropic.Anthropic()
reviews = {"r1": "Loved it!", "r2": "Broke after a day.", "r3": "It's fine."}

batch = client.messages.batches.create(requests=[
    {"custom_id": rid, "params": {
        "model": "claude-haiku-5-5", "max_tokens": 5,
        "messages": [{"role": "user", "content":
            f"Sentiment (positive/negative/neutral), one word: {text}"}]}}
    for rid, text in reviews.items()
])

while client.messages.batches.retrieve(batch.id).processing_status != "ended":
    time.sleep(60)

for item in client.messages.batches.results(batch.id):
    if item.result.type == "succeeded":
        print(item.custom_id, item.result.message.content[0].text)
    else:
        print(item.custom_id, "->", item.result.type)

Remember

  • Batches: about half price, results within 24 hours
  • Each request = custom_id + normal create params
  • Match results by custom_id; order is not preserved

Watch out for

  • Submitting 100,000 requests with an untested request shape and getting 100,000 errors.

Check yourself

  1. How do you know which result belongs to which input?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: platform.claude.com/docs/en/build-with-claude/batch-processing