Python SDK Best Practices
A field-tested checklist for using the anthropic Python SDK well in production: client setup, sync/async choice, retries, streaming, and error handling.
Search across all documentation pages
A field-tested checklist for using the anthropic Python SDK well in production: client setup, sync/async choice, retries, streaming, and error handling.
Anthropic() or AsyncAnthropic() at module or application startup, not inside a request handler or loop body - reconstructing it per call discards connection pooling.Anthropic() pick up ANTHROPIC_API_KEY automatically rather than hardcoding a key string in source; pass api_key=... explicitly only when the key comes from a secrets manager at runtime.anthropic dependency can introduce new response fields or block types between deploys; pin it and upgrade deliberately.max_retries and timeout deliberately, not by accident. Decide values based on whether the call is interactive (tight timeout, few retries) or a background job (looser timeout, more retries), rather than leaving every call site on the same defaults.http_client when you have a real infrastructure requirement. Proxies, custom CA bundles, and connection-pool tuning are valid reasons; don't add the complexity speculatively.Anthropic() in scripts, CLIs, and sync frameworks; use AsyncAnthropic() in async web services and anywhere you fan out concurrent calls.async def route. It blocks the entire event loop for the call's duration; use AsyncAnthropic() in async handlers, or wrap unavoidable sync calls in a thread pool.asyncio.gather to actually get concurrency out of the async client. Awaiting each call sequentially inside a loop gives up the throughput benefit that motivated choosing AsyncAnthropic() in the first place.asyncio.gather over many requests can overwhelm your own rate limits; cap it to a sensible concurrency limit for the workload.max_tokens is large. Above roughly 16,000 output tokens, a non-streaming call risks a client-side HTTP timeout; client.messages.stream() avoids that risk regardless of output size.text_stream for the common case, raw events only when you need them. text_stream handles the parsing for plain text; drop to raw event iteration only when you need tool-use or thinking events mid-stream.get_final_message() after the loop, not before. It returns the same complete, typed Message a non-streaming call would, including stop_reason and usage - don't reconstruct that information by hand from accumulated chunks.with/async with to the client you're using. Anthropic() streaming uses with and for; AsyncAnthropic() streaming uses async with and async for. Mixing them raises a TypeError.except anthropic.NotFoundError, except anthropic.RateLimitError, except anthropic.APIStatusError, except anthropic.APIConnectionError rather than one broad except Exception.429, 5xx, 408, and 409 are retried automatically up to max_retries; add your own handling for what happens after retries are exhausted, not to duplicate them.400, 401, 403, 404, or 422 unchanged. These are non-retryable by design - the request itself needs to change, not just be resent.e.status_code, e.message, and e.type on APIStatusError, not the raw exception string. The .type field gives finer-grained classification than the HTTP status alone (distinguishing rate_limit_error from overloaded_error, for example).AuthenticationError. A bad or missing API key does not resolve itself on retry; surface it loudly rather than looping.Message and ContentBlock, not dict or Any. This is what lets a type checker catch a malformed call site before you run the code, and what makes isinstance() narrowing on content blocks actually useful.Usually, yes.
The defaults (max_retries=2, timeout=600.0 seconds) are reasonable general-purpose values; only override them when a specific scenario (interactive UI, background batch job) calls for something tighter or looser.
Constructing the client once at startup (item A1) and choosing sync vs async to match your program's actual concurrency model (item B1) - both are foundational, and getting either wrong compounds into every other item on this list.
Not strictly - a short, bounded response is unlikely to hit a client-side timeout non-streaming. Streaming becomes important as max_tokens grows or as you want incremental UI feedback.
Because it blocks the entire event loop, not just the current request - every other request being served by that same async process stalls for the duration of the blocking call, not only the one that made it.
For a genuinely disposable script where any failure just means "stop and print an error," a broad catch is fine. In any code that's meant to run unattended or serve traffic, the specific-first chain in section D is worth the extra lines.
Awaiting async tool calls or requests sequentially in a loop instead of using asyncio.gather - it compiles, it works, and it silently gives up the concurrency that was the entire reason to choose the async client.
No.
Add one only when there's a concrete requirement (a proxy, a private CA bundle, connection-pool tuning) - it's an advanced customization point, not a default production hardening step.
The underlying practices (retry handling, streaming discipline, typed responses) are identical; only the syntax (await, async with, async for) differs between the two client classes.
Whenever you add a new call site with different reliability or latency requirements than your existing ones, or after a production incident that traced back to error handling or timeout configuration.
It's lower-stakes for a small project, but still worth doing - an unpinned dependency can introduce a new response field or block type between deploys with no warning, which is a harder bug to trace than a version bump you chose deliberately.
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the official
anthropicPython SDK (latest 0.x release). Model names, SDK versions, and pricing move quickly - verify current specifics at platform.claude.com/docs before relying on them.
Reviewed by Chris St. John·Last updated Jul 19, 2026