Integration Guides

Claude API Error 400: Causes and Fixes for invalid_request_error

Kenji Watanabe

A Claude API error 400 is the API telling you it understood your request well enough to reject it. The key worked, the endpoint exists, the model ID resolved. Something in the body, or in how the body relates to the model you named, broke a rule.

That narrows the search a lot, but "something in the body" still covers a wide field. This guide walks through the causes that actually produce invalid_request_error in practice, in roughly the order worth checking them, and ends with what changes when your requests pass through an OpenAI-compatible layer or a gateway on the way to Anthropic.

What a 400 invalid_request_error means

Anthropic's error reference defines a 400 as a problem with the format or content of your request. Two details in that definition matter more than they first appear:

  • The same invalid_request_error type may also be used for other 4XX status codes that are not listed separately. Read the HTTP status and the error type together.
  • The API also returns a 400 when usage reaches an organization or workspace spend limit you set. That is the one common 400 that has nothing to do with your payload.

Every error comes back as JSON with the same outer shape: a top-level error object that always has type and message, plus a request_id:

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

The message is where the diagnosis lives. Some validation messages start with the position of the offending element, for example messages.1.content.0, which tells you exactly which turn and which block to look at. Do not string-match on these messages in code; Anthropic notes the values inside error objects can expand over time. Use them for reading, and branch on the status and type.

Step zero: capture the full body and the request ID

Most wasted time on 400s comes from debugging a summarized error ("Bad Request") instead of the real one. Before changing anything:

  1. Log the complete error body, not just the exception class. The official SDKs raise typed exceptions (for example anthropic.BadRequestError in Python), and the underlying message is on that object.
  2. Log the exact JSON you sent. Not the object you built, the serialized payload. A surprising number of 400s come from a helper that silently adds a field, drops a block, or reorders content.
  3. Keep the request_id. Every response carries a request-id header, and the same value appears in error bodies. It is what support can trace.

With the message and the payload side by side, most of the causes below take a minute to confirm or rule out.

Cause 1: the request body is shaped wrong

The Messages API has a small required core: model, max_tokens, and messages. Beyond that, the shape rules that trip people up most often are:

  • There is no system role inside messages. The system prompt goes in the top-level system parameter. Code ported from chat-completions style APIs often sends {"role": "system", ...} as the first message.
  • content is either a string or an array of typed blocks. A string is shorthand for one text block. In array form, every element needs a type (text, image, document, tool_use, tool_result, and so on) and the fields that type requires.
  • Fields belong to specific blocks. An image block carries a source; a tool_result block carries a tool_use_id. Putting a field on the wrong block, or at the wrong nesting level, is a validation failure.

If you are unsure whether the problem is shape or semantics, reduce the request to one user message with plain text content. If that succeeds, add pieces back until it fails. For a walkthrough of a known-good first request, see how to use the Claude API.

Cause 2: the conversation ends on an assistant turn

A common belief is that user and assistant turns must strictly alternate or the API rejects the request. The Messages API reference actually says consecutive user or assistant turns are combined into a single turn, so two user messages in a row are not, by themselves, an error.

The rule that does bite is about the last message. Claude 4.6 and later models do not support prefilling an assistant message. Send a conversation that ends with an assistant turn to one of those models and you get a 400 whose message reads:

This model does not support assistant message prefill. The conversation must end with a user message.

This surfaces most often after a model upgrade: code that used a partial assistant message to force JSON output, or to continue a truncated answer, worked on an older model and fails on the new one. The replacements are structured outputs or output_config.format for fixed JSON shapes, and system prompt instructions for everything else. The Claude API structured output guide covers the migration.

Cause 3: sampling parameters the model no longer accepts

This is the most frequent "it worked yesterday" 400. According to the Messages API reference:

Parameter

Behaviour on models released after Claude Opus 4.6

temperature

Only 1.0 is accepted for backwards compatibility; any other value returns a 400

top_p

Values of 0.99 or higher are accepted; anything lower returns a 400

top_k

Any value is rejected with a 400

Frameworks and wrappers frequently set these for you. A default of temperature: 0.7 buried in a config file, or a top_k inherited from an older prompt template, is enough. Search the serialized payload, not your own code, because the value may come from a library default.

Cause 4: thinking configuration that doesn't match the model

