Anthropic SDK Intro

Su Suriya Ravichandran Updated 10 Nov 2026
28 min read ·Lesson 1 of 200

What Is the Anthropic Python SDK?

The Anthropic Python SDK is the official Python library for using Claude from your own programs. Instead of building web requests by hand, you call normal Python methods like client.messages.create(...). The library handles the network communication for you.

In technical terms, the SDK is a client library for the Claude API. The Claude API is the HTTP (REST) service Anthropic runs at https://api.anthropic.com. Each SDK method maps to one API endpoint. When you call a method, the SDK:

  1. builds an HTTP request,
  2. adds the required headers,
  3. sends the request,
  4. waits for the response, and
  5. turns the JSON it gets back into typed Python objects.

You work with Python functions and objects. The SDK deals with URLs, headers, status codes, and JSON parsing.

These are the names you will use throughout this tutorial:

ItemValueWhere you use it
Package name on PyPIanthropicpip install anthropic
Module nameanthropicimport anthropic
Synchronous client classAnthropicfrom anthropic import Anthropic
Asynchronous client classAsyncAnthropicfrom anthropic import AsyncAnthropic
Default API key variableANTHROPIC_API_KEYRead automatically from the environment
Required Python version3.10 or laterCheck with python --version

Note: The package, the module, and the class have related but different names. You install anthropic, you import anthropic, and you create an Anthropic object (capital A). Mixing these up is one of the most common beginner errors.

Terms Used in This Lesson

Several terms appear repeatedly in this lesson. Each one is explained below in plain language, followed by its technical meaning.

TermSimple meaningTechnical meaning
APIA doorway that lets one program ask another program to do somethingApplication Programming Interface: a defined set of operations, inputs, and outputs
REST APIAn API you reach over the web with URLsAn HTTP-based API where each URL (endpoint) is a resource, and HTTP methods (GET, POST, …) are actions on it
EndpointOne specific "address" of the APIA URL path plus HTTP method, such as POST /v1/messages
SDKA ready-made toolkit that makes an API easy to use from one languageSoftware Development Kit: a library that wraps the API in language-native functions and types
ClientThe object in your code that talks to the APIAn instance (such as Anthropic()) that stores configuration and sends requests
JSONThe text format used to send data back and forthJavaScript Object Notation: the request and response body format of the Claude API

Why the SDK Exists

Any language that can send HTTP requests can call the Claude API. So why use a dedicated library? The best way to answer is to send the same request both ways and compare them.

Calling Claude Without the SDK

The script below calls the Messages endpoint directly with the general-purpose requests library (pip install requests):

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": "Explain what an SDK is in one sentence."}
        ],
    },
    timeout=60,
)

response.raise_for_status()   # Raise an exception for 4xx/5xx responses
data = response.json()        # Convert the JSON text into a Python dict

for block in data["content"]:
    if block["type"] == "text":
        print(block["text"])

This works, but your code now has to handle every protocol detail itself:

  • The URL. You must know the base address and the exact path, /v1/messages.
  • Authentication. You must read the key yourself and send it in the Authorization header as Bearer <key>. The older x-api-key: <key> header is still accepted as an alternative. If you leave authentication out, the API rejects the request as unauthenticated.
  • The API version. The anthropic-version header tells the API which version of its request and response format you expect. If you leave it out, the request is rejected. If you send an old value, you get the old behavior. You have to choose and maintain this value.
  • The content type. You declare that the body is JSON.
  • Parsing. The response is a plain dictionary. A typo such as data["contnet"] fails only at runtime with a KeyError, and your editor cannot warn you about it beforehand.
  • Failures. raise_for_status() throws a generic HTTPError. To tell an invalid key apart from a rate limit, you have to inspect status codes yourself.
  • Reliability. If the network drops for a moment or the API is briefly overloaded, the request fails. Retrying with a delay is your job.
  • Timeouts. You must choose a timeout. Without one, requests can wait indefinitely.

Every one of these lines is a place where bugs can appear. Every project that calls Claude would need to repeat them.

The Same Request With the SDK

from anthropic import Anthropic

