The Messages API has exactly two roles in the messages array: user and assistant. That’s it.
There is no “system” role
System instructions go in the top-level system parameter, not as a message with a system role. If you try to put a message with role: "system" in the messages array, it won’t work the way you expect. The system prompt has its own dedicated place in the request structure.
Tool results use the “user” role
This trips people up. When the model requests a tool call (via an assistant message containing a tool_use block), you send the result back as a user message with a tool_result content block. Not a “tool” role. Not a “function” role. Just user.
The logic makes sense once you see it: the messages array captures the conversation between two sides — the model (assistant) and everything else (user). Your application is on the user side. Tool execution happens on your side. So tool results are user messages.
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": "toolu_abc123",
"content": "Customer order #12345 found: shipped March 15"
}]
}
Consecutive same-role messages get merged
If you accidentally send two user messages in a row, the API doesn’t reject them. It silently merges them into one. This isn’t something to rely on intentionally, but it means the API is forgiving of structural imperfections. The strict alternation pattern (user → assistant → user → assistant) is the expected flow, but not rigidly enforced.
System prompt vs user message: different jobs
System instructions shape how the model behaves across the entire conversation — tone, role, constraints. User messages provide what the model should respond to in this specific turn. The system prompt is like a job description. User messages are the tasks that come in.
One-liner: Only two roles exist — user and assistant — and tool results are user messages because your application is on the user side.