Claude API 400 错误怎么排查:invalid_request_error 的九个常见成因
Claude API 返回 400,意思是:API 已经读懂了你的请求,并且明确拒绝了它。Key 是有效的,端点路径是对的,模型 ID 也解析成功了——问题出在请求体本身,或者请求体和你所选模型之间的规则不匹配。
这一点先把范围缩小了很多,但「请求体有问题」依然是一大片。本文按实际排查时值得检查的顺序,逐个拆解会触发 invalid_request_error 的常见成因,最后单独讲:请求经过 OpenAI 兼容层或中转网关时,400 又会从哪里冒出来。
400 invalid_request_error 到底表示什么
Anthropic 官方错误文档对 400 的定义是:请求的格式或内容有问题。这句定义里有两个细节容易被忽略:
invalid_request_error这个类型也可能出现在其他未单独列出的 4XX 状态码上,所以要把 HTTP 状态码和错误类型放在一起看。- 当用量达到你自己设置的组织或工作区支出上限时,API 同样返回 400。这是唯一一种与请求体完全无关的常见 400。
所有错误都以统一结构的 JSON 返回:顶层 error 对象里一定有 type 和 message,外层还带一个 request_id:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "..."
},
"request_id": "req_..."
}真正的诊断信息在 message 里。部分校验报错会在开头标出出错元素的位置,比如 messages.1.content.0,直接告诉你是第几轮消息、第几个内容块。代码里不要按报错文本做字符串匹配——官方说明这些对象里的取值会随版本扩展。报错文本拿来读,程序分支按状态码和错误类型走。
第零步:拿到完整报错体和 request ID
排查 400 时最常见的浪费,是对着一句被框架概括过的「Bad Request」调试,而不是对着真实报错。动手改之前先做三件事:
- 记录完整的错误体,而不只是异常类名。官方 SDK 会抛出带类型的异常(Python 里是
anthropic.BadRequestError),原始报错信息就在异常对象上。 - 记录实际发出去的 JSON。不是你在代码里构造的对象,而是序列化后的最终请求体。相当多的 400 来自某个辅助函数悄悄加了字段、丢了内容块或调换了顺序。
- 保留
request_id。每个响应都带request-id响应头,错误体里也有同一个值,联系支持时靠它追踪。
报错信息和真实请求体摆在一起,下面大多数成因一分钟内就能确认或排除。
成因一:请求体结构不对
Messages API 的必填核心很小:model、max_tokens、messages。除此之外,最容易踩的结构规则是:
messages里没有system角色。 系统提示词要放在顶层的system参数里。从 Chat Completions 风格代码迁移过来时,经常会把{"role": "system", ...}当作第一条消息发出去。content要么是字符串,要么是带类型的内容块数组。 字符串等价于一个text块。用数组时,每个元素都要有type(text、image、document、tool_use、tool_result等),并带上该类型要求的字段。- 字段必须挂在对应的块上。
image块带source,tool_result块带tool_use_id。字段放错块、放错层级,都会校验失败。
分不清是结构问题还是语义问题时,把请求缩减成「一条纯文本 user 消息」。能成功,就逐项加回原来的内容,直到再次失败。一个确定可用的最小请求长什么样,可以参考 Claude API 怎么用。
成因二:对话以 assistant 消息结尾
很多人以为 user 和 assistant 必须严格交替,否则就会报错。实际上 Messages API 参考文档写的是:连续的 user 或 assistant 消息会被合并成同一轮,所以连着两条 user 消息本身并不会触发 400。
真正会出问题的是最后一条消息。Claude 4.6 及之后的模型不支持 assistant 预填充(prefill)。对这些模型发送以 assistant 消息结尾的对话,会得到一条 400,报错原文为:
This model does not support assistant message prefill. The conversation must end with a user message.
这类报错多出现在升级模型之后:以前靠一段半截 assistant 消息强制输出 JSON、或续写被截断的回答,在老模型上正常,换新模型就失败。替代方案是:需要固定 JSON 结构时用结构化输出或 output_config.format,其余情况改用系统提示词说明要求。迁移细节见 Claude API 结构化输出。
成因三:模型已不再接受的采样参数
这是「昨天还好好的,今天就 400」最常见的原因。Messages API 参考文档写明:
|
参数 |
在 Claude Opus 4.6 之后发布的模型上的行为 |
|---|---|
|
|
出于向后兼容只接受 |
|
|
只接受大于等于 |
|
|
任何取值都返回 400 |
很多框架和封装库会替你设置这些参数。配置文件里一个默认的 temperature: 0.7,或者老提示词模板里带过来的 top_k,就足以触发。要在序列化后的请求体里搜,而不是只搜自己写的代码,因为这个值可能来自库的默认配置。
成因四:thinking 配置与模型不匹配
thinking 相关设置是按模型区分的,每种不匹配都有各自的 400:
- 在新模型上手动开启 extended thinking。 Claude 4.7 及之后的模型已移除 extended thinking,发送
thinking: {"type": "enabled"}会返回"thinking.type.enabled" is not supported for this model,并提示改用 adaptive thinking 配合output_config.effort。 - 在老模型上用 adaptive thinking。 Claude 4.5 及更早的模型会以
adaptive thinking is not supported on this model拒绝thinking: {"type": "adaptive"},这些模型要用type: "enabled"加budget_tokens。 - 在 thinking 常开的模型上尝试关闭。 在 Claude Fable 5.1 等模型上,
thinking: {"type": "disabled"}会返回 400。正确做法是不传该参数;如果只是不想在响应里看到思考内容,在 thinking 配置上设置display: "omitted"。 - 改动过的 thinking 块。 如果最近一条 assistant 消息里的
thinking或redacted_thinking块在回传前被编辑、调换顺序、过滤掉或重新拼装,请求会失败。配合工具调用时,该轮 assistant 的所有 thinking 块都必须原样回传,包括thinking字段为空的块。典型元凶是一个只保留text和tool_use块的内容过滤函数。
成因五:tool_use 与 tool_result 没有对上
工具调用引发的 400 结构最复杂,因为规则横跨两条消息。Anthropic 工具调用文档的要求是:
- 工具结果必须紧跟在工具调用之后。 含
tool_use的 assistant 消息与含对应tool_result的 user 消息之间,不能插入任何其他消息。 - 在这条 user 消息里,
tool_result块必须排在最前面。 任何文本都要放在所有结果之后。 - 每个结果通过
tool_use_id对应一次调用,取值必须等于上一轮 assistant 消息中某个tool_use块的id。
看到类似 tool_use ids were found without tool_result blocks immediately after 的报错,说明上面三条至少破了一条。下面这个请求会失败,因为文本排在了结果前面:
{
"role": "user",
"content": [
{"type": "text", "text": "Here are the results:"},
{"type": "tool_result", "tool_use_id": "toolu_01", "content": "15 degrees"}
]
}正确的顺序是:
{
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": "toolu_01", "content": "15 degrees"},
{"type": "text", "text": "What should I do next?"}
]
}现实中最常见的触发场景有三个:裁剪历史的逻辑恰好切在调用和结果之间;重试代码把 assistant 那一轮又追加了一遍;Agent 在回传结果前先插了一条状态提示。Claude 在同一轮里调用多个工具时,要在紧随其后的同一条 user 消息里把它们全部答复。
工具定义本身也有几类会返回 400 的问题:
- 工具
name必须匹配^[a-zA-Z0-9_-]{1,128}$,空格、点号和非 ASCII 字符都不行。 input_examples里的每个示例都必须通过该工具input_schema的校验。- 强制工具调用并非处处可用。
tool_choice设为any或tool时,与手动 extended thinking 同时使用会被拒绝;在 Claude Fable 5.1 和 Claude Mythos 5.1 上会返回tool_choice: type "tool" and "any" are not supported for this model.。这些场景改用auto,需要入参严格符合 schema 时再配合 strict tool use。
成因六:图片超出支持范围
直连 Claude API 时,图片块有自己的一组限制:
- 格式: JPEG、PNG、GIF、WebP(
image/jpeg、image/png、image/gif、image/webp)。声明的media_type要和实际字节一致,而不是照搬上传文件的扩展名。 - 大小: 单张图片 base64 编码后不超过 10 MB,尺寸不超过 8000x8000 px。
- 数量: 上下文窗口为 200k token 的模型每个请求最多 100 张,其他模型最多 600 张。
- 多图规则: 一个请求里图片超过 20 张后,其中每张图片都要满足更严格的单图尺寸限制——包括你重复发送的历史轮次图片,以及嵌在
tool_result里的图片。超限的图片会被invalid_request_error拒绝,报错里会提到「many-image requests」。把每张图的长宽都控制在 2000 px 以内即可避开。
最后这条能解释 Agent 里一个很让人困惑的现象:截图循环前二十轮都正常,之后突然开始报错——因为对话历史里的图片数刚刚越过了 20 张这条线。
另外要注意,请求体过大是另一种错误。Messages API 请求超过 32 MB 返回的是 413 request_too_large,不是 400。如果是 base64 图片把请求体撑大,可以改用 Files API 上传一次、之后用 file_id 引用,不必每轮重发图片字节。
成因七:提示词太长
长度问题分两种,行为不同:
- 仅输入部分就超过上下文窗口。 所有模型都返回 400
invalid_request_error(prompt is too long)。系统提示词、所有消息(包括工具结果和图片)、工具定义都计入。 - 输入没超,但输入加
max_tokens超过窗口。 在 Claude 4.5 及更新的模型上,API 会接受这个请求;如果生成过程中触到上限,会以stop_reason: "model_context_window_exceeded"停止。更早的模型则直接返回校验错误。
max_tokens 也要控制在模型的输出上限以内:Claude Opus 5、Claude Sonnet 5 等当前模型是 128K token,Claude Haiku 4.5 是 64K。Models API 会返回每个模型的 max_input_tokens 和 max_tokens,可以直接读取而不必写死。token 计数接口可以在发送前估算请求大小。长对话里什么在占用窗口、怎么管理,见 Claude API 上下文窗口。
成因八:经过 OpenAI 兼容层时的协议转换
很多 400 是在「翻译」过程中产生的:你的代码用 Chat Completions 格式说话,中间某一层把它转成 Anthropic 的 Messages 格式,转出来的请求体被 Messages API 拒绝了。
如果用的是 Anthropic 官方的 OpenAI SDK 兼容端点,要清楚它文档里写明的规则:
n必须恰好为 1。temperature接受 0 到 1,大于 1 的值会被截断为 1。- 对话中任意位置的 system 和 developer 消息都会被提到最前面,拼接成一段系统提示词。
- 大多数不支持的字段(如
response_format、seed、logprobs)会被静默忽略而不是报错,函数定义里的strict也会被忽略。 - 错误保持 OpenAI 的错误格式,但具体报错文本与 OpenAI 并不等价,只适合用于日志和调试。
如果经过的是第三方网关或中转服务,并由它转换成原生 Messages API,就要检查转换本身:
- tool 消息。 OpenAI 风格的
role: "tool"消息,必须转成一个tool_result块,放进紧跟在对应tool_use那轮 assistant 消息之后的 user 消息里。客户端为并行调用发来多条 tool 消息时,转换后的结果要落在同一条 user 消息里,并且结果在前。 - 被透传的参数。 如果网关把客户端的
temperature或top_p原样转发给已不接受这些参数的模型,就会触发成因三的 400——哪怕你自己的代码里根本没写采样参数。 - 内容片段。
image_url片段要转换成格式受支持、media_type正确的图片块。
排查这些之前,先确认 400 是哪一跳返回的。来自 Messages API 的错误就是开头那种 JSON 结构,类型为 invalid_request_error 并带 request_id;网关自己的校验错误通常长得不一样,而 HTML 错误页永远不会来自 Messages API。兼容性在哪里止步,见 OpenAI 兼容性问题;网关在客户端和上游之间如何工作,见 Claude API 中转是什么。
成因九:不是请求体,而是支出上限
如果所有请求在同一时间开始返回 400——包括几分钟前还正常的请求和最简单的测试调用——先别动请求体,去查支出上限。用量达到你设置的组织或工作区支出上限时,API 返回 400(Claude Code 工作区的上限可能返回 429)。这时要去 Console 调整,而不是改代码。
五分钟排查顺序
- 读完整的
message,留意类似messages.3.content.1的位置信息,直接定位到那个内容块。 - 判断是全部失败还是部分失败。 全部同时失败,优先怀疑支出上限或刚换过模型;只有部分失败,就是特定请求体的问题。
- 与最后一次成功的请求做对比。 模型 ID 变更是头号诱因:预填充、采样参数、thinking 配置、强制工具调用全都与模型相关。
- 在序列化后的请求体里搜
temperature、top_p、top_k、thinking、tool_choice。 - 涉及工具时, 确认每个
tool_use都被紧接着答复、结果排在最前、ID 一一对应。 - 涉及图片时, 统计整段历史里的图片数,并检查格式、大小和尺寸。
- 中间有转换层时, 先确认错误来自哪一跳,再用原生 Messages API 发同一个请求做对照。
常见问题
Claude API 400 错误会是 API Key 导致的吗?
一般不会。凭据问题返回的是 401 authentication_error,权限问题返回 403 permission_error。400 说明 Key 已通过认证,请求因内容被拒,或者触到了组织/工作区的支出上限。如果你怀疑是 Key 的问题,可以看 Anthropic API Key 用不了?先读状态码。
返回 400 的请求要不要重试?
不改就重试没有意义。官方 SDK 会对连接错误、429、5xx 这类暂时性故障按指数退避自动重试,默认两次;但 400 描述的是请求本身的问题,原样重发只会得到同样的结果。先修请求体,或先处理支出上限。
为什么同一个请求在一个模型上正常,换个模型就 400?
因为好几条校验规则是按模型区分的:assistant 预填充、非默认的 temperature、top_p、top_k、thinking 类型、强制工具调用,都取决于模型代际。升级模型后立刻出现 400,第一件事就是对照新模型接受的参数检查请求。
400 和 413 有什么区别?
400 invalid_request_error 针对的是请求里面有什么;413 request_too_large 针对的是请求有多大:Messages API 的上限是 32 MB。直连 Claude API 时,413 由 Cloudflare 在请求到达 API 服务器之前返回。