client = Anthropic()  # Reads ANTHROPIC_API_KEY from the environment

message = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "Explain what an SDK is in one sentence."}
    ],
)

for block in message.content:
    if block.type == "text":
        print(block.text)

How this example works:

  • Anthropic() creates a client. No key is passed, so the SDK looks for the ANTHROPIC_API_KEY environment variable. Keeping the key out of your source code is the recommended pattern.
  • client.messages.create(...) sends POST /v1/messages. The SDK adds the URL, the Authorization header, the anthropic-version header, and the content type automatically.
  • The keyword arguments model, max_tokens, and messages are the same fields you put in the JSON body before. They are now Python parameters, so your editor can autocomplete and check them.
  • message is a typed Message object, not a dictionary. You read block.type and block.text as attributes.
  • The loop goes through message.content because Claude's reply is a list of content blocks, not a single string. With current models, the list can begin with a thinking block (the model's internal reasoning) before the text block, so the loop prints only the blocks whose type is "text". Lesson 16 (Understand Message Blocks) covers this structure in detail.

The request sent over the network is essentially the same in both versions. The difference is how much of the plumbing your code must own.

Side-by-Side Comparison

ConcernRaw HTTP (requests)Anthropic SDK
Endpoint URLYou write itBuilt in
Authentication headerYou add Authorization: Bearer <key>Added from ANTHROPIC_API_KEY or api_key=
anthropic-version headerYou choose and maintain itSet automatically for your SDK version
Request parametersUntyped dictTyped parameters with editor autocomplete
ResponsePlain dictTyped objects (Pydantic models)
ErrorsGeneric HTTPError + status codeSpecific exceptions (AuthenticationError, RateLimitError, …)
RetriesWrite your ownTwo automatic retries with exponential backoff by default
TimeoutsChoose your own (none by default in requests)10-minute default, configurable
StreamingParse server-sent events manuallyBuilt-in helpers
Async supportRequires a different HTTP libraryAsyncAnthropic with the same interface

How the SDK Works Behind the Scenes

You do not need to know the internals to use the SDK. Knowing the general flow, however, makes errors and configuration options much easier to understand.

Your Python code
      │  client.messages.create(model=..., max_tokens=..., messages=[...])
      ▼
Anthropic client
      │  1. Resolves the API key (argument or ANTHROPIC_API_KEY)
      │  2. Builds the request: URL, JSON body, headers
      │  3. Applies timeout and retry settings
      ▼
HTTP layer (httpx2)
      │  4. Sends the HTTPS request to api.anthropic.com
      ▼
Claude API
      │  5. Runs the model and returns a JSON response (or an error)
      ▼
Anthropic client
      │  6a. Success → parses JSON into a typed Message object
      │  6b. Retryable failure → waits, then retries automatically
      │  6c. Final failure → raises a specific Python exception
      ▼
Your Python code receives a Message object or catches an exception

Step by step:

  1. Authentication is resolved. If you passed api_key=... to the constructor, the SDK uses it. Otherwise it reads ANTHROPIC_API_KEY. This is why Anthropic() with no arguments works once the environment variable is set.
  2. The request is built. Your keyword arguments become the JSON body. The SDK adds the anthropic-version header that matches its own major version (covered in the versioning section below).
  3. Policies are applied. Each request inherits the client's timeout and retry settings, unless you override them for that call.
  4. The request is sent. The SDK uses httpx2, an API-compatible fork of the popular httpx HTTP library, for connections, connection pooling, and TLS.
  5. The API responds. It returns either a successful JSON body or an error status code, such as 401 or 429.
  6. The response is converted. Successful responses become typed Python objects. Temporary failures, such as connection problems or 429/5xx responses, are retried. Errors that cannot be recovered become specific exceptions you can catch.

The Structure of the SDK: Client → Resource → Method

Every call in the SDK follows the same pattern:

client . resource . method ( parameters )
   │        │         │
   │        │         └── the action: create, list, retrieve, count_tokens, ...
   │        └── the API area: messages, models, files, ...
   └── your configured Anthropic / AsyncAnthropic instance

