Create a Client
What a Client Is
A client is the object your program uses to talk to the Claude API. You create it once, and it holds everything needed to send requests:
- credentials: your API key,
- connection settings: the API address, timeouts, and retry rules, and
- open network connections that can be reused between requests.
from anthropic import Anthropic
client = Anthropic()
Every API call then goes through this object: client.messages.create(...), client.models.list(), and so on.
Why the SDK uses a client object instead of plain functions such as anthropic.create_message(...): configuration is set once and applied automatically to every request. You can also have several clients with different configurations in the same program, for example one per API key or workspace, without them interfering with each other.
Creating a Client with Defaults
from anthropic import Anthropic
client = Anthropic()
With no arguments, the client uses these defaults:
| Setting | Default | Where it comes from |
|---|---|---|
| Credential | ANTHROPIC_API_KEY (then the other sources in Lesson 7) | Environment |
| API address | https://api.anthropic.com | ANTHROPIC_BASE_URL if set |
| Timeout | 10 minutes per request | Built-in default |
| Retries | 2 automatic retries for retryable errors | Built-in default |
| API version | The one tied to your SDK major version (2026-10-21 for 2.x) | Built-in |
For learning and many scripts, these defaults are exactly right.
Note: Creating a client does not contact the API. Network activity starts only with the first request. A client can therefore be created successfully even when the key is wrong or the network is down. Those problems appear as exceptions on the first call.
Constructor Options
You can override the defaults with keyword arguments:
from anthropic import Anthropic
client = Anthropic(
api_key="...", # Credential; normally omitted (use ANTHROPIC_API_KEY)
base_url="https://...", # API address
timeout=60.0, # Seconds per request
max_retries=3, # Automatic retries for retryable errors
default_headers={"anthropic-workspace-id": "wrkspc_..."}, # Sent on every request
)
| Option | Type | Purpose | When to change it |
|---|---|---|---|
api_key | str | The API key to use | The key comes from a secrets manager or a differently named variable |
auth_token | str | A bearer token instead of an API key | Gateways and token-based authentication |
base_url | str | The API address | Corporate gateways, proxies, or test servers |
timeout | float or httpx2.Timeout | Maximum wait per request | Interactive apps that must respond quickly |
max_retries | int | Number of automatic retries | Increase for batch jobs; set to 0 for custom retry logic |
default_headers | dict | Headers added to every request | Workspace selection, gateway headers |
http_client | DefaultHttpxClient | Custom HTTP configuration | Proxies and custom network settings |
Remember from Lesson 7: pass at most one credential (api_key or auth_token). Passing two raises ValueError.
Timeouts in Detail
A single number sets the overall timeout in seconds:
client = Anthropic(timeout=30.0)
For finer control, use an httpx2.Timeout object. httpx2 is installed with the SDK.
import httpx2
from anthropic import Anthropic
client = Anthropic(
timeout=httpx2.Timeout(60.0, connect=5.0),
)
Here, establishing the connection may take at most 5 seconds, and the other phases of the request use the 60-second default.
How to choose a timeout:
| Workload | Suggested approach |
|---|---|
| Short interactive answers | 30–60 seconds |
| Long documents or large outputs | Use streaming (Lessons 51–60) rather than a very long timeout |
| Background batch jobs | Keep the default or use the Message Batches API |
When a request times out, the SDK raises anthropic.APITimeoutError. Timed-out requests are also retried according to max_retries, so the worst-case wait is roughly the timeout multiplied by the number of attempts, plus the backoff delays between them.
Retries in Detail
client = Anthropic(max_retries=4)
Automatic retries apply only to errors that may succeed on a second try: connection errors, 408, 409, 429, and 5xx responses. Errors such as 400 or 401 are never retried, because the same request would fail the same way. Between attempts, the SDK waits with exponential backoff: each wait is longer than the previous one, which gives an overloaded service time to recover.
max_retries | Use when |
|---|---|
0 | You implement your own retry logic, or you are testing and want failures immediately |
2 (default) | Most applications |
3–5 | Background jobs where finishing matters more than speed |
Per-Request Settings with with_options()
Sometimes one request needs different settings, such as a shorter timeout for a quick classification. with_options() returns a copy of the client with the changes applied:
from anthropic import Anthropic
client = Anthropic() # Defaults: 10-minute timeout, 2 retries
message = client.with_options(timeout=15.0, max_retries=0).messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Is this spam? 'You won a prize!' Answer yes or no."}],
)
- The original
clientis unchanged. - The copy shares the original's connection pool, so creating it is cheap.
- A credential passed to
with_options()replaces the copy's credential (Lesson 7).
You can also keep the configured copy for repeated use:
fast_client = client.with_options(timeout=15.0)
client.copy(...) accepts the same options. with_options is the more readable name for per-request use.
The Client's Lifecycle
Create Once, Reuse Everywhere
A client keeps a connection pool: open HTTPS connections that are reused for later requests. Opening a new HTTPS connection requires several network round trips, including the TLS handshake, so reusing connections makes requests faster.
# Good: one client, reused
client = Anthropic()
for question in questions:
client.messages.create(...)
# Bad: a new client, and new connections, for every request
for question in questions:
Anthropic().messages.create(...)
The second version throws away the connection pool on every iteration. It works, but it is slower and wastes resources, and the effect grows with traffic.
Closing a Client
By default, a client's connections are closed automatically when the client object is garbage-collected. You can also close it explicitly:
client = Anthropic()
try:
...
finally:
client.close()
Or, more simply, with a context manager:
with Anthropic() as client:
message = client.messages.create(...)
# Connections are closed here, even if an exception occurred
| Situation | Approach |
|---|---|
| Long-running application (web server, worker) | Create one client at startup and keep it for the life of the process |
| Short script | Either approach; a context manager is the tidiest |
| Code that creates temporary clients (for example, per tenant) | Use with or call .close() so connections are released promptly |
Sync and Async Clients
Anthropic is synchronous: each call blocks until the response arrives. AsyncAnthropic has the same options and methods, but its calls must be awaited:
from anthropic import AsyncAnthropic
client = AsyncAnthropic(timeout=60.0, max_retries=3)
Use the async client in async frameworks such as FastAPI, and when sending many requests concurrently. Lessons 71–80 cover it in depth.
Multiple Clients in One Program
Each client is independent. That is useful when different parts of an application need different configurations:
import os
from anthropic import Anthropic
# Interactive features: fail fast
chat_client = Anthropic(timeout=30.0, max_retries=1)
# Background reports: be patient
report_client = Anthropic(timeout=300.0, max_retries=5)
# A second workspace, with its own key
analytics_client = Anthropic(api_key=os.environ["ANALYTICS_ANTHROPIC_KEY"])
If two clients differ only in timeout or retries, prefer one client plus with_options(). It shares connections and keeps configuration in one place. Create genuinely separate clients when credentials differ.
Practical Example: A Client Factory
In a real application, client creation belongs in one place, configured from settings and easy to replace in tests. A small factory function achieves that.
"""claude_client.py: create and share a configured Anthropic client."""
import logging
import os
from functools import lru_cache
import anthropic
from anthropic import Anthropic
logger = logging.getLogger(__name__)
@lru_cache(maxsize=1)
def get_client() -> Anthropic:
"""Return the application's shared client, creating it on first use."""
timeout = float(os.environ.get("CLAUDE_TIMEOUT", "60"))
max_retries = int(os.environ.get("CLAUDE_MAX_RETRIES", "2"))
client = Anthropic(timeout=timeout, max_retries=max_retries)
logger.info(
"Anthropic client ready (sdk=%s, timeout=%ss, max_retries=%s)",
anthropic.__version__,
timeout,
max_retries,
)
return client
def ask(prompt: str, client: Anthropic | None = None) -> str:
"""Send a single prompt and return the text of the reply."""
client = client or get_client()
message = client.messages.create(
model=os.environ.get("CLAUDE_MODEL", "claude-opus-5-5"),
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return "".join(block.text for block in message.content if block.type == "text")
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
print(ask("Name two benefits of reusing HTTP connections."))
Example output:
INFO:__main__:Anthropic client ready (sdk=2.0.0, timeout=60.0s, max_retries=2)
Reusing HTTP connections avoids repeating the connection and TLS setup for every request, which lowers latency. It also reduces CPU and network overhead on both the client and the server.
How It Works
@lru_cache(maxsize=1)remembers the return value ofget_client(). The first call creates the client, and every later call returns the same object. This is a simple, thread-safe-to-call way to get a shared, lazily created client: it is created only when first needed.- Settings come from environment variables with defaults (Lesson 8). They are converted with
float()andint(), because environment values are strings. - The startup log line records the SDK version and configuration. This is invaluable when diagnosing behavior in production. It never logs the key.
ask()accepts an optionalclientparameter. Normal code callsask("...")and uses the shared client. Tests can pass a fake or specially configured client instead. This technique is called dependency injection, and Lessons 181–184 use it for testing without real API calls.- The reply text is assembled from text blocks, so the function works even when a response contains other block types first.
Common Mistakes
Creating a Client per Request
What happens: latency increases and resources are wasted, especially under load.
Fix: create one client at startup, or use a cached factory as above, and reuse it.
Adding /v1 to base_url
client = Anthropic(base_url="https://api.anthropic.com/v1") # Wrong
What happens: the SDK adds its own /v1/... paths, so requests go to /v1/v1/messages and fail with 404 Not Found.
Fix: use only the host part, https://api.anthropic.com, or the base address your gateway documents.
Setting Very Long Timeouts Instead of Streaming
What happens: some networks drop idle connections. Requests then fail after a long wait, and are retried, multiplying the delay.
Fix: use streaming for long responses. The SDK even raises a ValueError for non-streaming requests that it expects to take longer than about 10 minutes, unless you enable streaming or set a timeout yourself.
Disabling Retries in Production Without a Replacement
What happens: brief network glitches and rate limits become user-facing errors.
Fix: keep the default retries, or implement your own retry logic deliberately (Lessons 143 and 146).
Using the Sync Client in Async Code
What happens: in FastAPI or other async applications, a blocking Anthropic call freezes the event loop, and every other request waits.
Fix: use AsyncAnthropic in async code.
Expecting Client Creation to Validate the Key
What happens: Anthropic() succeeds, and the code assumes everything is configured correctly.
Fix: creating a client does not contact the API. To verify configuration at startup, make a zero-token request such as client.models.list(limit=1) (Lesson 6).
Best Practices
- One long-lived client per process (per credential), created at startup or lazily through a cached factory.
- Configure timeouts deliberately: short for interactive features, with streaming for long outputs.
- Keep automatic retries enabled unless you replace them with your own logic.
- Use
with_options()for per-request differences instead of creating extra clients. - Use context managers or
.close()for short-lived clients. - Log the SDK version and client settings at startup, never the credential.
- Design functions to accept a client parameter so they can be tested without real API calls.
Exercise
Create a client with a 20-second timeout and 3 retries. Print
client.timeoutandclient.max_retriesto confirm the settings.Use
with_options()to send one request withmax_retries=0, and confirm that the original client still reportsmax_retries == 3.Rewrite this code so it reuses one client:
for topic in ["lists", "dicts", "sets"]: reply = Anthropic().messages.create( model="claude-opus-5-5", max_tokens=1024, messages=[{"role": "user", "content": f"Explain Python {topic} in one sentence."}], )Explain why
Anthropic(base_url="https://api.anthropic.com/v1")fails.
Steps 1–2:
from anthropic import Anthropic
client = Anthropic(timeout=20.0, max_retries=3)
print(client.timeout, client.max_retries) # 20.0 3
quick = client.with_options(max_retries=0)
print(quick.max_retries, client.max_retries) # 0 3
Step 3:
from anthropic import Anthropic
client = Anthropic()
for topic in ["lists", "dicts", "sets"]:
reply = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": f"Explain Python {topic} in one sentence."}],
)
print("".join(block.text for block in reply.content if block.type == "text"))
Step 4: the SDK appends its own endpoint paths, which already start with /v1, to base_url. The request goes to https://api.anthropic.com/v1/v1/messages, which does not exist, so the API returns 404 Not Found.