InvalidRequestError: malformed messages
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.
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
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
| Field | Description |
|---|---|
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')BadRequestError (invalid type for 'messages[0].content')BadRequestError ('messages' is required)BadRequestError (odd number of messages)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.