The pattern mirrors the API's URL structure. Once you know one endpoint, you can usually guess the matching SDK call:

SDK callHTTP endpointPurposeCovered in
client.messages.create(...)POST /v1/messagesSend messages and get Claude's replyLessons 11–30
client.messages.stream(...)POST /v1/messages (streaming)Receive the reply piece by pieceLessons 51–60
client.messages.count_tokens(...)POST /v1/messages/count_tokensCount input tokens before sendingLesson 46
client.messages.batches.create(...)POST /v1/messages/batchesSubmit many requests for asynchronous processingLessons 131–140
client.models.list()GET /v1/modelsList available modelsLesson 12
client.files.upload(...)POST /v1/filesUpload files for later useLessons 115–116
client.beta.…VariousAccess features that are still in betaAdvanced lessons

Nested resources work the same way. batches lives under messages because its URL lives under /v1/messages/.

Why it is designed this way: a predictable structure makes the SDK discoverable. In an editor with autocomplete, typing client. lists the available resources, and typing client.messages. lists the available actions. You rarely need to memorize anything.

The Three Parameters You Will See Everywhere

Almost every example in this tutorial calls client.messages.create() with three required parameters:

message = client.messages.create(
    model="claude-opus-5-5",   # Which Claude model answers
    max_tokens=1024,           # The maximum length of the reply, in tokens
    messages=[                 # The conversation so far
        {"role": "user", "content": "Hello, Claude"}
    ],
)
ParameterWhat it controlsWhat happens if you remove it
modelThe Claude model that processes the request. Models differ in capability, speed, and cost.The call fails: the API requires a model.
max_tokensAn upper limit on output length. A token is a small piece of text, roughly a word or part of a word. The model's internal thinking counts toward this limit, so do not set it too low.The call fails: the API requires an explicit limit.
messagesA list of turns, each with a role ("user" or "assistant") and content.The call fails: there is nothing for Claude to respond to.

Each of these gets its own lesson later (Choose a Claude Model, Set Max Tokens, Send User Messages). For now, the point is that the SDK expects these parameters, and the SDK's type definitions describe them.


Synchronous and Asynchronous Clients

The SDK includes two client classes with the same methods and parameters:

  • Anthropic: synchronous. Each call blocks until the response arrives. This is the simplest option and is ideal for scripts, notebooks, command-line tools, and traditional web frameworks such as Django or Flask views.
  • AsyncAnthropic: asynchronous. Each call is a coroutine that you await. While one request waits on the network, your program can do other work, such as running other requests.

Synchronous Example

from anthropic import Anthropic

client = Anthropic()

message = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Name three uses of Python."}],
)

for block in message.content:
    if block.type == "text":
        print(block.text)

Asynchronous Example

import asyncio

from anthropic import AsyncAnthropic


async def main() -> None:
    async with AsyncAnthropic() as client:
        message = await client.messages.create(
            model="claude-opus-5-5",
            max_tokens=1024,
            messages=[{"role": "user", "content": "Name three uses of Python."}],
        )

        for block in message.content:
            if block.type == "text":
                print(block.text)


asyncio.run(main())

What changed between the two versions:

  • The class is AsyncAnthropic instead of Anthropic.
  • The call is prefixed with await, because create() now returns a coroutine.
  • The code runs inside an async def function, which asyncio.run() starts.
  • async with closes the client's network connections when the block ends.

Everything else, including the method name, parameters, and response object, is identical. That is intentional: after you learn the synchronous API, the asynchronous one only adds await.

SituationRecommended client
Learning, scripts, notebooks, CLI toolsAnthropic
Django or Flask views (synchronous frameworks)Anthropic
FastAPI or other async frameworksAsyncAnthropic
Sending many requests in parallelAsyncAnthropic

This tutorial uses the synchronous client until the Async SDK section (Lessons 71–80).


Typed Requests and Responses

The SDK is fully type-annotated:

  • Request parameters are described with TypedDicts. These are dictionaries whose allowed keys and value types are declared, so editors and type checkers can validate them.
  • Responses are Pydantic models. These are Python objects with declared fields, so you access data through attributes like message.content instead of dictionary keys.

