# Messages, system, and the request shape

Web page: https://academy.memberpress.ai/courses/api-fundamentals/l2/

Lesson 2 of 9 in [Anthropic API Fundamentals](https://academy.memberpress.ai/courses/api-fundamentals/), 15 min.

Video: https://www.youtube.com/watch?v=MeDnbxz2cu8

The request shape is small — four fields cover 90% of production use. Getting them wrong in subtle ways produces errors that look unrelated to the actual problem.

**The three required fields**

Every call to `POST /v1/messages` needs `model`, `max_tokens`, and `messages`.

`model` names which Claude version to use. Use `claude-opus-4-5` for this course unless you have a reason to deviate. Model names are stable; Anthropic does not silently reassign them.

`max_tokens` is a hard ceiling on output length. The API stops generating when it hits this limit and returns `stop_reason: "max_tokens"`. Set it deliberately — a value of 4096 on a route that only needs short answers wastes money and inflates latency. A value of 128 on a summarizer will produce truncated summaries.

`messages` is a list of turn objects. Each object has `role` (`"user"` or `"assistant"`) and `content` (a string, or a list of content blocks — more on that in the tool-use course).

**What `system` actually does**

`system` is a separate top-level field, not part of `messages`. Anthropic processes it before any user turn. Use it for persistent instructions that should frame the entire conversation: persona, output format, language, constraints.

```bash
curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-4-5",
    "max_tokens": 512,
    "system": "You are a terse technical assistant. Reply in plain text only, no markdown.",
    "messages": [
      {"role": "user", "content": "Explain what an API key is."}
    ]
  }'
```

The system instruction holds for every turn in the conversation. A common mistake is putting persistent instructions in the first user message. That works until the user's next message overrides the framing, or until the model weighs the user instruction against itself and produces inconsistent output. Put the constraint in `system` and it stays out of the turn-weighting.

**Multi-turn shape**

The API is stateless. You maintain the conversation history yourself by building up the `messages` array across calls.

```python
# scripts/multiturn.py
import anthropic, os

client = anthropic.Anthropic()
history = []

def turn(user_text):
    history.append({"role": "user", "content": user_text})
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=512,
        system="You are a concise assistant.",
        messages=history,
    )
    reply = response.content[0].text
    history.append({"role": "assistant", "content": reply})
    return reply

print(turn("My name is Dana."))
print(turn("What is my name?"))
```

Run this. The second reply should use "Dana" — because the assistant turn containing that information is in `history` when the second call fires. Now remove the line that appends the assistant reply and run again:

```python
# broken version — do not append assistant reply
def turn_broken(user_text):
    history.append({"role": "user", "content": user_text})
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=512,
        system="You are a concise assistant.",
        messages=history,
    )
    # missing: history.append({"role": "assistant", ...})
    return response.content[0].text
```

The second call will not know the name. More than that, the `messages` array now contains two consecutive user turns, which violates the alternating-roles requirement and will return a `400`. That is the entire statefulness model of the API — you own the array, you own the state.

**The `content` field in detail**

Content can be a plain string: `"content": "Hello."` That is the shorthand. The full form is a list of content blocks: `"content": [{"type": "text", "text": "Hello."}]`. Both are valid. The API always returns the list form in responses. When you build tools or handle images, you will use the list form for the request too. For now, the string shorthand is fine for text-only requests.

**If you get stuck**

- `400` with a message about `messages`: the array is malformed, empty, or the last message has `role: "assistant"` instead of `"user"`. The final message in the array must have `role: "user"`.
- `400` about alternating roles: the `messages` array must strictly alternate `user` / `assistant`. You cannot send two user turns in a row without an assistant turn between them. The most common cause is forgetting to append the assistant reply before the next user message.
- System instruction not holding: confirm it is in the top-level `system` field, not inside `messages`. A system-style instruction placed as the first user message will have lower weight than a true system prompt.
- The model ignores `max_tokens`: `max_tokens` is an output ceiling, not a target. The model stops when it finishes naturally or hits the cap — whichever comes first. You cannot force longer output by raising `max_tokens`.

**Cross-reference**

l5 (Rate limits and the cost of context) shows how the `usage.input_tokens` field grows as you extend the history array. The cost compounds faster than most people expect. l3 (Streaming vs. blocking) builds directly on the request shape — everything shown here applies unchanged.

**Deliverable**

A two-turn conversation where the second user message refers to something said in the first turn, and the assistant correctly uses that context. Confirm `usage.input_tokens` is larger on the second call than the first.

## Try these prompts

- Write a system prompt that instructs Claude to reply only in plain text with no markdown, then write the user message that tests it.
- Show the Python messages array for a two-turn conversation where the user says "My name is Dana" and then asks "What is my name?"
- What happens if you send two consecutive user messages without an assistant message between them? Write the error this produces.

Next: [Streaming vs. blocking](https://academy.memberpress.ai/courses/api-fundamentals/l3/)
