Message Batches API and Files API
Two auxiliary APIs that are nearly always worth using for the right workloads. The Message Batches API runs requests asynchronously at a flat 50 % discount on both input and output — perfect for evals, content moderation, bulk content generation, large-scale data labeling. The Files API uploads images, PDFs, and other binaries once and lets you reference them by file_id in Messages requests — saves request payload size dramatically when you reuse the same file across many calls.
See also
- claude-api-and-sdks — synchronous Messages API counterpart
- claude-models-and-capabilities — batch pricing per model
- vision-and-multimodal — Files API is the recommended way to attach images / PDFs
- prompt-caching — pairs with 1-hour TTL for cross-request batches
1. Message Batches API
Per platform.claude.com/docs/en/build-with-claude/batch-processing.
1.1 What it is
Submit a list of up to 100,000 Message requests in one POST. Anthropic processes them asynchronously and returns results as a JSONL file. Most batches complete in under 1 hour; hard SLA is 24 hours (anything not done by then expires).
50 % discount on all token usage (input, output, cache reads, cache writes, server-tool surcharges). Counts against your workspace’s spend limits and may slightly overshoot (concurrent processing).
1.2 Endpoint family
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/messages/batches | Create a batch |
| GET | /v1/messages/batches/{id} | Poll status |
| GET | /v1/messages/batches/{id}/results | Download results (JSONL stream) |
| POST | /v1/messages/batches/{id}/cancel | Cancel in-progress batch |
| GET | /v1/messages/batches | List batches |
1.3 Limits
- 100,000 requests OR 256 MB total request size per batch, whichever hits first
- Results retained for 29 days after batch creation
- 24-hour processing deadline; requests not finished by then expire
- Each batched request needs
max_tokens >= 1—max_tokens: 0(cache pre-warming) is not supported in batches (per docs: “an ephemeral cache entry written during batch processing would likely expire before the follow-up request runs”) - Batches scoped to a workspace
- Rate limits apply per
platform.claude.com/docs/en/api/rate-limits#message-batches-api
1.4 Supported features
Everything: vision, tool use, system messages, multi-turn conversations, beta features, prompt caching, citations. Each request in a batch is processed independently — you can mix request types, models, parameters freely.
1.5 Pricing (per MTok, 50 % off synchronous rates)
| Model | Batch input | Batch output |
|---|---|---|
| Opus 4.7 | $2.50 | $12.50 |
| Opus 4.6 | $2.50 | $12.50 |
| Opus 4.5 | $2.50 | $12.50 |
| Opus 4.1 | $7.50 | $37.50 |
| Sonnet 4.6 | $1.50 | $7.50 |
| Sonnet 4.5 | $1.50 | $7.50 |
| Haiku 4.5 | $0.50 | $2.50 |
| Haiku 3.5 (Bedrock / Vertex) | $0.40 | $2.00 |
1.6 Create a batch
import anthropic
from anthropic.types.message_create_params import MessageCreateParamsNonStreaming
from anthropic.types.messages.batch_create_params import Request
client = anthropic.Anthropic()
batch = client.messages.batches.create(
requests=[
Request(
custom_id="my-first-request",
params=MessageCreateParamsNonStreaming(
model="claude-opus-4-7",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, world"}],
),
),
Request(
custom_id="my-second-request",
params=MessageCreateParamsNonStreaming(
model="claude-haiku-4-5",
max_tokens=512,
messages=[{"role": "user", "content": "Hi again"}],
),
),
]
)
print(batch.id, batch.processing_status) # batch_01..., "in_progress"custom_id is your ID for matching results back. Must match ^[a-zA-Z0-9_-]{1,64}$.
1.7 Poll status
status = client.messages.batches.retrieve(batch.id)
print(status.processing_status) # "in_progress", "canceling", "ended"
print(status.request_counts)
# RequestCounts(processing=42, succeeded=8, errored=0, canceled=0, expired=0)
print(status.created_at, status.ended_at, status.expires_at)1.8 Retrieve results (JSONL stream)
# Stream results — one JSONL line per request
for result in client.messages.batches.results(batch.id):
print(result.custom_id, result.result.type)
if result.result.type == "succeeded":
msg = result.result.message
print(msg.content[0].text)
elif result.result.type == "errored":
print("error:", result.result.error)
elif result.result.type == "expired":
print("expired (24h deadline)")
elif result.result.type == "canceled":
print("canceled")Result shape per JSONL line:
{
"custom_id": "my-first-request",
"result": {
"type": "succeeded",
"message": { /* Standard Message object */ }
}
}1.9 Cancel
client.messages.batches.cancel(batch.id)
# Status moves to "canceling" then "ended"; in-flight results may still be returned1.10 Extended output (300k tokens)
Beta: output-300k-2026-03-24. Bumps max_tokens ceiling to 300k for Opus 4.7 / 4.6 / Sonnet 4.6 in batches only.
1.11 Best practices
- Use 1-hour cache TTL (
extended-cache-ttl-2025-04-11) for batches with shared context — batches often exceed 5 minutes, so the 5m TTL is wasted. - Pre-sort by model if mixing — single-model batches schedule faster (anecdotal, observed behavior).
- Custom IDs should encode whatever you need for join — request hash, input row ID, eval case name.
- Plan for partial expiration under high demand — retry just the expired subset.
- Combine with the Console batch viewer at
/console/batchesto monitor in-flight progress.
2. Files API
Per platform.claude.com/docs/en/build-with-claude/files. Beta header: files-api-2025-04-14.
2.1 What it is
Upload binaries (images, PDFs, other) once, get a file_id, reference it in subsequent Messages requests. Two main wins:
- Request payload size: a 30 MB PDF as base64 in the request body bumps payload near the 32 MB limit. Files API references add ~50 bytes.
- Reuse: upload a 50-page contract once, query it across 100 conversations.
2.2 Endpoints
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/files | Upload |
| GET | /v1/files | List |
| GET | /v1/files/{id} | Get metadata |
| GET | /v1/files/{id}/content | Download |
| DELETE | /v1/files/{id} | Delete |
2.3 Upload
uploaded = client.beta.files.upload(
file=open("report.pdf", "rb"),
purpose="vision", # also: "code_execution", "fine_tune"
)
print(uploaded.id, uploaded.size_bytes, uploaded.created_at)
# file_011CNvxoj286tYUAZFiZMf1U, 1234567, 2026-05-25T...purpose is documented but currently primarily used for routing — "vision" for image/PDF use in Messages, "code_execution" for files Claude reads/writes in the code-execution sandbox.
2.4 Reference in a message
{
"role": "user",
"content": [
{
"type": "image",
"source": {"type": "file", "file_id": uploaded.id}
},
{"type": "text", "text": "What's in this image?"},
],
}Same for document blocks:
{
"type": "document",
"source": {"type": "file", "file_id": uploaded.id},
"citations": {"enabled": True},
}2.5 List / get / delete
files = client.beta.files.list(limit=20)
for f in files.data:
print(f.id, f.filename, f.size_bytes, f.created_at)
meta = client.beta.files.retrieve(uploaded.id)
content_bytes = client.beta.files.download(uploaded.id) # bytes
client.beta.files.delete(uploaded.id)2.6 Limits
- Max file size: 32 MB (matches synchronous request body limit)
- Retention: 30 days standard
- Storage cost: free up to a threshold per workspace, then nominal — see pricing
- Total storage cap: per-workspace; varies by tier
2.7 Beta header enum (complete list as of 2026-05-25)
Per platform.claude.com/docs/en/api/files-content, the anthropic-beta header accepts these values:
message-batches-2024-09-24
prompt-caching-2024-07-31
computer-use-2024-10-22
computer-use-2025-01-24
pdfs-2024-09-25
token-counting-2024-11-01
token-efficient-tools-2025-02-19
output-128k-2025-02-19
files-api-2025-04-14
mcp-client-2025-04-04
mcp-client-2025-11-20
dev-full-thinking-2025-05-14
interleaved-thinking-2025-05-14
code-execution-2025-05-22
extended-cache-ttl-2025-04-11
context-1m-2025-08-07
context-management-2025-06-27
model-context-window-exceeded-2025-08-26
skills-2025-10-02
fast-mode-2026-02-01
output-300k-2026-03-24
user-profiles-2026-03-24
advisor-tool-2026-03-01
managed-agents-2026-04-01
cache-diagnosis-2026-04-07
Pass any combination as anthropic-beta: header-1,header-2,header-3 in the request header.
2.8 Workflow patterns
Pattern: bulk document analysis
# 1. Upload corpus once
file_ids = []
for pdf_path in glob.glob("contracts/*.pdf"):
f = client.beta.files.upload(file=open(pdf_path, "rb"), purpose="vision")
file_ids.append((pdf_path, f.id))
# 2. Batch-process queries against each
requests = []
for pdf_path, fid in file_ids:
requests.append(Request(
custom_id=pdf_path,
params=MessageCreateParamsNonStreaming(
model="claude-sonnet-4-6",
max_tokens=2048,
messages=[{"role": "user", "content": [
{"type": "document", "source": {"type": "file", "file_id": fid},
"citations": {"enabled": True}},
{"type": "text", "text": "Extract all parties, dates, and amounts."},
]}],
),
))
batch = client.messages.batches.create(requests=requests)Pattern: shared visual context
# Upload logo once, attach to every product description run
logo = client.beta.files.upload(file=open("logo.png", "rb"), purpose="vision")
for product in products:
resp = client.messages.create(
model="claude-haiku-4-5",
max_tokens=512,
messages=[{"role": "user", "content": [
{"type": "image", "source": {"type": "file", "file_id": logo.id}},
{"type": "text", "text": f"Write a 50-word product description: {product}"},
]}],
)3. Combining Batches + Files + Caching
The full cost-optimization stack for a large workload:
- Upload binaries via Files API → 1 KB per file reference vs MB per base64
- Use shared system prompt + tool definitions with 1-hour cache TTL (
extended-cache-ttl-2025-04-11) - Submit via Batches API → 50 % discount
- Pick the cheapest sufficient model (often Haiku 4.5 at $0.50/$2.50 batch rate)
A 100,000-request workload analyzing PDFs:
- Without optimizations: $3,000+ (Sonnet 4.6 sync rates)
- With all three: ~$300-400 (Haiku 4.5 batch + cached system prompt + Files-referenced PDFs)
4. Common gotchas
- Batch
max_tokens: 0fails — cache pre-warming doesn’t work inside batches. custom_idcollisions — duplicate custom_ids in a batch cause undefined which-wins behavior. Make them unique.- 24-hour expiration — under load, batches expire. Always handle the
"expired"result type. - Files API beta header forgotten — silent 404. Always include
anthropic-beta: files-api-2025-04-14. - File retention 30d, batch results 29d — close enough that “fire and forget” rarely works; download results promptly.
- Workspace isolation — files and batches are workspace-scoped; another workspace cannot access.
- Batches don’t stream — synchronous-style
stream: trueis silently ignored; results arrive as final messages.
5. Further reading
- Batch processing guide
- Creating Message Batches — full API reference
- Files API guide
- Beta headers — full enum
- Rate limits — Message Batches API
- Pricing — current batch + storage rates