Your first LLM call
Send messages to a model and read its reply, tokens and stop reason.
Why it matters
Every AI product you've used, from chat apps to writing assistants to agents, is built on the request you're about to send. The chat window is a website making this exact HTTP call for you. Once you can make it yourself, you can put a model inside any script, spreadsheet export or workflow. Everything in Phase 1 builds on this one call.
What you'll be able to do
- Send a request to a model with a system prompt and a user message
- Pull the reply text out of the response, whatever blocks it contains
- Check stop_reason and token usage before trusting a reply
- Continue a conversation by sending the whole history
What it covers
- An LLM is an API
- The request body
- Making the call from Python
- Reading the response
- Conversations: send the whole history
- Worked example: a reusable ask() function
An LLM is an API
You already know every piece of this. Think of it as filling in an order form and posting it: the envelope says who it's from and where it goes, and the form inside says what you want. Calling Claude is a POST request with three headers (the envelope) and a JSON body (the form):
POST https://api.anthropic.com/v1/messages x-api-key: <your API key> anthropic-version: 2026-10-21 content-type: application/json
{
"model": "claude-opus-5-5",
"max_tokens": 1000,
"system": "You write email subject lines for an outdoor clothing brand.",
"messages": [
{"role": "user", "content": "Write one subject line for our spring sale."}
]
}
The headers carry your key (x-api-key), the version of the API your code was written for (anthropic-version), and the body's format. The body says which model to use and what to send it.
Note: In the academy, requests to
api.anthropic.comgo through the academy's own server, which attaches the real API key. So thex-api-keyyour code sends is replaced. If the academy has no key set up, you get a clearly labeled simulated reply with the same shape as a real one. On your own computer, you'd read your key from an environment variable, exactly as in the last lesson.
The request body
| Field | Required | What it does |
|---|---|---|
model | yes | Which model answers. claude-opus-5-5 is Anthropic's main model; smaller ones are faster and cheaper (Phase 1 covers choosing). |
max_tokens | yes | The most the reply may contain, in tokens. If the model reaches it, the reply is cut off. |
messages | yes | The conversation so far: a list of {"role": ..., "content": ...} dictionaries. |
system | no | Standing instructions: who the model is, what it's for, rules to follow. |
A token is the unit models read and write: a word or a piece of a word. In English, one token is roughly four characters, so 1,000 tokens is about 750 words. Providers measure limits and prices in tokens.
Roles. Each message has a role: "user" for what you (or your app's user) say, and "assistant" for what the model said earlier. The list must start with a user message. Instructions for the whole conversation go in system, not in the messages.
Note: Older tutorials also set
temperaturein the body. The current API version (2026-10-21) no longer accepts it, and returns an error. Phase 5 explains what temperature does inside a model.
Build a request below and send it. The panel shows the JSON body and the matching Python.
Change the system prompt and send again to see how much it steers the reply.
Making the call from Python
The same request with requests:
import os import requests api_key = os.environ.get("ANTHROPIC_API_KEY", "added-by-the-academy-server") response = requests.post( "https://api.anthropic.com/v1/messages", headers={ "x-api-key": api_key, "anthropic-version": "2026-10-21", "content-type": "application/json", }, json={ "model": "claude-opus-5-5", "max_tokens": 1000, "system": "You write short email subject lines for an outdoor clothing brand.", "messages": [ {"role": "user", "content": "Write one subject line for our spring sale."}, ], }, timeout=60, ) response.raise_for_status() data = response.json() for block in data["content"]: if block["type"] == "text": print(block["text"]) print("stop_reason:", data["stop_reason"]) print("tokens:", data["usage"])
Everything from the requests lesson applies. raise_for_status() turns a 401 (bad key), 400 (something wrong in the body) or 429 (too many requests) into an exception. Without it, you'd get a confusing KeyError later. Model calls can take many seconds, so the time limit is generous.
Reading the response
The reply's text is inside content, a list of blocks. Each block has a type. The one you usually want is "text".
Claude Opus 5.5 is a reasoning model that can think before it answers. So the list may start with a "thinking" block, followed by the "text" block. (The thinking text is left empty unless you ask for a summary.) That's why the code above loops over the blocks and keeps the text ones. The shortcut data["content"][0]["text"] works only when the first block happens to be text. It fails with a KeyError when it isn't.
Two more fields deserve a look every time:
stop_reason says why the model stopped:
"end_turn": it finished its answer."max_tokens": it hit yourmax_tokenslimit, so the reply is incomplete. Thinking counts toward the limit too. So a very lowmax_tokenscan end the reply before any text at all. Raise the limit, or treat the reply as unusable.- Other values, such as
"tool_use"and"refusal", appear when you use features from Phase 1.
usage counts input_tokens (everything you sent) and output_tokens (everything it wrote, including thinking). You're billed for both, per million tokens. Output tokens cost several times more than input tokens. Check the provider's pricing page for current numbers.
import requests response = requests.post( "https://api.anthropic.com/v1/messages", headers={"x-api-key": "added-by-the-academy-server", "anthropic-version": "2026-10-21"}, json={ "model": "claude-opus-5-5", "max_tokens": 10, "messages": [{"role": "user", "content": "Write a detailed launch plan for a new running shoe."}], }, timeout=60, ) data = response.json() text = "" for block in data["content"]: if block["type"] == "text": text += block["text"] print(data["stop_reason"]) if data["stop_reason"] == "max_tokens": print("Cut off after", len(text), "characters of text. Don't use this reply as-is.")
reply = {"content": [{"type": "text", "text": "Hello!"}], "stop_reason": "end_turn"}
print(reply["content"][0]["text"])Show the answer
Hello!
reply["content"] is a list with one block, [0] takes that block, and ["text"] takes its words: Hello! The words live inside a list of content blocks, which is why it takes both steps.
"stop_reason": "max_tokens". What does that tell you?Show the answer
The reply was cut off at your limit and may be incomplete
It hit max_tokens before finishing. Raise the limit or don't rely on the reply.
Conversations: send the whole history
The API doesn't remember anything between requests. Each call stands alone, like a new caller reaching a help line with no notes from last time. To ask a follow-up, you send the earlier turns again, with the model's previous reply as an "assistant" message:
messages = [
{"role": "user", "content": "Write one subject line for our spring sale."},
{"role": "assistant", "content": "Rain jackets 30% off, through Sunday only"},
{"role": "user", "content": "Make it shorter and mention free returns."},
]
Chat apps do exactly this behind the scenes, resending the conversation each turn. It's also why long conversations cost more: every turn re-sends everything before it as input tokens.
SDKs wrap this same call
Providers publish official Python packages, called SDKs, that build these requests for you. With Anthropic's anthropic package (pip install anthropic), the same call looks like this:
import anthropic client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment message = client.messages.create( model="claude-opus-5-5", max_tokens=1000, messages=[{"role": "user", "content": "Write one subject line for our spring sale."}], ) for block in message.content: if block.type == "text": print(block.text)
Same fields, same response, plus automatic retries and clearer errors. You'll use the SDK in Phase 1. Because you know the raw HTTP underneath, none of it will feel like magic. And you can fix it when it breaks.
Show the answer
Nothing: each request stands alone, so you must resend the history
The API keeps no memory between calls. Chat apps resend the whole conversation every turn.
Worked example: a reusable ask() function
Wrap the call in a function you can reuse: it sends one question, refuses to return a cut-off reply, and gives back the text plus the token counts.
import requests def ask(question, system="You write short email subject lines for an outdoor clothing brand.", max_tokens=1000): response = requests.post( "https://api.anthropic.com/v1/messages", headers={"x-api-key": "added-by-the-academy-server", "anthropic-version": "2026-10-21"}, json={ "model": "claude-opus-5-5", "max_tokens": max_tokens, "system": system, "messages": [{"role": "user", "content": question}], }, timeout=60, ) response.raise_for_status() data = response.json() if data["stop_reason"] == "max_tokens": raise ValueError("The reply was cut off. Raise max_tokens.") text = "".join(block["text"] for block in data["content"] if block["type"] == "text") return text, data["usage"] for product in ["rain jackets", "trail shoes"]: text, usage = ask(f"Write one subject line for our {product} sale.") print(product, "->", text[:80]) print(" tokens in/out:", usage["input_tokens"], usage["output_tokens"])
Each question becomes one request, and the function hides the details: the headers, the loop over content blocks, and the cut-off check. Without an API key you'll see the academy's labeled simulated reply. With one, you'll see two real subject lines.
The token counts show what each call cost. The input count includes the system prompt, so it's sent, and billed, on every call. Functions like ask are how real projects keep model calls in one place, so a change of model or limit is a one-line edit.
Show the answer
About 300 words
One token is roughly four characters, or three-quarters of a word in English. 400 tokens is about 300 words, and thinking uses some of that too.
Practice what you just read
- 5 code examples you can edit and run
- a practice exercise that checks your code
- a 5-question quiz
- 5 review cards for spaced repetition
Try a quiz question
Where do standing instructions like "You write subject lines for an outdoor brand; no exclamation marks" belong?