Inspecting a Response Object

from anthropic import Anthropic

client = Anthropic()

message = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Say hello in French."}],
)

print(type(message))                         # The Python class of the response
print(message.id)                            # Unique ID of this message
print(message.role)                          # Who wrote it: always "assistant"
print(message.model)                         # The model that produced it
print([block.type for block in message.content])  # The types of the content blocks
print(message.stop_reason)                   # Why Claude stopped generating
print(message.usage.input_tokens)            # Tokens you sent
print(message.usage.output_tokens)           # Tokens Claude generated

Example output (IDs, block types, token counts, and wording will differ for you):

<class 'anthropic.types.message.Message'>
msg_01XFDUDYJgAACzvnptvVoYEL
assistant
claude-opus-5-5
['text']
end_turn
14
9

What each field tells you:

  • message.id uniquely identifies the response. It is useful in logs.
  • message.role is "assistant" because the reply comes from Claude.
  • message.model confirms which model actually answered.
  • The block types show what the reply contains. For a simple greeting this is often just ['text']. When the model reasons first, you will see ['thinking', 'text'].
  • message.stop_reason explains why generation ended. end_turn means Claude finished naturally. max_tokens means it hit your length limit. Lesson 20 covers all stop reasons.
  • message.usage reports token counts, which determine cost. output_tokens includes any thinking tokens. Lesson 30 covers this in depth.

Why Typed Objects Are Better Than Dictionaries

With a dictionary, a misspelled key such as data["stop_reasn"] is discovered only when the program runs. With a typed object, your editor can show the available attributes as you type, and a type checker can flag message.stop_reasn before you run anything.

Because the response is an object, dictionary-style access does not work:

message["content"]   # TypeError: 'Message' object is not subscriptable
message.content      # Correct

When you need a plain dictionary or JSON string, for example to store a response or return it from a web API, use the built-in helpers:

data = message.to_dict()       # Python dict
json_text = message.to_json()  # JSON-formatted string

What the SDK Handles for You

The SDK's most valuable features are the ones you never have to write yourself. Each is introduced briefly here and covered fully in its own lesson.

Authentication

import os

from anthropic import Anthropic

client = Anthropic()  # Uses ANTHROPIC_API_KEY automatically

# Equivalent explicit form. This is useful when the key comes from somewhere else,
# such as a secrets manager:
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

Warning: Never write the key itself into your code, as in Anthropic(api_key="sk-ant-..."). Code ends up in Git repositories, screenshots, and shared files, and anyone who has the key can use your account. Lessons 7, 8, and 171–173 cover safe key handling.

Specific Exceptions

When a request fails, the SDK raises an exception that describes the kind of failure:

HTTP statusExceptionTypical cause
400BadRequestErrorInvalid parameters, such as a malformed messages list
401AuthenticationErrorMissing or invalid API key
403PermissionDeniedErrorThe key is not allowed to perform this action
404NotFoundErrorUnknown resource, such as a wrong model name
429RateLimitErrorToo many requests in a short time
≥ 500InternalServerErrorA problem on the API side
—APIConnectionErrorThe server could not be reached (network issue)

The status-code exceptions all inherit from APIStatusError. So you can catch specific cases first and handle everything else with one generic branch:

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": "Hello, Claude"}],
    )
except anthropic.AuthenticationError:
    print("Your API key is missing or invalid.")
except anthropic.RateLimitError:
    print("Rate limited. Wait a moment and try again.")
except anthropic.APIConnectionError:
    print("Could not reach the API. Check your internet connection.")
except anthropic.APIStatusError as error:
    print(f"The API returned an error: {error.status_code}")

Why the order matters: Python checks except clauses from top to bottom. AuthenticationError and RateLimitError are subclasses of APIStatusError. If the APIStatusError branch came first, it would catch them, and the specific branches would never run. Always put more specific exceptions first.

Automatic Retries

Some failures are temporary. The SDK automatically retries the following 2 times by default, with a short exponential backoff (a delay that grows after each attempt):

  • connection errors
  • 408 Request Timeout
  • 409 Conflict
  • 429 Rate Limit
  • 500-level errors

