Claude API Intro
What Is the Claude API?
The Claude API is the service your programs talk to when they use Claude. Your program sends a question over the internet, Claude's servers process it, and the answer comes back as data your program can use.
Technically, the Claude API is a RESTful HTTP API hosted at:
https://api.anthropic.com
Each capability is exposed as an endpoint: a URL path combined with an HTTP method. For example, sending a message to Claude is:
POST https://api.anthropic.com/v1/messages
POSTis the HTTP method. It means "send data to the server and ask it to create something", here a new message from Claude./v1/is the URL version prefix.messagesis the resource you are working with.
Requests and responses use JSON, a plain-text data format that maps directly to Python dictionaries and lists.
The Anthropic Python SDK from Lesson 1 is a wrapper around this API. Everything the SDK does ends up as an HTTP request to one of these endpoints. Understanding the API itself helps you read error messages, debug problems, and make sense of the SDK's design.
The Claude API vs. the Claude App
The most common beginner confusion is between the Claude app and the Claude API.
| Claude app (web, desktop, mobile) | Claude API | |
|---|---|---|
| What it is | A finished chat product | A programmable service |
| Who uses it | People typing in a chat window | Programs written by developers |
| Interface | Graphical chat window | HTTP requests and JSON |
| Account | Claude app account and plan | Claude Console account at platform.claude.com |
| Billing | App subscription plans | Pay per use, measured in tokens |
| Conversation memory | The app keeps your chat history | You send the conversation history yourself with each request |
| Customization | App settings | Full control of prompts, models, limits, and tools |
An app subscription does not include API access, and API usage is not covered by an app plan. They are separate products with separate billing.
Why both exist: the app is for using Claude directly. The API is for building Claude into your own software, such as a support chatbot on your website, a tool that summarizes uploaded documents, or a backend that extracts data from emails.
How a Request Works
Every interaction with the Claude API follows the same request-response cycle:
┌──────────────┐ 1. HTTPS request (headers + JSON body) ┌──────────────┐
│ Your program │ ─────────────────────────────────────────► │ Claude API │
│ │ │ │
│ │ ◄───────────────────────────────────────── │ (runs the │
└──────────────┘ 2. HTTPS response (status + JSON body) │ model) │
└──────────────┘
- Your program sends an HTTPS request. Headers carry metadata such as your credentials and the API version. The body contains the JSON describing what you want.
- The API authenticates you, validates the request, runs the Claude model, and sends back a response. The status code indicates success (
200) or the kind of failure (400,401,429, …). The body contains Claude's reply or an error description.
A Complete Request with cURL
curl is a command-line tool for sending HTTP requests. It is installed by default on macOS, most Linux distributions, and current versions of Windows. Showing the request with curl makes every part of it visible:
curl https://api.anthropic.com/v1/messages \
-H "Authorization: Bearer $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2026-10-21" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5-5",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "What is an API? Answer in one sentence."}
]
}'
Note: This uses macOS/Linux shell syntax.
$ANTHROPIC_API_KEYreads an environment variable that must already contain your key (Lessons 6 and 7 show how). The trailing `` continues the command onto the next line.
Line-by-line explanation:
| Part | Meaning |
|---|---|
curl https://api.anthropic.com/v1/messages | Send a request to the Messages endpoint. curl uses POST automatically because a body (-d) is provided. |
-H "Authorization: Bearer $ANTHROPIC_API_KEY" | Proves who you are. Bearer means "the token that follows is my credential". |
-H "anthropic-version: 2026-10-21" | Declares which version of the API's request/response format your code expects. |
-H "content-type: application/json" | Tells the server the body is JSON. |
-d '{...}' | The request body. |
"model" | Which Claude model should answer. |
"max_tokens" | The maximum length of the output, in tokens, including any thinking the model does. |
"messages" | The conversation: a list of turns, each with a role and content. |
The Response
A successful request returns HTTP status 200 and a JSON body like this (simplified; real responses include a few more fields):
{
"id": "msg_01XFDUDYJgAACzvnptvVoYEL",
"type": "message",
"role": "assistant",
"model": "claude-opus-5-5",
"content": [
{
"type": "text",
"text": "An API is a defined set of rules that lets one program request data or services from another program."
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 18,
"output_tokens": 24
}
}
| Field | What it tells you |
|---|---|
id | Unique identifier of this message |
type | The object type: always "message" for a successful reply |
role | "assistant": the message was written by Claude |
model | The model that generated the reply |
content | A list of content blocks. Here there is one text block. With current models, the list can also start with a thinking block (the model's internal reasoning) before the text, and later lessons cover other block types, such as tool calls. |
stop_reason | Why Claude stopped. end_turn means it finished naturally; max_tokens means it hit your limit. |
stop_sequence | The custom stop text that ended generation, if any (Lesson 48) |
usage | How many tokens you sent (input_tokens) and received (output_tokens, which includes any thinking). These numbers determine cost. |
Why content is a list, not a string: a single reply can contain several kinds of output, such as reasoning, text, and a request to call a tool. A list of typed blocks can represent all of these consistently. Because a thinking block can come first, code must find the text blocks by their type instead of assuming the first block is text. This is why Lesson 1 looped over message.content.
Request Headers in Detail
Headers are key-value metadata sent with every request. The Claude API uses these:
| Header | Value | Required? |
|---|---|---|
Authorization | Bearer <your API key> | Yes, unless x-api-key is sent |
x-api-key | <your API key> | Legacy alternative to Authorization; still supported |
anthropic-version | An API version such as 2026-10-21 | Yes |
content-type | application/json | Yes, for requests with a JSON body |
anthropic-workspace-id | A workspace ID such as wrkspc_01Jw… | Only for API keys that are not scoped to a single workspace (Lesson 6) |
Authentication: Authorization and x-api-key
Both headers carry the same API key. Authorization: Bearer <key> is the current standard form, and x-api-key: <key> is the older form that the API still accepts. New code should use Authorization.
Note: The Python SDK sends
Authorization: Bearer <key>starting with version 2.0.0. Earlier SDK versions sentx-api-key. This only matters if a proxy or gateway in your infrastructure inspects one of these headers (see Lesson 5).
What happens if authentication is missing or wrong: the API returns status 401 with an authentication_error, and no model runs. You are not billed for requests that fail authentication.
API Versioning: anthropic-version
The API evolves over time. When Anthropic makes a change that would break existing code, such as removing a parameter, it publishes a new API version, identified by a date. Your request states which version it was written for, and the API behaves according to that version.
| API version | Notes |
|---|---|
2023-06-01 | Long-standing version, sent by SDK releases before Python SDK 2.0.0 |
2026-10-21 | Current version, sent by Python SDK 2.0.0 and later |
Why the header exists: it lets Anthropic improve the API without breaking applications that were written years ago. Your code changes behavior only when you move to a new version.
What happens if you omit it: the request is rejected with an error. What happens if you change it: the request and response format of that version applies. For example, 2026-10-21 rejects the temperature, top_p, and top_k parameters that 2023-06-01 accepted (Lesson 19).
When you use the SDK, this header is set automatically to match your SDK's major version.
The APIs Available
"The Claude API" is a family of related APIs that share the same base URL, authentication, and conventions:
| API | Endpoint | Purpose | Lessons |
|---|---|---|---|
| Messages | POST /v1/messages | Send a conversation and receive Claude's reply | 11–30 |
| Token Counting | POST /v1/messages/count_tokens | Count input tokens before sending a request | 44–46 |
| Message Batches | POST /v1/messages/batches | Process large numbers of requests asynchronously, at a 50 percent cost reduction | 131–140 |
| Models | GET /v1/models | List available models and their details | 12 |
| Files | POST /v1/files, GET /v1/files | Upload files once and reference them in many requests | 115–116 |
The Messages API is the core of almost everything in this tutorial. Streaming, tool use, vision, structured output, prompt caching, and thinking are all features of the Messages API, controlled through its request parameters.
Statelessness: The API Does Not Remember
A standard Messages API request is stateless: the API does not remember earlier requests. Each request must contain everything Claude needs to answer it.
This surprises developers who are used to the Claude app, where the chat "remembers" what you said. In the API, if you want Claude to know about a previous question and answer, you must send them again:
{
"model": "claude-opus-5-5",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "My name is Priya."},
{"role": "assistant", "content": "Nice to meet you, Priya!"},
{"role": "user", "content": "What is my name?"}
]
}
Claude can answer "Priya" only because the earlier turns are included in this request. If you sent only the last message, Claude would have no way of knowing.
Why the API is designed this way:
- Control. You decide exactly what context Claude sees. You can edit, shorten, or summarize the history.
- Simplicity and scale. The server does not need to store a session for every user of every application.
- Predictability. The same request produces a reply based only on what is in it.
The trade-off: your application stores the history, and longer histories mean more input tokens and therefore higher cost. Lessons 24–28 and 62–64 show how to manage this.
Tokens: How Usage Is Measured
A token is a small piece of text, usually a word or part of a word. Models read and write text as tokens, and the API measures usage in tokens:
- Input tokens: everything you send, including the system prompt, the conversation history, and any documents or images.
- Output tokens: everything Claude generates, including thinking that is not shown in the response.
Tokens matter for three practical reasons:
- Cost. API pricing is per token, with separate rates for input and output, and rates vary by model. Current prices are listed on Anthropic's pricing page (and in Lesson 12).
- Limits.
max_tokenscaps the length of the output, including thinking. Each model also has a maximum context window, which is the total number of tokens it can consider at once. - Rate limits. Your account can process only a certain number of tokens per minute (described below).
The usage field in each response tells you exactly how many tokens were used. Lessons 13 and 44–47 cover counting and controlling tokens.
Errors
When a request fails, the API returns a non-200 status code and a JSON error body with a consistent structure:
{
"type": "error",
"error": {
"type": "not_found_error",
"message": "The requested resource could not be found."
},
"request_id": "req_011CSHoEeqs5C35K2UUqR7Fy"
}
error.typeis a machine-readable category you can branch on in code.error.messageis a human-readable explanation.request_ididentifies this exact request. Include it when you contact Anthropic support.
The exact message text varies; the structure is what your code should rely on.
Common error types:
| Status | error.type | Typical cause | Retry? |
|---|---|---|---|
| 400 | invalid_request_error | Malformed request, missing or unsupported parameter | No: fix the request |
| 401 | authentication_error | Missing, invalid, or expired API key | No: fix the key |
| 402 | billing_error | A problem with billing or payment details | No: fix billing in the Console |
| 403 | permission_error | The key lacks permission for this action | No |
| 404 | not_found_error | Unknown resource, such as a misspelled model name | No |
| 413 | request_too_large | Request body exceeds the size limit | No: reduce the size |
| 429 | rate_limit_error | Too many requests or tokens per minute | Yes, after a delay |
| 500 | api_error | Unexpected error on Anthropic's side | Yes |
| 529 | overloaded_error | The API is temporarily overloaded | Yes, after a delay |
Why the distinction matters: client errors (4xx, except 429) mean your request is wrong, and sending it again will fail the same way. Rate-limit and server errors are usually temporary, so retrying after a short wait often succeeds. The SDK uses this distinction to decide what to retry automatically (Lesson 1).
Response Headers
Besides the body, every response includes useful headers:
| Header | Meaning |
|---|---|
request-id | Globally unique ID of the request, for debugging and support |
anthropic-organization-id | The organization the API key belongs to |
anthropic-workspace-id | The workspace the request ran in |
Rate-limit headers are also included. They report how much of your limit remains, and Lesson 142 uses them.
Limits: Rate Limits, Spend Limits, and Request Size
Rate Limits and Spend Limits
To protect capacity and prevent runaway costs, each organization has limits based on its usage tier. Organizations start on a tier automatically and can move up over time:
- Rate limits: the maximum requests per minute and tokens per minute.
- Spend limits: the maximum monthly cost of API usage.
You can see your current limits in the Claude Console under Rate limits, and spend limits on the Billing page. Exceeding a rate limit returns a 429 error.
Request Size Limits
| Endpoint | Maximum request size |
|---|---|
| Messages, Token Counting | 128 MB |
| Message Batches | 256 MB |
| Files | 500 MB |
A larger request returns 413 request_too_large. Cloud platforms such as Amazon Bedrock have their own, smaller limits.
Direct API vs. Cloud Platforms
You can reach Claude models in two ways:
| Direct Claude API | Cloud platforms | |
|---|---|---|
| Where | api.anthropic.com | Amazon Bedrock, Claude Platform on AWS, Google Cloud, Microsoft Foundry |
| Account and billing | Claude Console, billed by Anthropic | Your cloud provider account and billing |
| Authentication | API key (or Workload Identity Federation) | The cloud provider's identity system (IAM) |
| Feature availability | Newest models and features first | Varies by platform |
| Best for | New projects, full feature access | Organizations already committed to a cloud provider |
This tutorial uses the direct Claude API. Lessons 161–170 cover the cloud platforms.
The Same Request: Raw HTTP vs. SDK
The raw request in Python, using the requests library:
import os
import requests
response = requests.post(
"https://api.anthropic.com/v1/messages",
headers={
"Authorization": f"Bearer {os.environ['ANTHROPIC_API_KEY']}",
"anthropic-version": "2026-10-21",
"content-type": "application/json",
},
json={
"model": "claude-opus-5-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "What is an API? Answer in one sentence."}],
},
timeout=60,
)
if response.status_code != 200:
error = response.json()["error"]
print(f"Error {response.status_code} ({error['type']}): {error['message']}")
else:
data = response.json()
text = "".join(block["text"] for block in data["content"] if block["type"] == "text")
print(text)
print("Request ID:", response.headers.get("request-id"))
The same request with the SDK:
import anthropic
from anthropic import Anthropic
client = Anthropic()
try:
message = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "What is an API? Answer in one sentence."}],
)
except anthropic.APIStatusError as error:
print(f"Error {error.status_code}: {error.message}")
else:
text = "".join(block.text for block in message.content if block.type == "text")
print(text)
print("Request ID:", message._request_id)
How the two map to each other:
| Raw API concept | SDK equivalent |
|---|---|
URL POST /v1/messages | client.messages.create() |
Authorization header | Read from ANTHROPIC_API_KEY automatically |
anthropic-version header | Set automatically by the SDK |
| JSON body fields | Keyword arguments (model=, max_tokens=, messages=) |
| Response JSON | A typed Message object |
Text blocks: block["type"] == "text" and block["text"] | block.type == "text" and block.text |
| Error body + status code | Exceptions such as anthropic.APIStatusError |
request-id response header | message._request_id |
Both programs send essentially the same HTTP request. Knowing the raw form lets you understand what the SDK is doing and debug it when something goes wrong.
Note: Both examples collect the text by filtering blocks on their
typeand joining them, instead of readingcontent[0]. With current models such as Claude Opus 5.5, the first block can be a thinking block, which has notextfield. Position-based access then fails with aKeyError(raw JSON) orAttributeError(SDK). Lesson 16 explains every block type.
Common Mistakes
Expecting the API to Remember Previous Requests
Mistake: sending only the newest user message and expecting Claude to know the earlier conversation.
Why it happens: the Claude app keeps history, so developers assume the API does too.
Result: Claude answers as if the conversation just started. For example, it does not know the user's name mentioned earlier.
Fix: keep the conversation history in your application and send the relevant turns with every request (Lessons 24–26).
Assuming an App Subscription Includes API Access
Mistake: trying to use a Claude app plan for API calls.
Fix: create a Claude Console account at platform.claude.com, set up billing there, and create an API key (Lesson 6).
Retrying Errors That Cannot Succeed
Mistake: automatically retrying every failed request, including 400 and 401 errors.
Result: wasted time and log noise, because the same invalid request fails again every time.
Fix: retry only 429, 5xx, and connection errors. The SDK already does this by default.
Treating content as a String, or Reading content[0]
Mistake: print(data["content"]), string operations on content, or reading content[0] and assuming it is text.
Fix: content is a list of blocks, and the first one may be a thinking block. Read text from the blocks whose type is "text".
Ignoring request_id
Mistake: logging only "request failed".
Fix: always log the request_id (or message._request_id in the SDK). It is the fastest way to investigate a failed or unexpected request with support.
Best Practices
- Use the SDK for application code. Use raw HTTP for learning, debugging, or languages without an official SDK.
- Design for statelessness from the start. Decide where conversation history is stored, such as memory, a database, or a cache, and how it is trimmed.
- Extract text by block type, never by position.
- Watch
usageon every response. Token counts are your cost. Logging them early prevents billing surprises. - Branch on
error.typeand status codes, not on error message text, which can change. - Know your limits. Check your rate-limit tier in the Console before load-testing or launching a feature with heavy traffic.
- Keep the API version deliberate. Upgrade SDK major versions, and therefore API versions, intentionally, with testing (Lesson 5).
Exercise
- Using
curlor therequestsexample above, send a request and identify theid, the block types incontent,stop_reason, andusagein the response. - Send the request again with
"max_tokens": 5. What is thestop_reasonnow, and what doescontentcontain? - Change the
modelto"claude-does-not-exist". Which status code anderror.typedo you get? - Send two separate requests: first
"My favorite color is green.", then"What is my favorite color?". Then send one request that contains both turns, with a short assistant reply in between. Explain why the results differ.
idstarts withmsg_.contentcontains a text block, sometimes preceded by a thinking block.stop_reasonis usuallyend_turn, andusageshowsinput_tokensandoutput_tokens.stop_reasonbecomesmax_tokens, because the 5-token limit was reached before Claude finished. Since thinking also counts toward the limit,contentmay hold a very short, cut-off text block, or only a thinking block with no text at all.- You receive a
404status witherror.typeset tonot_found_error, because the model does not exist. - In separate requests, the second request contains no information about your favorite color, because the API is stateless. In the combined request, the earlier turn is part of the input, so Claude can answer "green".