Why the Anthropic API is so strict
Claude expects a perfect back-and-forth rhythm. Unlike some LLMs that let you stack messages, the Anthropic Messages API treats the conversation like a tennis match. You send a message, Claude replies, and then you send another. If you break this user → assistant → user pattern, the API rejects the request immediately.
You'll usually run into this 400 error in one of these three ways:
- A user sent two messages in a row (double-posting) before the AI responded.
- You tried to put the "system" prompt inside the
messagesarray. - The conversation history starts with an "assistant" message instead of a "user" prompt.
Step-by-Step Fix
1. Move System Messages out of the Array
If you're coming from the OpenAI ecosystem, this is a common trap. In Anthropic's API, the system prompt is a top-level parameter. It should never be an object inside your message history list.
# WRONG: This will cause a 400 error
client.messages.create(
model="claude-3-5-sonnet-20240620",
messages=[
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "Hello!"}
]
)
# CORRECT: Use the dedicated system parameter
client.messages.create(
model="claude-3-5-sonnet-20240620",
system="You are a helpful assistant",
messages=[
{"role": "user", "content": "Hello!"}
]
)
2. Merge Consecutive Messages of the Same Role
In real-world chat apps, users often send several messages before the AI can respond. You can't just append these to a list. Instead, you must combine them into a single message object before hitting the API.
Use this Python helper to clean up your history by merging duplicate roles:
def merge_consecutive_messages(messages):
if not messages:
return []
merged = []
for msg in messages:
# Check if the current role matches the last one we added
if merged and merged[-1]["role"] == msg["role"]:
prev_content = merged[-1]["content"]
curr_content = msg["content"]
# Combine strings with a double newline
if isinstance(prev_content, str) and isinstance(curr_content, str):
merged[-1]["content"] = f"{prev_content}\n\n{curr_content}"
else:
# Merge complex content blocks like images and text
if isinstance(prev_content, str):
merged[-1]["content"] = [{"type": "text", "text": prev_content}]
if isinstance(curr_content, str):
curr_content = [{"type": "text", "text": curr_content}]
merged[-1]["content"].extend(curr_content)
else:
merged.append(msg)
return merged
3. Always Start with a "user" Message
Anthropic requires every conversation to begin with the user role. If your database pulls a history that starts with a cached assistant response, the API will fail. Always slice or prepend your list to ensure a user prompt leads the way.
Verification
Before calling the API, print your messages array. It should look like a clean alternating sequence. For example: 100% of the roles must follow the [user, assistant, user, assistant] order. If you see two identical roles touching, your merging logic likely failed.
Practical Tips
- UI Throttling: Disable the "Send" button while the assistant is thinking to prevent users from stacking messages.
- Content Blocks: When sending text and an image together, use a single "user" role with multiple content blocks rather than two separate messages.
- Debugging: Log the JSON payload of your request. It's often easier to spot duplicate roles in a raw log than in complex application code.