You can change this for the whole client or for one request:

from anthropic import Anthropic

client = Anthropic(max_retries=4)   # Applies to every request from this client

client.with_options(max_retries=0).messages.create(   # This request only
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello, Claude"}],
)

Setting max_retries=0 disables retries. That is useful when you implement your own retry logic (Lessons 143 and 146) or when you want failures to surface immediately during testing.

Timeouts

By default, a request times out after 10 minutes. That is generous because long responses can take time to generate. You can set your own limit in seconds:

client = Anthropic(timeout=30.0)                      # All requests
client.with_options(timeout=10.0).messages.create(...)  # One request

When a request times out, the SDK raises APITimeoutError, a subclass of APIConnectionError. Timed-out requests also count toward the automatic retries.

Note: For long outputs, streaming (Lessons 51–60) is the recommended approach. It is preferred over a very large timeout because some networks drop connections that stay idle too long.

Request IDs

Every response carries the ID that the API assigned to that request:

print(message._request_id)   # e.g. req_018EeWyXxfu5pfWkrYcMdjWG

Log this value. If something goes wrong, the request ID lets Anthropic support find the exact request. Although the name starts with an underscore, _request_id is a public, documented property. Other underscore-prefixed attributes in the SDK are private and should not be relied on.

Closing Connections

The client keeps network connections open so later requests can reuse them. Connections are closed automatically when the client is garbage-collected. You can also close them explicitly with a context manager:

from anthropic import Anthropic

with Anthropic() as client:
    message = client.messages.create(
        model="claude-opus-5-5",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hello, Claude"}],
    )
# The client's connections are closed here

SDK Version and API Version

Two separate version numbers are involved, and it helps to keep them apart:

  • The SDK version is the version of the anthropic Python package you installed, for example 2.0.0.
  • The API version is the date-style value in the anthropic-version header, for example 2026-10-21. It tells the API which request and response format your code expects.

Each major version of the SDK sends one specific API version by default:

anthropic package versionAPI version sent
Earlier releases (before 2.0.0)2023-06-01
2.0.0 and later 2.x releases2026-10-21

Why it works this way: when Anthropic changes the API in a backward-incompatible way, it publishes a new API version instead of breaking existing integrations. Tying each SDK major version to one API version means a minor upgrade (2.0 → 2.1) will not quietly change the API behavior your code depends on. Moving to a new API version requires a deliberate major upgrade, and the official migration guide lists what changed.

To see which SDK version your environment is using:

import anthropic

print(anthropic.__version__)

If a feature from the documentation is missing, check this first. Your environment, such as a different virtual environment, may still be using an older package. Lesson 5 (Upgrade the SDK) covers upgrading.

Note: The SDK requires Python 3.10 or later. On an older interpreter, pip will not install a current release.


Where the SDK Fits in the Claude Ecosystem

Beginners often confuse the following products. They serve different purposes:

ProductWhat it isWho uses itHow you access Claude
Claude apps (web, desktop, mobile)Ready-made chat interfaceEnd usersTyping in a chat window
Claude APIHTTP service at api.anthropic.comDevelopers in any languageHTTP requests with an API key
Anthropic Python SDKPython library that calls the Claude APIPython developersclient.messages.create(...)
Other official SDKsThe same idea for TypeScript, Go, Java, C#, PHP, and RubyDevelopers in those languagesLanguage-native methods
Cloud provider clientsSDK classes that send requests through cloud platformsTeams already on AWS, Google Cloud, or AzureSame methods, different client class

A subscription to the Claude apps and access to the Claude API are separate. API usage is billed per token through the Claude Console, where you also create API keys (Lesson 6).

The Python SDK also includes clients for cloud platforms. They expose the same messages.create() interface but authenticate through the cloud provider:

PlatformClient classInstall extra
Amazon BedrockAnthropicBedrockMantle (AnthropicBedrock for the older InvokeModel path)pip install "anthropic[bedrock]"
Claude Platform on AWSAnthropicAWS (beta)pip install "anthropic[aws]"
Claude Models on Google Cloud (Google managed)AnthropicVertexpip install "anthropic[vertex]"
Claude Models on Google Cloud (Anthropic managed)AnthropicGoogleCloud (beta)pip install "anthropic[google_cloud]"
Microsoft FoundryAnthropicFoundryNone (included in the base package)

