API Beginner easy · 4 min

InvalidRequestError: malformed messages

What you will learn
The Anthropic API rejects requests when your messages list violates the required structure: learn what makes a message valid.

Why this matters

Messages are the core input to Claude. A single structural mistake silently breaks your entire request, and the error message tells you exactly what went wrong if you know where to look. Developers waste hours debugging when they should just fix the format.

Skip if: If you're using a wrapper library (like LangChain's newer LCEL abstractions) that constructs messages for you, you won't hit this directly: but you still need to understand message structure to debug when wrappers fail.

Explanation

The Anthropic API requires messages to be a list of dictionaries, where each dictionary has exactly two keys: role (either "user" or "assistant") and content (a string). Any deviation: wrong role, missing content, extra fields, wrong type: triggers InvalidRequestError.

Under the hood, the API validates message structure before it even attempts to run inference. This happens synchronously in your request handler, not in a background job. The error includes a message field that pinpoints exactly which message in your list is invalid.

You'll hit this most often when: (1) concatenating user input without escaping newlines properly, (2) accidentally passing a list instead of a string for content, (3) including system context as a message instead of using the system parameter, or (4) forgetting to alternate roles (user → assistant → user, never user → user).

Request code

python
from anthropic import Anthropic, BadRequestError

client = Anthropic()

try:
    response = client.messages.create(
        model="claude-opus-4-6",
        max_tokens=256,
        messages=[
            {"role": "user", "content": "What is 2+2?"},
            {"role": "assistant", "content": "2+2 equals 4."},
            {"role": "user", "content": "Prove it."}
        ]
    )
    print(response.content[0].text)
except BadRequestError as e:
    print(f"API Error: {e.message}")

Authentication

You need a valid Anthropic API key. Export it as an environment variable: `export ANTHROPIC_API_KEY='sk-ant-...'`. The Anthropic client reads this at instantiation.

Response shape

FieldDescription
content List of content blocks: typically one TextBlock with the assistant's response
content[0].text The actual text response from Claude
model The model ID that processed the request (e.g., 'claude-opus-4-6')
usage Object with 'input_tokens' and 'output_tokens' counts

Field guide

content

Always a list, even if one element: iterate or index carefully

usage

Developers often forget to check this for cost accounting; this is where you track token consumption for billing

Setup trap

If you set ANTHROPIC_API_KEY in your shell *after* importing Anthropic but *before* instantiating the client, it works fine: the client reads the environment at instantiation. But if you instantiate first (e.g., at module load time), then set the key, the client will use None and fail with 'Unauthorized' on the first request, not during client creation. Always instantiate after environment setup.

Cost

An InvalidRequestError costs nothing: it fails before token consumption. This actually saves money compared to a valid but poorly-written message that uses tokens inefficiently.

Rate limits

Invalid requests don't count against rate limits in most cases: they fail in the validation layer before reaching the model queue.

Common gotcha

The most common mistake: passing a list of strings instead of a list of dictionaries. `messages=["hello"]` will fail, but `messages=[{"role": "user", "content": "hello"}]` works. The error message says 'invalid type for messages': developers often misread this as a problem with the messages parameter itself, not its contents.

Error recovery

BadRequestError (invalid type for 'messages[0].role')
You passed a role that isn't 'user' or 'assistant'. Check for typos: 'User', 'USER', 'human' are all invalid. Use lowercase exactly.
BadRequestError (invalid type for 'messages[0].content')
Content must be a string, not a list, dict, or None. If building content dynamically, convert to string first: content=str(user_input)
BadRequestError ('messages' is required)
You didn't pass messages at all, or passed it as None. Every request must have at least one message.
BadRequestError (odd number of messages)
You likely forgot to include the assistant's response before the next user message. Messages must alternate: user → assistant → user, starting with user.

Experienced dev note

In production, validate your messages structure *before* calling the API. Write a simple validator function that checks role ∈ {user, assistant}, content is a non-empty string, and alternation. This costs zero API calls and catches 90% of InvalidRequestError before they hit Anthropic's servers. Also: the 'system' parameter is separate from messages: don't try to sneak system context into messages[0] with a fake role. That's where most refactors from other LLM APIs break.

Check your understanding

You're dynamically building messages by reading from a database where each row has a 'speaker' column (either 'human' or 'bot') and a 'text' column. Why will simply mapping speaker='human' → role='user' and speaker='bot' → role='assistant' likely fail on the first request, and what should you check?

Show answer hint

Consider the order of messages in the database and whether they alternate correctly. Also check if 'text' might be None, empty, or a number instead of a string.

VERSION anthropic 0.94.x: BadRequestError is the exception type for invalid requests. Older versions (0.3.x) used different exception names. Always catch the specific exception, not a generic Exception.

Community Notes

No notes yetBe the first to share a version-specific fix or tip.