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

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

MethodPathPurpose
POST/v1/messages/batchesCreate a batch
GET/v1/messages/batches/{id}Poll status
GET/v1/messages/batches/{id}/resultsDownload results (JSONL stream)
POST/v1/messages/batches/{id}/cancelCancel in-progress batch
GET/v1/messages/batchesList 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 >= 1max_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)

ModelBatch inputBatch 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 returned

1.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

  1. 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.
  2. Pre-sort by model if mixing — single-model batches schedule faster (anecdotal, observed behavior).
  3. Custom IDs should encode whatever you need for join — request hash, input row ID, eval case name.
  4. Plan for partial expiration under high demand — retry just the expired subset.
  5. Combine with the Console batch viewer at /console/batches to 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:

  1. 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.
  2. Reuse: upload a 50-page contract once, query it across 100 conversations.

2.2 Endpoints

MethodPathPurpose
POST/v1/filesUpload
GET/v1/filesList
GET/v1/files/{id}Get metadata
GET/v1/files/{id}/contentDownload
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:

  1. Upload binaries via Files API → 1 KB per file reference vs MB per base64
  2. Use shared system prompt + tool definitions with 1-hour cache TTL (extended-cache-ttl-2025-04-11)
  3. Submit via Batches API → 50 % discount
  4. 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

  1. Batch max_tokens: 0 fails — cache pre-warming doesn’t work inside batches.
  2. custom_id collisions — duplicate custom_ids in a batch cause undefined which-wins behavior. Make them unique.
  3. 24-hour expiration — under load, batches expire. Always handle the "expired" result type.
  4. Files API beta header forgotten — silent 404. Always include anthropic-beta: files-api-2025-04-14.
  5. File retention 30d, batch results 29d — close enough that “fire and forget” rarely works; download results promptly.
  6. Workspace isolation — files and batches are workspace-scoped; another workspace cannot access.
  7. Batches don’t stream — synchronous-style stream: true is silently ignored; results arrive as final messages.

5. Further reading