Because the interface is shared, code written against Anthropic can usually move to a cloud client by changing only how the client is created. Lessons 161–170 cover this.


When to Use the SDK and When Not To

Use the SDK when:

  • You are writing Python: scripts, data pipelines, Django/Flask/FastAPI backends, Jupyter notebooks, or automation tools.
  • You want typed responses, specific exceptions, retries, and streaming without writing them yourself.
  • You will use advanced features such as tool use, batches, or file uploads. The SDK includes helpers for these that would take significant effort to rebuild.

Consider a different approach when:

  • You are not using Python. Use the official SDK for your language. If your language has none, call the HTTP API directly.
  • You need a quick one-off test from the terminal. A single curl command, or Anthropic's ant command-line tool, may be faster than writing a script.
  • You are in a tightly restricted environment where you cannot install third-party packages. Raw HTTP with only the standard library is possible, but you then take on everything the SDK would have handled.

For almost every Python project, the SDK is the right default.


Practical Example: A First Look Script

This script combines the ideas in this lesson into a small, well-structured program. It checks the setup, sends a question, prints the answer, and reports useful metadata. It also handles the most common failures cleanly.

Setup

Install the SDK and set your API key. Lessons 4, 7, and 8 explain these steps in detail.

pip install anthropic
# macOS / Linux
export ANTHROPIC_API_KEY="your-api-key"
# Windows PowerShell
$env:ANTHROPIC_API_KEY = "your-api-key"

Save the following as first_look.py. Do not name it anthropic.py; the Common Mistakes section below explains why.

"""first_look.py: a first look at the Anthropic Python SDK."""

import os
import sys

import anthropic
from anthropic import Anthropic

MODEL = "claude-opus-5-5"
QUESTION = "In two sentences, what does a Python SDK for a web API do?"


def main() -> int:
    print(f"anthropic SDK version: {anthropic.__version__}")

    # 1. Fail early with a clear message if the key is not configured.
    if not os.environ.get("ANTHROPIC_API_KEY"):
        print("ANTHROPIC_API_KEY is not set.", file=sys.stderr)
        return 1

    # 2. Create the client and send the request.
    try:
        with Anthropic(max_retries=3, timeout=60.0) as client:
            message = client.messages.create(
                model=MODEL,
                max_tokens=1024,
                messages=[{"role": "user", "content": QUESTION}],
            )
    except anthropic.AuthenticationError:
        print("The API key was rejected. Check that it is correct.", file=sys.stderr)
        return 1
    except anthropic.APIConnectionError:
        print("Could not connect to the API.", file=sys.stderr)
        return 1
    except anthropic.APIStatusError as error:
        print(f"API error {error.status_code}: {error.message}", file=sys.stderr)
        return 1

    # 3. Collect every text block into one string (thinking blocks are skipped).
    answer = "".join(block.text for block in message.content if block.type == "text")

    # 4. Show the answer and the metadata.
    print()
    print(answer or "(no text in this response)")
    print("-" * 40)
    print(f"Message ID:    {message.id}")
    print(f"Model:         {message.model}")
    print(f"Blocks:        {[block.type for block in message.content]}")
    print(f"Stop reason:   {message.stop_reason}")
    print(f"Input tokens:  {message.usage.input_tokens}")
    print(f"Output tokens: {message.usage.output_tokens}")
    print(f"Request ID:    {message._request_id}")
    return 0


if __name__ == "__main__":
    sys.exit(main())

Run it:

python first_look.py

Example output (Claude's wording, the block types, the IDs, and the token counts will differ for you):

anthropic SDK version: 2.0.0

A Python SDK wraps a web API in ordinary Python functions and objects, so you can call the service without building HTTP requests by hand. It also handles details like authentication, error handling, and converting responses into convenient data types.
----------------------------------------
Message ID:    msg_01XFDUDYJgAACzvnptvVoYEL
Model:         claude-opus-5-5
Blocks:        ['thinking', 'text']
Stop reason:   end_turn
Input tokens:  22
Output tokens: 118
Request ID:    req_018EeWyXxfu5pfWkrYcMdjWG

How the Script Works

  1. Printing anthropic.__version__ confirms which SDK version is running. When behavior does not match the documentation, this is the first thing to check.
  2. The environment check stops the program with a readable message if the key is missing. Without it, the failure would surface later as a less obvious authentication error.
  3. Anthropic(max_retries=3, timeout=60.0) sets client-level policies. Temporary failures get three retries instead of two, and no request waits longer than 60 seconds.
  4. with ... as client guarantees the client's connections are closed when the block ends, even if an exception occurs.
  5. The except chain goes from specific to general. A rejected key, a network problem, and any other API error each produce a different, actionable message. error.message holds the error description from the API.
  6. max_tokens=1024 leaves room for both the model's thinking and a two-sentence answer. With a very small limit, the thinking could use up the whole budget before any text is written (Lesson 13).
  7. Collecting the answer joins all text blocks and skips thinking blocks. This is more robust than message.content[0].text, which fails whenever the first block is a thinking block. answer or "(no text in this response)" prints a clear placeholder if there is no text at all.
  8. The metadata lines show the fields you will use constantly: the block types, the message ID and request ID for debugging, the stop reason for detecting truncated replies, and the token counts for monitoring cost. Notice that output_tokens can be larger than the visible answer, because thinking tokens are counted too.
  9. sys.exit(main()) returns an exit code of 0 for success or 1 for failure. That lets shell scripts and schedulers such as cron detect whether the run succeeded.

Try These Variations

ChangeWhat you will observeWhat it teaches
Set max_tokens=20Stop reason: max_tokens, with a cut-off answer, or (no text in this response) if thinking used the whole budgetmax_tokens is a hard limit that includes thinking, and stop_reason tells you when it was hit
Set the API key to an invalid valueThe AuthenticationError branch runs401 errors are not retried; they fail immediately
Change MODEL to a made-up nameAn APIStatusError branch runs (status 404)Model names are validated by the API
Disconnect from the internetThe APIConnectionError branch runs after retriesConnection errors are retried before the exception is raised

Common Mistakes

1. Naming Your File anthropic.py

What happens: You save your script as anthropic.py and run it. You get an error like:

ImportError: cannot import name 'Anthropic' from partially initialized module 'anthropic' (most likely due to a circular import)

Why: Python searches the script's own folder before installed packages. Your file named anthropic.py shadows the real SDK, so from anthropic import Anthropic imports your own file.

Fix: Rename the file, for example to first_look.py. Also delete any __pycache__/anthropic*.pyc left in that folder. Avoid naming files after any package you import, such as requests.py, json.py, or openai.py.

2. Confusing the Package, Module, and Class Names

What happens: Code like import Anthropic or client = anthropic() fails with ModuleNotFoundError or TypeError: 'module' object is not callable.

Why: anthropic (lowercase) is the module. Anthropic (capitalized) is the class inside it.

Fix: Use from anthropic import Anthropic, then client = Anthropic(). Or use import anthropic, then client = anthropic.Anthropic().

3. Treating message.content as a String, or Reading Only the First Block

What happens: print(message.content) prints something like [ThinkingBlock(...), TextBlock(text='...', type='text', ...)] instead of the answer. String operations such as message.content.upper() raise AttributeError. And message.content[0].text fails with AttributeError: 'ThinkingBlock' object has no attribute 'text' whenever the reply starts with a thinking block.

Why: A response can contain several content blocks of different types: thinking, text, tool calls, and others. So content is always a list of blocks, and the first block is not guaranteed to be text.

Fix: Read the text from the blocks by type:

text = "".join(block.text for block in message.content if block.type == "text")

4. Hardcoding the API Key

What happens: The key is written directly in the code and later committed to Git or shared. Anyone who finds it can spend your credits.

Why: It is the fastest way to "make it work" while learning.

Fix: Keep the key in the ANTHROPIC_API_KEY environment variable or in a .env file that Git ignores. If a key is ever exposed, revoke it in the Claude Console and create a new one.

5. Creating a New Client for Every Request

What happens: Code like Anthropic().messages.create(...) inside a loop or a web request handler creates a fresh client on every call.

Why it matters: Each client has its own connection pool. Creating a new one each time throws away open connections, which adds latency and wastes resources.

Fix: Create the client once, for example at module level or at application startup, and reuse it. Clients are designed to be long-lived.

6. Using the Synchronous Client Inside async Code

What happens: In an async application such as FastAPI, calling Anthropic().messages.create() from an async def endpoint blocks the event loop. Every other request waits until Claude responds.

Fix: In async code, use AsyncAnthropic and await its methods.

7. Following Documentation Written for a Different SDK Version

What happens: An example from a blog post or an older tutorial uses parameters or response fields that your installed version does not support, or that behave differently.

Fix: Check anthropic.__version__, compare it with the official documentation, and upgrade if needed. For major-version upgrades, read the migration guide before upgrading.


Best Practices

  • Load credentials from the environment. Let Anthropic() read ANTHROPIC_API_KEY, or pass a key from a secrets manager. Never commit keys to source control.
  • Create one client and reuse it. Configure it once with your timeout and retry settings, and share it across your application.
  • Use with_options() for exceptions to the rule. Override timeouts or retries for specific calls instead of creating extra clients.
  • Catch specific exceptions first. Handle AuthenticationError, RateLimitError, and APIConnectionError separately from the generic APIStatusError, so each failure produces a useful response.
  • Read text by block type, never by position.
  • Log _request_id and usage. Request IDs make support cases solvable, and token counts make costs visible.
  • Check stop_reason. A value of max_tokens means the answer was cut off, or missing. Do not treat it as a complete response.
  • Pin the SDK version in production. Record the version in requirements.txt (for example anthropic==2.0.0, or a range such as anthropic>=2.0,<3) so that deployments are reproducible and major upgrades are deliberate.
  • Match the client to your framework. Use Anthropic for synchronous code and AsyncAnthropic for async code.
  • Use the official documentation as the source of truth. The API evolves quickly. When in doubt, check the Python SDK page and the API reference at platform.claude.com/docs.

Exercise

Write a script named sdk_check.py that:

  1. Prints the installed SDK version.
  2. Creates a client with max_retries=2 and a 30-second timeout.
  3. Asks Claude: "List three things a client library handles for developers."
  4. Prints only the text of the answer.
  5. Prints the stop reason and the total number of tokens used (input + output).
  6. Prints a friendly message instead of a traceback if the API key is invalid.

Then experiment:

  • Run it with max_tokens=15 and check the stop reason and the printed text.
  • Set ANTHROPIC_API_KEY to invalid-key and confirm your friendly error message appears.
Show a possible solution
import anthropic
from anthropic import Anthropic

print(f"SDK version: {anthropic.__version__}")

client = Anthropic(max_retries=2, timeout=30.0)

try:
    message = client.messages.create(
        model="claude-opus-5-5",
        max_tokens=1024,
        messages=[
            {
                "role": "user",
                "content": "List three things a client library handles for developers.",
            }
        ],
    )
except anthropic.AuthenticationError:
    print("Your API key was rejected. Check ANTHROPIC_API_KEY.")
else:
    answer = "".join(block.text for block in message.content if block.type == "text")
    total_tokens = message.usage.input_tokens + message.usage.output_tokens

    print(answer or "(no text in this response)")
    print(f"Stop reason:  {message.stop_reason}")
    print(f"Total tokens: {total_tokens}")

How it works: The try/except/else structure runs the else block only when the request succeeds. This keeps success handling separate from error handling. Total tokens are the sum of the two usage fields, and the output count includes any thinking.

With max_tokens=15, the stop reason becomes max_tokens. The answer is either visibly cut off, or missing entirely (the script prints (no text in this response)) if the model spent the whole budget thinking. This is why real code uses comfortable limits such as 1,024 and always checks stop_reason.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Anthropic SDK Intro and get answers drawn from it.

Signed-in readers only.