批量API将请求异步处理,输入输出token均半价,24小时是上限而非预计时间,适合分类、汇总等非实时任务。
Message Batches API 接收一个请求列表——这些请求本来需要逐个发送——然后异步处理它们,并以更低的成本计费。代价是延迟:你放弃了单个请求何时完成的任何保证,换来的是整个批次完成时间的明确上限。
Anthropic 将批处理文档记录为在 24 小时内返回结果,输入和输出 token 相比同等标准 API 调用享有 50% 的折扣。这两个数字都发布在 Anthropic 的批处理文档和定价页面上,折扣自动应用,无需设置任何标志。
24 小时是一个上限,不是预估。批次经常远早完成,而且 API 中没有任何方式可以请求更快。这个决策的核心很简单:如果有任何人在等待单个结果,这不是正确的端点;如果工作是分类处理积压、评估运行、批量摘要、表格 enrichment,或者任何截止时间是"明天"而非"现在"的任务,那么在不增加代码复杂度的前提下(只需轮询),将账单减半几乎等于白送。
明确说明批处理不能提供什么,和说明它能提供什么同样重要。没有顺序保证、没有部分结果流、没有方式对批次内的某个请求优先处理、也没有比 request_counts 中的计数更细的进度信号。每个请求都是独立的——批次不是对话,条目之间不能相互引用。批次中的请求在其他所有方面都是正常请求,包括它可能按自己的条件失败:一个错误的 schema 或过大的 prompt 只会导致那一条失败,而不是整个提交失败。
折扣和完成窗口是定价和服务承诺,两者都是可能变更的数字。在基于它们构建成本模型之前,请从 Anthropic 的定价和批处理页面读取这些数字,而不是从这里。
批次是发送到 /v1/messages/batches 的 POST 请求,带有一个 requests 数组。每个条目有你选择的 custom_id 和一个 params 对象,其格式正是 Messages API 请求体——相同的模型、相同的必需 max_tokens、相同的 system、tools 和 messages:
POST https://api.anthropic.com/v1/messages/batches
anthropic-version: 2023-06-01
content-type: application/json
{
"requests": [
{
"custom_id": "ticket-90214",
"params": {
"model": "claude-sonnet-4-20250514",
"max_tokens": 512,
"system": "Classify the ticket. Reply with one word.",
"messages": [{ "role": "user", "content": "Card declined at checkout..." }]
}
},
{
"custom_id": "ticket-90215",
"params": {
"model": "claude-sonnet-4-20250514",
"max_tokens": 512,
"system": "Classify the ticket. Reply with one word.",
"messages": [{ "role": "user", "content": "How do I export my data?" }]
}
}
]
}
custom_id 是至关重要且人们往往考虑不足的一个字段。结果返回时无序,所以这个 ID 是你关联回自己记录的唯一连接键。它在该批次内必须唯一。使用你的主键,而不是数组索引——索引在有人过滤输入列表后就失效了。
请求的其他方面没有任何变化。批次中的请求可以使用 tools、images、extended thinking 和 prompt caching;因为批次内的条目是 close together 处理的,所以在批次间建立共享的 cached prefix 值得主动设置,而不是假设 caching 和 batching 是互斥的。参见 cache_control 和 prompt caching。
轮询和处理状态
提交的响应立即返回一个 Message Batch 对象,其 id 以 msgbatch_ 开头。然后你轮询 GET /v1/messages/batches/{id} 直到完成:
{
"id": "msgbatch_013Zva...",
"type": "message_batch",
"processing_status": "in_progress",
"request_counts": {
"processing": 2,
"succeeded": 0,
"errored": 0,
"canceled": 0,
"expired": 0
},
"ended_at": null,
"created_at": "2026-08-11T09:14:02Z",
"expires_at": "2026-08-12T09:14:02Z",
"results_url": null
}
processing_status 有三个值:in_progress、canceling 和 ended。注意 ended 的含义——它只表示批次处理完毕,不代表其中的所有请求都成功了。成功与否看的是 request_counts 中的计数。包含 10,000 个请求但其中 10,000 个都出错的批次也是 ended。
轮询间隔最多以数十秒为单位,并使用退避策略;没有必要为了保持连接而使用 webhook,对于一个有着 24 小时窗口的作业,每秒疯狂请求状态端点没有任何意义。
读取结果,包括失败的情况
当 processing_status 达到 ended 时,results_url 会被填充。它以 .jsonl 格式流式返回——每行一个 JSON 对象,每行对应一个请求,不保证顺序。每行携带你的 custom_id 和一个 result,其类型是四个值之一:
{"custom_id":"ticket-90215","result":{"type":"succeeded","message":{
"id":"msg_01...","content":[{"type":"text","text":"billing"}],
"stop_reason":"end_turn","usage":{"input_tokens":41,"output_tokens":3}}}}
{"custom_id":"ticket-90214","result":{"type":"errored","error":{
"type":"invalid_request_error","message":"..."}}}
succeeded——在 message 下是一个正常的 Message 对象,完整包含自己的 stop_reason 和 usage。在这里检查 stop_reason 的方式与你在线上调用时完全相同;批次返回的响应仍然可能在 max_tokens 处被截断。
errored——一个 per-request 错误对象,格式与同步 API 错误相同。这是正确性最关键的一点:批次内格式错误的请求不会导致整个批次失败,只会失败那一条,而代码如果遍历结果时假设 result.message 存在,就会抛出异常。
canceled——该请求在你取消批次时尚未开始。
expired——该请求在 24 小时窗口关闭时仍未被处理。你不需要为这些付费,而这些正是需要重新提交的那些。
结果在批次结束后的一段文档化保留期内仍可检索——足够长,你不需要立即消费它们;足够短,你不应该把 results URL 当作存储。在流式读取时将它们写入某个持久化的地方。
限制和过期规则
Anthropic 文档记录了每个批次的限制:最多 100,000 个请求或总请求大小 256 MB,以先到者为准。第二个限制在多模态工作中比人们预期的更早绑定:params 中的 base64 images 会被计入,所以包含几千个带图像请求的批次可能在远未接近 100,000 条目时就已超过 256 MB。按大小拆分,而不是按数量。
过期规则是需要围绕它进行设计的那个运营规则。窗口从创建时开始计时,所以任何在 24 小时后仍未被处理的请求都会以 expired 返回,而不是被结转。一个重新提交循环——读取结果文件、过滤 expired 和 errored、从那些 custom_id 构建一个新的批次——大约十五行代码,是区分"能完成的 pipeline"和"每晚悄悄丢失一部分行"的 pipeline 的关键。
取消是 best-effort 而非立即生效。向 /v1/messages/batches/{id}/cancel 发送 POST 请求会将批次移动到 canceling 状态,已经在 flight 中的请求会被允许完成——所以被取消的批次仍然会产生结果文件,混合着 succeeded 和 canceled 条目。你需要为已完成的部分付费。无法取消批次内的单个请求;粒度是批次级别。
两个能省下半下午的小细节。批次不像同步流量那样消耗相同的速率限制,这是其大部分吸引力的来源——一个本来需要数小时才能通过每分钟 token 限制推送的积压,可以作为一次提交进入。另外提交没有幂等性:两次发送相同的 requests 数组会创建两个批次并对两者计费。如果你的提交方可以在网络超时后重试,在做任何其他事情之前先记录返回的 msgbatch_ ID,并在重新提交可能已经在飞行中的作业之前检查是否已存在一个批次。
批处理端点是提供商 API 差异最大的地方之一:不同的提交格式、不同的状态枚举、不同的结果编码和不同的窗口。如果你向多个供应商做批处理,协调逻辑——用你自己的 ID join、按行分类结果、重新提交失败的部分——值得针对一个标准化的结果类型写一次,而不是针对两个供应商 schema 各写一次。这种标准化正是 Multigrid 这类网关的用途所在;而 24 小时窗口和折扣仍然是提供商的事情。