Anthropic Python SDK 1.0: How to Migrate to httpx2

TL;DR
Anthropic Python SDK 1.0 swaps httpx for httpx2, requires Python 3.10+, and drops temperature and Text Completions. Verified steps to upgrade safely.
Anthropic Python SDK 1.0, released August 20, 2026, moves the client's HTTP layer from httpx to httpx2 (the Pydantic-maintained fork), raises the minimum to Python 3.10, and deletes long-deprecated surface: the Text Completions API, temperature / top_p / top_k on Messages methods, and the tool runner's compaction_control. The verdict: for most apps that only pass plain values like timeout=30.0, the upgrade is a one-line pip change plus a type-checker pass. The real risk is silent - tracing and HTTP-mocking tools that patch httpx stop seeing SDK traffic until you call httpx2.alias_httpx().
Last updated: October 17, 2026
Every snippet below was type-checked with pyright (0 errors) and compiled against anthropic==1.15.0 (the current SDK release as of October 15, 2026) and httpx2==2.13.1 on Python 3.13. No live API calls were made with a real key; the error messages quoted are from local runtime checks that fail before any request is sent, plus a mocked-transport test.
What changed in 1.0 (the short list)#
The 1.0.0 changelog has a single breaking-change entry: "client: upgrade to httpx2 and some minor breaking changes. See MIGRATION.md for details." The last 0.x release was 0.125.0 on August 19, 2026, one day earlier. Here is what the migration guide actually covers, sorted by who it hits.
| Change | Who it hits | What to do |
|---|---|---|
| Python 3.10+ required | Anyone still on 3.9 | Upgrade the interpreter first |
httpx replaced by httpx2 | Custom http_client, transports, httpx.Timeout, isinstance checks on httpx types | import httpx2 as httpx or rename references |
| Tracing and mocking go quiet | OpenTelemetry HTTPXClientInstrumentor, Sentry httpx integration, respx, pytest-httpx, vcrpy | Call httpx2.alias_httpx() before anything imports httpx |
.with_raw_response returns new classes | Async raw-response users, anyone reading .text / .content | await response.parse(); .text() and .read() are methods |
| Text Completions API removed | client.completions.create(), HUMAN_PROMPT, AI_PROMPT | Move to client.messages.create() |
temperature / top_p / top_k removed | Anyone passing sampling params to Messages methods | Delete them, or extra_body for an older model |
output_format schema dict removed | Structured output with raw JSON schemas | output_config={"format": {...}}; classes stay on output_format= |
compaction_control removed | tool_runner users with client-side compaction | Server-side context_management |
| Bedrock region required | AnthropicBedrock() with no region configured | aws_region= or AWS_REGION |
| Headers matched case-insensitively; bytes values rejected | Custom headers with mixed casing or bytes values | Send one casing; .decode() bytes |
Why the HTTP swap at all? The migration guide describes httpx as "no longer actively maintained" and httpx2 as "an API-compatible fork maintained by the Pydantic team" with "the same classes, same behaviour, and security fixes included." The httpx2 README frames it more gently: Pydantic is picking up stewardship under the HTTPX2 name, with HTTPX itself seeing limited activity recently, to provide a maintained path that includes timely security updates. The original httpx project gave Python a well-designed sync and async HTTP client with HTTP/2 support, and httpx2 keeps that API intact rather than reinventing it. For an SDK that sits under almost every Claude integration in Python, a dependency that ships security fixes on a predictable cadence is the point.
%% title: Do you need to change code for SDK 1.0?
flowchart TD
A[Upgrade to anthropic 1.x] --> B{Python 3.10+?}
B -- No --> B1[Upgrade Python first]
B -- Yes --> C{Pass httpx objects to the SDK?}
C -- Yes --> C1[Switch imports to httpx2]
C -- No --> D{Tracing or HTTP mocks on httpx?}
C1 --> D
D -- Yes --> D1[Call httpx2.alias_httpx early]
D -- No --> E{Removed params or APIs used?}
D1 --> E
E -- Yes --> E1[Fix with pyright or mypy list]
E -- No --> F[Done]
E1 --> FStep 1: Upgrade and let the type checker build your checklist#
Check the interpreter, upgrade, then run a type checker over the project:
python --version # must print 3.10 or newer
pip install --upgrade "anthropic>=1,<2"
pip install pyright && pyright .
The migration guide says a type checker "will flag almost everything below as an error after upgrading, which makes it a good checklist even if you don't normally run one." That matched our experience: removed parameters, removed exports and un-awaited coroutines all show up as pyright errors. mypy works too.
If you use Claude Code, the guide's shortcut is to run /claude-api upgrade python in the project and review the diff it produces. Review it like any other PR - the type checker is still the way to confirm nothing was missed.
One dependency detail that is easy to miss: anthropic 1.15.0 depends on httpx2<3,>=2.0.0 and no longer depends on plain httpx at all. A fresh venv install of anthropic[bedrock,aiohttp]==1.15.0 pulled httpx2 2.13.1 and httpcore2 2.13.1 and no httpx. If your own code does import httpx for unrelated reasons and you were getting it transitively from the SDK, that import breaks on a clean install. Add httpx to your own requirements, switch to httpx2, or use the alias in Step 3.
Pydantic v1 and v2 both remain supported, so this upgrade does not force a Pydantic migration on you.
Step 2: Move custom HTTP config to httpx2#
Anything you construct from httpx and hand to the client - timeouts, transports, limits, a full http_client - must now come from httpx2. The SDK's own re-exports (anthropic.Timeout, anthropic.DefaultHttpxClient, anthropic.DefaultAsyncHttpxClient, anthropic.DefaultAioHttpClient) already point at httpx2, so code that only uses those keeps working unchanged.
This version type-checks cleanly against 1.15.0:
import anthropic
import httpx2
from anthropic import Anthropic, DefaultHttpxClient
client = Anthropic(
timeout=httpx2.Timeout(60.0, connect=5.0),
max_retries=3,
http_client=DefaultHttpxClient(
transport=httpx2.HTTPTransport(local_address="0.0.0.0"),
),
)
def log_failure(err: anthropic.APIStatusError) -> None:
response: httpx2.Response = err.response
print(response.status_code, response.headers.get("request-id"))
The fastest edit for an existing file is import httpx2 as httpx, which leaves every httpx.Timeout(...) reference valid.
The good news is that the most common mistake fails loudly. Passing an old httpx.Client as http_client= raises at construction. On 1.15.0 the message is:
TypeError: Invalid `http_client` argument; `httpx.Client` is from the `httpx` package, but this SDK uses `httpx2`. Use `httpx2.Client` instead.
Returned objects changed type too. APIStatusError.response, APIConnectionError.request, .http_response / .headers / .url on raw responses, and the response= argument your event hooks receive are now httpx2 objects. The attributes are identical, so err.response.status_code keeps working; only isinstance checks and annotations that name httpx.Response, httpx.Request or httpx.Headers need to switch. If you built retry or logging logic around err.response, as in our Claude API error handling guide, check those annotations first.
Two smaller cleanups:
- aiohttp:
pip install anthropic[aiohttp]andhttp_client=DefaultAioHttpClient()still work, but the extra no longer installshttpx_aiohttpbecause that code now ships inside the SDK. Drophttpx_aiohttpfrom your requirements. - Removed aliases:
anthropic.Transportandanthropic.ProxiesTypesare gone. Usehttpx2.BaseTransport,httpx2.AsyncBaseTransportandhttpx2.Proxy(or a proxy URL string) directly.
Step 3: Fix tracing and test mocks (the silent one)#
This is the change that will not show up as an error. Libraries that observe or stub HTTP traffic by patching the httpx package - OpenTelemetry's HTTPXClientInstrumentor, Sentry's httpx integration, respx, pytest-httpx and vcrpy are the ones the migration guide names - keep importing and patching httpx. The SDK no longer sends anything through httpx, so those patches never fire for Claude calls. Your other HTTP traffic still shows up, which makes the gap easy to miss.
We reproduced it with respx 0.23.1 mocking POST https://api.anthropic.com/v1/messages to return a 500:
- No alias:
route.calledwasFalse. The request escaped the mock, reached the network, and failed withAuthenticationErrorfrom the real API (the test key was fake). - With
httpx2.alias_httpx()first:route.calledwasTrue, and the SDK raisedInternalServerErrorfrom the mocked 500.
A test suite in the first state may still pass if it only asserts that "an error was raised", while quietly making real network calls in CI.
%% title: Why respx stops seeing SDK calls
sequenceDiagram
participant T as Test
participant R as respx mock
participant S as Anthropic SDK
participant H as httpx2
participant N as Network
T->>R: patch httpx
T->>S: messages.create()
S->>H: send request
H->>N: real HTTP call
Note over R: never invoked
T->>T: alias_httpx() first
T->>R: patch httpx (now httpx2)
T->>S: messages.create()
S->>H: send request
H->>R: interceptedThe fix is httpx2.alias_httpx(), which makes import httpx and import httpcore resolve to httpx2 and httpcore2 for the whole process. This runs as written:
# the very first lines of your entry point
import httpx2
httpx2.alias_httpx()
import httpx # now resolves to httpx2
assert httpx.Client is httpx2.Client
Under pytest, the migration guide recommends an early plugin so the alias runs before respx, pytest-httpx or your test modules are imported:
# tests/_alias_httpx.py
import httpx2
httpx2.alias_httpx()
# pyproject.toml
[tool.pytest.ini_options]
addopts = "-p tests._alias_httpx"
pythonpath = ["."]
Two rules from the guide. First, the alias must run before anything imports httpx; if httpx is already loaded it raises a RuntimeError. Second, it is for applications only - a library should never call it on behalf of its users, because it changes what import httpx means for the entire process.
For tracing specifically there is now a second option. SDK 1.14.0 (October 14, 2026) added native OpenTelemetry support: if your app registers a global tracer provider, the SDK creates its own spans and propagates trace context headers, with no httpx instrumentation involved. Turn it off with open_telemetry=False on the client or ANTHROPIC_OPEN_TELEMETRY=false. The SDK OpenTelemetry docs cover setup. If you rely on traces to understand what an agent actually did, our piece on local OpenTelemetry traces as agent receipts explains why it is worth confirming the Anthropic spans survived the upgrade.
Step 4: Update async raw responses#
.with_raw_response used to return LegacyAPIResponse for both clients. It now returns the same APIResponse / AsyncAPIResponse classes that .with_streaming_response already used. Two things change: on the async client, reading the body is now a coroutine, and .text / .content became methods on both clients.
Before (LegacyAPIResponse) | Sync after (APIResponse) | Async after (AsyncAPIResponse) |
|---|---|---|
response.parse() | response.parse() | await response.parse() |
response.text | response.text() | await response.text() |
response.content | response.read() | await response.read() |
response.http_response.json() | response.json() | await response.json() |
response.http_response.iter_bytes() | response.iter_bytes() | async for chunk in response.iter_bytes(): |
.headers, .status_code, .url, .request_id, .retries_taken, .http_response, .elapsed | unchanged | unchanged |
The async version, type-checked against 1.15.0:
import asyncio
from anthropic import AsyncAnthropic
client = AsyncAnthropic()
async def main() -> None:
response = await client.messages.with_raw_response.create(
model="claude-sonnet-4-6",
max_tokens=256,
messages=[{"role": "user", "content": "Say hi"}],
)
print(response.headers.get("request-id"))
message = await response.parse()
print(message.content)
if __name__ == "__main__":
asyncio.run(main())
On the sync client the only change is the method calls:
from anthropic import Anthropic
client = Anthropic()
response = client.messages.with_raw_response.create(
model="claude-sonnet-4-6",
max_tokens=256,
messages=[{"role": "user", "content": "Say hi"}],
)
print(response.status_code, response.request_id)
body_text = response.text()
message = response.parse()
print(message.usage.output_tokens)
The gotcha: forget the await and message is a coroutine, not a Message. Python will only tell you when you touch an attribute on it. pyright flags it at the call site, which is one more reason to run Step 1's type check.
%% title: Async raw response in 1.0
sequenceDiagram
participant App
participant SDK as AsyncAnthropic
participant API as Messages API
App->>SDK: await with_raw_response.create()
SDK->>API: POST /v1/messages
API-->>SDK: HTTP response
SDK-->>App: AsyncAPIResponse
App->>App: read headers directly
App->>SDK: await response.parse()
SDK-->>App: MessageStep 5: Remove deprecated parameters and APIs#
Sampling parameters. temperature, top_p and top_k are gone from messages.create(), messages.stream(), messages.parse(), their beta.messages counterparts, and beta.messages.tool_runner(). Passing one is a TypeError:
TypeError: Messages.create() got an unexpected keyword argument 'temperature'
They are gone from the method signatures, not from the API. The guide says "Current models do not use these sampling parameters", but models that predate the change still honour them. If you are pinned to such a model and need a sampling setting, pass it through extra_body, which is merged into the request JSON as-is:
from anthropic import Anthropic
client = Anthropic()
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
messages=[{"role": "user", "content": "Write a haiku about migrations."}],
extra_body={"temperature": 0.2},
)
print(message.content)
Batches behave differently. The keys are also removed from the per-request params type of messages.batches.create(), but there "a type checker flags the key but the SDK still forwards it at runtime." So a batch job will not crash on upgrade, and it will not warn you either - only pyright or mypy tells you the key is there.
Text Completions. client.completions.create() (the /v1/complete endpoint), Completion, CompletionCreateParams, anthropic.HUMAN_PROMPT and anthropic.AI_PROMPT are all removed. On 1.15.0, hasattr(client, "completions") and hasattr(anthropic, "HUMAN_PROMPT") are both False. Move to client.messages.create(); the Messages API guide covers the request shape.
Structured output. A raw JSON schema dict passed as output_format= is removed from beta.messages.create(), beta.messages.count_tokens() and batch params, and the helper methods (parse(), stream(), count_tokens(), tool_runner()) now accept only a type on output_format= - a dict there raises TypeError. The rule is simple: schema dicts go in output_config, classes stay on output_format.
from pydantic import BaseModel
from anthropic import Anthropic
class Order(BaseModel):
sku: str
quantity: int
client = Anthropic()
count = client.messages.count_tokens(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "Two units of SKU A-1"}],
output_config={"format": {"type": "json_schema", "schema": Order.model_json_schema()}},
)
parsed = client.messages.parse(
model="claude-sonnet-4-6",
max_tokens=512,
messages=[{"role": "user", "content": "Two units of SKU A-1"}],
output_format=Order,
)
print(count.input_tokens, parsed.parsed_output)
The remaining removals are smaller, and each has a direct replacement in the guide:
messages.parse(stream=True)is removed (the guide notes it never worked). Usewith client.messages.stream(..., output_format=Order) as stream:and readstream.get_final_message().parsed_output.body=b"..."on the low-levelclient.post/get/put/patch/deleteno longer accepts bytes.body=is always JSON-serialised; raw payloads go throughcontent=.isinstance(obj, anthropic.Stream)for message streams now returnsFalsebecause the compatibility shim is gone. Checkanthropic.lib.streaming.MessageStreaminstead.- Headers are matched case-insensitively across
default_headers,extra_headers,with_options(default_headers=...)andANTHROPIC_CUSTOM_HEADERS. A later entry replaces any casing of the same name, including headers the SDK sets itself, andomitremoves one.bytesheader values now raise; decode them tostr. - Renamed exports:
BetaBase64PDFBlockParambecomesBetaRequestDocumentBlockParam, andagent_toolset.READ_MAX_BYTESbecomesDEFAULT_MAX_FILE_BYTES.
Step 6: Replace compaction_control with server-side compaction#
tool_runner(compaction_control=...) and the CompactionControl dict are removed. The replacement is server-side compaction, which the guide describes as summarising the conversation "inside the API instead of with an extra client round-trip." On 1.15.0, compaction_control is absent from tool_runner's signature and context_management is present.
from anthropic import Anthropic
from anthropic.lib.tools import beta_tool
client = Anthropic()
@beta_tool
def get_build_status(branch: str) -> str:
"""Return the CI status for a branch.
Args:
branch: The git branch name.
"""
return f"{branch}: passing"
runner = client.beta.messages.tool_runner(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=[get_build_status],
messages=[{"role": "user", "content": "Is main green?"}],
betas=["compact-2026-01-12"],
context_management={
"edits": [
{"type": "compact_20260112", "trigger": {"type": "input_tokens", "value": 100_000}}
]
},
)
for message in runner:
print(message.stop_reason)
Things to know before you swap it in:
- The
compact-2026-01-12beta flag is required alongside thecontext_managementedit. - The trigger threshold must be at least 50,000 tokens. A lower client-side threshold you used before needs to go up.
- The compaction docs cover the other options, including
pause_after_compactionand custominstructionsfor how the summary is written.
The tool runner automates the loop that production tool-use patterns describe by hand; moving compaction server-side removes one more piece of that loop from your process.
Step 7: Set a Bedrock region explicitly#
AnthropicBedrock and AsyncAnthropicBedrock used to log a warning and fall back to us-east-1 when no region could be found. They now raise at construction:
ValueError: No AWS region was provided. Set the `aws_region` argument, the `AWS_REGION` / `AWS_DEFAULT_REGION` environment variable, or configure a region for your AWS profile.
The region is resolved in this order: the aws_region= argument, then AWS_REGION / AWS_DEFAULT_REGION, then the boto3 session configuration for the given aws_profile. That last step is new - previously the profile argument was ignored for region lookup, so a profile configured for eu-north-1 could silently send traffic to us-east-1.
from anthropic.lib.bedrock import AnthropicBedrock
client = AnthropicBedrock(aws_region="us-east-1")
from anthropic import AnthropicBedrock also works at runtime, but pyright 1.1.x reported reportPrivateImportUsage on it in our check because it is star-re-exported. The anthropic.lib.bedrock path type-checks cleanly.
%% title: How AnthropicBedrock picks a region in 1.0
flowchart TD
A[AnthropicBedrock] --> B{aws_region passed?}
B -- Yes --> Z[Use it]
B -- No --> C{AWS_REGION or AWS_DEFAULT_REGION set?}
C -- Yes --> Z
C -- No --> D{Profile has a region?}
D -- Yes --> Z
D -- No --> E[Raise ValueError]Also on Bedrock: unknown streaming events are now skipped instead of yielded. The only known affected event is amazon-bedrock-invocationMetrics. Because region choice decides where your requests are processed, this change is worth reading alongside our look at Fable 5 on Bedrock and the AWS data boundary.
Verify it works#
Run through this list before merging the upgrade:
python -c "import anthropic; print(anthropic.__version__)"prints a 1.x version.pyright .(or mypy) reports zero errors coming from SDK usage.- The test suite runs with the alias plugin, and at least one test asserts that a mock was actually hit, for example
assert route.calledwith respx. Without that assertion, a mock that silently stopped intercepting looks the same as one that works. - Your tracing backend still shows spans for Anthropic calls, either from httpx instrumentation behind the alias or from the SDK's native OpenTelemetry support.
- CI constructs
AnthropicBedrockwith the region env you intend for production, so a missingAWS_REGIONfails in CI rather than at deploy.
Upgrade now or pin below 1.0?#
The closest alternative to migrating is pinning anthropic<1, which leaves you on 0.125.0 (August 19, 2026). Here is how the two compare on documented behaviour:
anthropic>=1,<2 | anthropic<1 (0.125.0) | |
|---|---|---|
| HTTP stack | httpx2, maintained by Pydantic with security fixes | httpx |
| Python floor | 3.10 | 3.9 |
| New SDK features | Yes: Organization API endpoints (1.1.0), claude-haiku-5-5 (1.12.0), native OpenTelemetry (1.14.0), speed parameter and usage.speed (1.15.0) | Frozen at August 19, 2026 |
| Migration cost | The steps above; often small | None today |
| Legacy surface | Removed | Text Completions, sampling params and compaction_control still present |
Pinning has real value as a short bridge: if you are stuck on a Python 3.9 runtime, or your test suite needs the alias work before you trust it, a pin buys time without breaking production. As a long-term position it gets more expensive each release, since the changelog shows new API features landing on the 1.x line only. Anthropic's SDKs are generated from the API definition, which is why new endpoints arrive in the SDK so quickly; our piece on Anthropic and Stainless covers that background. A pin cuts you off from that pipeline.
Our recommendation: upgrade, and pin only for as long as it takes to fix tests or the runtime.
Gotchas and limits#
- Mocks and tracing miss SDK traffic silently until
httpx2.alias_httpx()runs. Nothing errors. alias_httpx()must run before any httpx import (otherwiseRuntimeError), and only in applications, never inside a library.- Every
AsyncAPIResponseread needsawait:parse(),read(),text()andjson(). - Sampling params on batches fail the type check but still forward at runtime, so batch jobs will not warn you on their own.
- Header keys replace across casings. Two casings of one name no longer produce two header lines; send one comma-joined value if you need both.
- Server-side compaction needs the
compact-2026-01-12beta and a trigger of at least 50,000 tokens. - anthropic no longer installs httpx. Code that imports httpx for unrelated reasons needs its own dependency or the alias.
FAQ#
Do I have to change code to upgrade to Anthropic Python SDK 1.0?#
Probably not if you only pass plain values like timeout=30.0 and max_retries=3 to the client. The migration guide says there is "likely nothing for you to do" in that case. Run pip install --upgrade "anthropic>=1,<2" and then pyright or mypy to find anything that does need to change, and check your test mocks and tracing separately, because those fail silently.
Why did Anthropic switch from httpx to httpx2?#
The migration guide calls httpx "no longer actively maintained" and describes httpx2 as an API-compatible fork maintained by the Pydantic team, with the same classes, the same behaviour and security fixes included. The httpx2 README says Pydantic took over stewardship to provide a maintained path with timely security updates.
How do I still set temperature with the Python SDK 1.0?#
You cannot pass temperature, top_p or top_k as keyword arguments anymore; doing so raises TypeError. Current models do not use these sampling parameters. If you are pinned to an older model that still honours them, pass extra_body={"temperature": 0.2}, which the SDK merges into the request JSON as-is.
Why are my respx or pytest-httpx mocks not catching Claude API calls?#
Those libraries patch the httpx package, and SDK 1.0 sends requests through httpx2. Call httpx2.alias_httpx() before anything imports httpx. Under pytest, put the two alias lines in tests/_alias_httpx.py and load it early with addopts = "-p tests._alias_httpx" and pythonpath = ["."].
Does AnthropicBedrock still default to us-east-1?#
No. In 1.0, AnthropicBedrock and AsyncAnthropicBedrock raise ValueError at construction when no region is found. Pass aws_region=, set AWS_REGION or AWS_DEFAULT_REGION, or configure a region on the AWS profile you pass as aws_profile.
Sources#
- Anthropic Python SDK MIGRATION.md
- Anthropic Python SDK CHANGELOG.md
- anthropic on PyPI
- pydantic/httpx2 on GitHub
- httpx2 on PyPI
- Claude docs: Compaction
- Claude docs: Structured outputs
- Claude docs: OpenTelemetry in the SDK
- Claude docs: Using the Messages API
Continue Reading#
- Claude API Reliability: Error Handling Best Practices - retries and fallbacks on top of the new httpx2 error objects
- Tool Use in the Claude API: Production Patterns for Reliable Agents - the loops the tool runner automates
- Claude Batch API: Cutting Async Workload Costs In Half - where the sampling-param caveat bites
Get the next deep dive like this in your inbox
One email a week on Anthropic and the rest of the AI dev stack. Free.
Read next on Claude Code
Claude API Reliability: Error Handling Best Practices
Claude API error handling for production: retry shapes, backoff with jitter, circuit breakers, fallback chains, and the observability to debug failures at 3am.
10 min readTool Use in the Claude API: Production Patterns for Reliable Agents
Master tool use in the Claude API. Schema design, retry logic, multi-step loops, and the failure modes that only show up at 10k calls a day.
12 min readAnthropic Buying Stainless Is About Agent Plumbing
Anthropic's Stainless acquisition is a bet that agents need generated SDKs, CLIs, docs and MCP servers built from one source of truth.
8 min readTechnical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.







