← Blog

Claude API: System Message vs User Message

2026-10-07 · 5 min read · SubToAPI Team

The Short Answer

The system message sets the behavior, role, and constraints for the entire conversation — it's instructions about how Claude should act. The user message is the actual content the user wants Claude to respond to — the task, question, or data for a specific turn. In the Claude API, these aren't just different roles in the same array; system is a separate top-level parameter, not a message in the messages list at all.

If you're wondering which one to put your instructions in: durable, conversation-wide rules go in system. Anything that changes per request — the actual question, document, or command — goes in a user message.

How Claude's Message Structure Actually Works

Unlike some chat APIs where "system" is just another role inside the messages array, Claude's API treats it differently:

{
  "model": "claude-3-5-sonnet-20241022",
  "max_tokens": 1024,
  "system": "You are a senior backend engineer. Answer concisely, always include code examples in Go.",
  "messages": [
    { "role": "user", "content": "How do I handle context cancellation in an HTTP handler?" }
  ]
}

Here, system is a sibling of messages, not an element of it. The messages array only ever contains user and assistant roles, alternating turn by turn. There is no role: "system" entry you can slip into that array — Claude will reject it or ignore it depending on the client you use.

This matters because it changes how you should think about prompt design: the system prompt isn't "the first thing said in the chat," it's a persistent configuration layer that applies to every turn, cached and reused across the whole conversation.

What Belongs in the System Message

The system message is the right place for:

Example: a support bot with a stable system prompt:

{
  "system": "You are a support agent for a project management SaaS. Be concise, cite the docs when relevant, and never promise refunds without escalation.",
  "messages": [
    { "role": "user", "content": "My export to CSV keeps failing on large boards." }
  ]
}

What Belongs in the User Message

The user message carries the per-turn payload:

{
  "system": "You are a log analysis assistant. Identify root causes and suggest fixes.",
  "messages": [
    { "role": "user", "content": "2024-03-01 12:03:11 ERROR connection reset by peer at db.query(...)" }
  ]
}

Note the system prompt stays stable across many conversations with different log snippets — that's the signal it's doing its job correctly.

Common Mistakes

Putting everything in the user message. It works, but you lose the benefit of a stable, reusable instruction block, and you often end up repeating the same boilerplate in every request, which wastes tokens and makes behavior inconsistent across calls.

Treating system as a greeting. The system message isn't "Hi, here's some context" — it's closer to a configuration object. Write it like a spec, not like a sentence you'd say out loud to a colleague.

Trying to add a role: "system" message mid-conversation. You can't insert new system instructions partway through the messages array. If you need to change behavior mid-conversation, you either update the system parameter on the next request or communicate the change through a user message (less reliable for persistent behavior changes).

Forgetting that system applies to every turn. If your system prompt says "always respond in under 50 words," that rule applies to turn 1 and turn 20 equally. If you want decaying or conditional instructions, you need to manage that logic yourself and update the system string per request.

A Practical Decision Rule

Ask: if I ran this same request again tomorrow with different user input, would this instruction still apply?

This is also exactly how SubToAPI passes requests through: when you call https://api.subtoapi.app/v1/messages with your sub_live_... key, the system and messages fields map directly onto this same structure, so any prompt design you've already built for Claude works unchanged. Check the docs/messages reference for the exact request shape, or docs/quickstart if you're wiring this up for the first time.

Example: Combining Both Correctly

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-3-5-sonnet-20241022",
    "max_tokens": 512,
    "system": "You are a code reviewer. Point out bugs and security issues only. No style nits.",
    "messages": [
      { "role": "user", "content": "def login(user, pw): return db.query(f\"SELECT * FROM users WHERE name='"'"'{user}'"'"' AND pw='"'"'{pw}'"'"'\")" }
    ]
  }'

The system prompt defines the review policy once; the user message supplies the code to review. Swap the user message for a different snippet and the review policy stays intact without rewriting it.

Questions

Can I put multiple instructions in one system message? Yes — the system parameter is a single string, so combine persona, format rules, and constraints into one well-structured block of text.

Does the system message count toward token usage? Yes, it's included in input tokens on every request, which is why keeping it concise and stable (rather than repeating it across unrelated info) matters for cost and latency.

What if I need different system behavior for different users? Build the system string dynamically per request based on user/account context, then send it as part of each API call — there's no persistent per-user system state on the API side.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →