接入教程

Claude API 400 错误怎么排查:invalid_request_error 的九个常见成因

Kenji Watanabe

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 对象里一定有 typemessage,外层还带一个 request_id

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "..."
  },
  "request_id": "req_..."
}

真正的诊断信息在 message 里。部分校验报错会在开头标出出错元素的位置,比如 messages.1.content.0,直接告诉你是第几轮消息、第几个内容块。代码里不要按报错文本做字符串匹配——官方说明这些对象里的取值会随版本扩展。报错文本拿来读,程序分支按状态码和错误类型走。

第零步:拿到完整报错体和 request ID

排查 400 时最常见的浪费,是对着一句被框架概括过的「Bad Request」调试,而不是对着真实报错。动手改之前先做三件事:

  1. 记录完整的错误体,而不只是异常类名。官方 SDK 会抛出带类型的异常(Python 里是 anthropic.BadRequestError),原始报错信息就在异常对象上。
  2. 记录实际发出去的 JSON。不是你在代码里构造的对象,而是序列化后的最终请求体。相当多的 400 来自某个辅助函数悄悄加了字段、丢了内容块或调换了顺序。
  3. 保留 request_id。每个响应都带 request-id 响应头,错误体里也有同一个值,联系支持时靠它追踪。

报错信息和真实请求体摆在一起,下面大多数成因一分钟内就能确认或排除。

成因一:请求体结构不对

Messages API 的必填核心很小:modelmax_tokensmessages。除此之外,最容易踩的结构规则是:

  • messages 里没有 system 角色。 系统提示词要放在顶层的 system 参数里。从 Chat Completions 风格代码迁移过来时,经常会把 {"role": "system", ...} 当作第一条消息发出去。
  • content 要么是字符串,要么是带类型的内容块数组。 字符串等价于一个 text 块。用数组时,每个元素都要有 typetextimagedocumenttool_usetool_result 等),并带上该类型要求的字段。
  • 字段必须挂在对应的块上。 image 块带 sourcetool_result 块带 tool_use_id。字段放错块、放错层级,都会校验失败。

分不清是结构问题还是语义问题时,把请求缩减成「一条纯文本 user 消息」。能成功,就逐项加回原来的内容,直到再次失败。一个确定可用的最小请求长什么样,可以参考 Claude API 怎么用

成因二:对话以 assistant 消息结尾

很多人以为 user 和 assistant 必须严格交替,否则就会报错。实际上 Messages API 参考文档写的是:连续的 userassistant 消息会被合并成同一轮,所以连着两条 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 之后发布的模型上的行为

temperature

出于向后兼容只接受 1.0,其他任何值都返回 400

top_p

只接受大于等于 0.99 的值,更低的值返回 400

top_k

任何取值都返回 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 消息里的 thinkingredacted_thinking 块在回传前被编辑、调换顺序、过滤掉或重新拼装,请求会失败。配合工具调用时,该轮 assistant 的所有 thinking 块都必须原样回传,包括 thinking 字段为空的块。典型元凶是一个只保留 texttool_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 设为 anytool 时,与手动 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/jpegimage/pngimage/gifimage/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_tokensmax_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_formatseedlogprobs)会被静默忽略而不是报错,函数定义里的 strict 也会被忽略。
  • 错误保持 OpenAI 的错误格式,但具体报错文本与 OpenAI 并不等价,只适合用于日志和调试。

如果经过的是第三方网关或中转服务,并由它转换成原生 Messages API,就要检查转换本身:

  • tool 消息。 OpenAI 风格的 role: "tool" 消息,必须转成一个 tool_result 块,放进紧跟在对应 tool_use 那轮 assistant 消息之后的 user 消息里。客户端为并行调用发来多条 tool 消息时,转换后的结果要落在同一条 user 消息里,并且结果在前。
  • 被透传的参数。 如果网关把客户端的 temperaturetop_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 调整,而不是改代码。

五分钟排查顺序

  1. 读完整的 message,留意类似 messages.3.content.1 的位置信息,直接定位到那个内容块。
  2. 判断是全部失败还是部分失败。 全部同时失败,优先怀疑支出上限或刚换过模型;只有部分失败,就是特定请求体的问题。
  3. 与最后一次成功的请求做对比。 模型 ID 变更是头号诱因:预填充、采样参数、thinking 配置、强制工具调用全都与模型相关。
  4. 在序列化后的请求体里搜 temperaturetop_ptop_kthinkingtool_choice
  5. 涉及工具时, 确认每个 tool_use 都被紧接着答复、结果排在最前、ID 一一对应。
  6. 涉及图片时, 统计整段历史里的图片数,并检查格式、大小和尺寸。
  7. 中间有转换层时, 先确认错误来自哪一跳,再用原生 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 预填充、非默认的 temperaturetop_ptop_k、thinking 类型、强制工具调用,都取决于模型代际。升级模型后立刻出现 400,第一件事就是对照新模型接受的参数检查请求。

400 和 413 有什么区别?

400 invalid_request_error 针对的是请求里面有什么;413 request_too_large 针对的是请求有多大:Messages API 的上限是 32 MB。直连 Claude API 时,413 由 Cloudflare 在请求到达 API 服务器之前返回。