Every Claude application, however elaborate, is built on one endpoint: POST /v1/messages. A request needs a model string, a max_tokens cap, and a messages array of {role, content} turns alternating user and assistant. Everything else — system, temperature, tools, tool_choice, stream, stop_sequences, metadata — is optional. The system prompt is not a message — it is a separate top-level system parameter, which matters because the exam likes to test whether you know a system prompt cannot appear mid-conversation as a message with role system. (Some current models do support a distinct, separately-documented mid-conversation system-turn feature for injecting fresh context deep in a long tool-use loop — but that is a different mechanism from the top-level system parameter, and it does not change the rule that messages entries are only ever user or assistant.)
content inside a message can be a plain string for a simple text turn, or an array of typed blocks — text, image, document, tool_use, tool_result — when the turn needs more than one part. Roles must alternate, but the API tolerates two consecutive messages of the same role by treating them as one logical turn; it does not tolerate starting the array with an assistant message with nothing before it. max_tokens is a hard output cap, not a target — Claude does not try to fill it, and hitting it mid-generation truncates the response, which is a different outcome from Claude choosing to stop.
Reading a response
A response carries an id, type: "message", a role of assistant, the model that actually served the request, a content array of typed blocks, a stop_reason, an optional stop_sequence (populated only when a custom stop sequence triggered the stop), and a usage object with input_tokens, output_tokens, and, when caching is in play, cache_creation_input_tokens and cache_read_input_tokens. content is an array, not a single string, because one turn can legitimately hold explanatory text and a tool call in the same response — code that assumes content[0] is always the whole answer breaks the moment a tool call shows up. The block types you'll see depend on what the request asked for: plain requests return text; a request with tools can return tool_use; a request with extended thinking enabled returns thinking (or, rarely, redacted_thinking) ahead of the final text.
Key concept
stop_reason is what tells your application loop what happened, not the presence or absence of text. The values worth knowing cold: end_turn (natural completion), max_tokens (hit the output cap mid-generation — treat the content as truncated, not finished), stop_sequence (a custom sequence you supplied was hit), tool_use (Claude wants a tool executed before it can continue), pause_turn (a long-running server-side tool turn paused and should be resumed by sending the response straight back), and refusal (the model declined to continue on an otherwise normal 200 response). Treat anything other than end_turn as "not finished, check why" rather than assuming it's always tool_use.
Multi-turn conversations and statelessness
The API is stateless: there is no server-side conversation object, no session id, nothing you can reference on a later call to mean "continue where we left off." Every call resends the full message history the model needs, including prior assistant turns and any tool results, or Claude genuinely has no memory of anything said before the messages you actually included. This is why context management (Domain 4) and prompt caching (Lesson 1.4) both exist — a long conversation means a growing, re-sent payload on every single call, and both the cost and the latency of a turn scale with how much history you're carrying, not with how long the conversation has felt to the user.
A subtler consequence: because the client owns the entire history, the client can also edit it. Trimming an old tool result, summarizing a stale portion of the conversation, or dropping an irrelevant early turn are all legitimate application-level techniques — the API has no opinion about conversation history beyond validating role alternation and block structure on the call you actually send.