Thinking settings are model-specific, and each mismatch has its own 400:

  • Manual extended thinking on newer models. Claude 4.7 and later models removed extended thinking. Sending thinking: {"type": "enabled"} returns a message saying "thinking.type.enabled" is not supported for this model and pointing you to adaptive thinking with output_config.effort.
  • Adaptive thinking on older models. Claude 4.5 and earlier models reject thinking: {"type": "adaptive"} with adaptive thinking is not supported on this model. Those models use type: "enabled" with budget_tokens.
  • Trying to turn thinking off where it is always on. On models such as Claude Fable 5.1, thinking: {"type": "disabled"} returns a 400. Omit the parameter instead; to keep thinking content out of responses, set display: "omitted" on the thinking configuration.
  • Edited thinking blocks. If the most recent assistant message contains thinking or redacted_thinking blocks that were edited, reordered, filtered out, or reconstructed before you sent them back, the request fails. With tool use, every thinking block from that assistant turn must be passed back exactly as received, including blocks whose thinking field is empty. A content filter that keeps only text and tool_use blocks is the classic culprit.

Cause 5: tool_use and tool_result don't line up

Tool calling produces the most structurally intricate 400s, because the rules span two messages. From Anthropic's tool-use documentation:

  • Tool results must immediately follow their tool calls. You cannot place any message between the assistant message that contains tool_use and the user message that contains the matching tool_result.
  • In that user message, tool_result blocks come first. Any text must come after all the results.
  • Each result names its call through tool_use_id, which must equal the id of a tool_use block from the preceding assistant turn.

If you see an error like tool_use ids were found without tool_result blocks immediately after, one of those three has broken. This request fails because text precedes the result:

{
  "role": "user",
  "content": [
    {"type": "text", "text": "Here are the results:"},
    {"type": "tool_result", "tool_use_id": "toolu_01", "content": "15 degrees"}
  ]
}

And this is the correct order:

{
  "role": "user",
  "content": [
    {"type": "tool_result", "tool_use_id": "toolu_01", "content": "15 degrees"},
    {"type": "text", "text": "What should I do next?"}
  ]
}

The usual real-world triggers are conversation-trimming logic that cuts history between a call and its result, retry code that re-appends the assistant turn, and agents that insert a status message before posting results. When Claude calls several tools in one turn, answer all of them in the single user message that follows.

A few tool-definition problems also return 400s:

  • A tool name must match ^[a-zA-Z0-9_-]{1,128}$. Spaces, dots, and non-ASCII characters fail.
  • Every entry in input_examples must validate against that tool's input_schema.
  • Forced tool use is not available everywhere. tool_choice of any or tool is rejected alongside manual extended thinking, and on Claude Fable 5.1 and Claude Mythos 5.1 it returns tool_choice: type "tool" and "any" are not supported for this model. Use auto there, with strict tool use if you need schema-valid inputs.

Cause 6: images outside the supported limits

Image blocks carry their own set of limits on the direct Claude API:

  • Formats: JPEG, PNG, GIF, and WebP (image/jpeg, image/png, image/gif, image/webp). Declare the media_type that matches the actual bytes, not the file extension someone gave the upload.
  • Size: up to 10 MB per image, base64-encoded, and at most 8000x8000 px.
  • Count: up to 100 images per request on models with a 200k-token context window, 600 on the others.
  • The many-image rule: once a request contains more than 20 images, a stricter per-image dimension limit applies to every image in it, including images from earlier turns you resend and images nested inside tool_result content. Images over that limit are rejected with an invalid_request_error that references "many-image requests". Keeping each image at or under 2000 px on both sides avoids it.

That last rule explains a confusing pattern in agents: a screenshot loop works for twenty turns and then starts failing, because the conversation history crossed the 20-image line.

Note that an oversized request is a different error. Messages API requests over 32 MB return 413 request_too_large, not 400. If base64 images are pushing you toward that ceiling, the Files API lets you reference an uploaded image by file_id instead of resending the bytes every turn.

Cause 7: the prompt is too long

Length problems split into two cases that behave differently:

  • The input alone exceeds the context window. Every model returns a 400 invalid_request_error ("prompt is too long"). Everything counts: the system prompt, every message including tool results and images, and the tool definitions.
  • Input plus max_tokens exceeds the window, but input alone fits. On Claude 4.5 models and newer, the API accepts the request, and if generation reaches the limit it stops with stop_reason: "model_context_window_exceeded". Earlier models return a validation error instead.

Keep max_tokens within the model's output ceiling as well: 128K tokens on current models such as Claude Opus 5 and Claude Sonnet 5, and 64K on Claude Haiku 4.5. The Models API reports max_input_tokens and max_tokens for each model, so you can read limits instead of hard-coding them. The token counting endpoint estimates a request before you send it. For long-running conversations, the Claude API context window guide covers what fills the window and how to manage it.

Cause 8: protocol translation through an OpenAI-compatible layer

Many 400s are born in translation. Your code speaks the chat-completions format; something converts it into Anthropic's Messages format; the conversion produces a body the Messages API rejects.

If you use Anthropic's own OpenAI SDK compatibility endpoint, know its documented rules:

  • n must be exactly 1.
  • temperature accepts 0 to 1; values above 1 are capped at 1.
  • System and developer messages anywhere in the conversation are hoisted and concatenated into one system prompt at the start.
  • Most unsupported fields, such as response_format, seed, and logprobs, are silently ignored rather than rejected, and strict on function definitions is ignored.
  • Errors keep the OpenAI error format, but the detailed messages are not equivalent to OpenAI's, so use them for logging and debugging only.

If you go through a third-party gateway or proxy that translates to the native Messages API, check the conversion itself:

  • Tool messages. An OpenAI-style role: "tool" message has to become a tool_result block inside a user message that directly follows the assistant turn with the matching tool_use. If a client sends several tool messages for parallel calls, the translated results need to land in that one user message, results first.
  • Forwarded parameters. If the gateway passes your client's temperature or top_p straight through to a model that no longer accepts them, you get the 400 from Cause 3 even though your own code never mentions sampling.
  • Content parts. image_url parts must translate into image blocks with a supported format and a correct media_type.

Before debugging any of this, confirm which hop produced the 400. An error from the Messages API has the JSON shape shown at the top, with invalid_request_error as the type and a request_id. A gateway's own validation error usually looks different, and an HTML error page never comes from the Messages API. The OpenAI compatibility issues guide maps where compatibility stops, and the Claude API proxy guide explains how a gateway sits between your client and the upstream.

Cause 9: it's not the body, it's a spend limit

If every request starts returning 400 at once, including requests that worked minutes ago and minimal test calls, check spend limits before touching the payload. The API returns a 400 when usage reaches an organization or workspace spend limit you set (limits on the Claude Code workspace can return a 429 instead). The fix is in the Console, not in your code.

A five-minute diagnostic order

  1. Read the full message and note any position like messages.3.content.1. Go straight to that block.
  2. Check whether everything fails or only some requests. Everything failing at once suggests a spend limit or a recent model change; only some failing points at specific payloads.
  3. Diff against the last working request. Model ID changes are the top trigger: prefill, sampling parameters, thinking configuration, and forced tool choice are all model-specific.
  4. Search the serialized payload for temperature, top_p, top_k, thinking, and tool_choice.
  5. If tools are involved, confirm each tool_use is answered immediately, results come first, and IDs match.
  6. If images are involved, count them across the whole history and check format, size, and dimensions.
  7. If a translation layer is involved, confirm which hop produced the error, then test the same request against the native Messages API.

Frequently asked questions

Can a Claude API error 400 be caused by my API key?

Not normally. Credential problems return 401 authentication_error, and permission problems return 403 permission_error. A 400 means the key authenticated and the request was rejected on its contents, or an org or workspace spend limit was reached. If you are chasing a key problem, the status-code guide for a key that isn't working sorts the codes.

Should I retry a request that returned 400?

Not unchanged. The official SDKs retry transient failures such as connection errors, 429s, and 5xx errors with exponential backoff, twice by default, but a 400 describes the request itself, so resending the identical body reproduces it. Fix the payload, or the spend limit, first.

Why does the same request work on one model and fail on another?

Because several validation rules are model-specific: assistant prefill, non-default temperature, top_p and top_k, the thinking type, and forced tool choice all depend on the model generation. When a 400 appears right after a model upgrade, compare the request against that model's accepted parameters before anything else.

What is the difference between a 400 and a 413?

A 400 invalid_request_error is about what the request contains. A 413 request_too_large is about how many bytes it is: over 32 MB for the Messages API. On the direct Claude API, Cloudflare returns the 413 before the request reaches the API servers.