Set API Keys
How the SDK Gets Your API Key
Once you have an API key (Lesson 6), the SDK needs access to it so it can authenticate each request. There are two ways to provide it:
- Automatically, from the
ANTHROPIC_API_KEYenvironment variable. This is the recommended default. - Explicitly, by passing it to the client:
Anthropic(api_key=...).
from anthropic import Anthropic
client = Anthropic() # Automatic: reads ANTHROPIC_API_KEY
client = Anthropic(api_key=some_value) # Explicit: uses exactly this value
Whichever way you choose, the SDK sends the key on every request as an Authorization: Bearer <key> header (SDK 2.0.0 and later). You never build that header yourself.
This lesson explains both approaches, the rules the SDK follows when looking for credentials, and how to set the key correctly on every operating system.
Why Not Just Write the Key in the Code?
The simplest-looking approach is the worst one:
# Do NOT do this
client = Anthropic(api_key="sk-ant-api03-abc123...")
Hardcoding a key causes several problems:
- Source code is shared. It goes into Git, gets pushed to GitHub, is pasted into chats, and appears in screenshots. Anyone who sees the key can use it, and you pay for their usage.
- Git never forgets. Deleting the line in a later commit does not remove it from the repository history.
- Changing the key means changing code. Rotating a key would require editing files and redeploying.
- Every environment gets the same key. Development and production should use different keys.
The solution is to keep configuration out of code. The code says "use the API key"; the environment the code runs in decides which key that is. This principle is a core part of the widely used Twelve-Factor App methodology, and it is exactly what ANTHROPIC_API_KEY is designed for.
What an Environment Variable Is
An environment variable is a named value that the operating system passes to every program it starts. Your terminal has a set of them, such as PATH and HOME on macOS/Linux or USERPROFILE on Windows. When you run python script.py, the Python process receives a copy of the terminal's environment variables.
Python reads them through os.environ:
import os
key = os.environ.get("ANTHROPIC_API_KEY")
print("Key is set" if key else "Key is NOT set")
Important properties:
- A process receives a copy of the environment at the moment it starts. Changing a variable later does not affect processes that are already running.
- Variables set in one terminal window do not appear in other windows unless they were set persistently.
- Variables are inherited by child processes. Programs started from your terminal see the variables you set there.
Lesson 8 covers environment variables in depth, including .env files. This lesson focuses on getting your API key into them.
Method 1: Set ANTHROPIC_API_KEY (Recommended)
Temporary: Current Terminal Session Only
Use this for quick tests. The variable disappears when you close the terminal.
macOS / Linux (bash, zsh):
export ANTHROPIC_API_KEY="sk-ant-api03-..."
Windows PowerShell:
$env:ANTHROPIC_API_KEY = "sk-ant-api03-..."
Windows Command Prompt:
set ANTHROPIC_API_KEY=sk-ant-api03-...
Warning: In Command Prompt, do not put quotes around the value.
set ANTHROPIC_API_KEY="sk-ant-..."stores the quotation marks as part of the key, and every request then fails with an authentication error.
Then run your script in the same terminal window:
python first_look.py
Avoid Leaving the Key in Shell History
Commands you type are usually saved in your shell's history file. A key typed directly into an export command can therefore end up on disk in plain text. On macOS and Linux, you can enter the key without it appearing on screen or in history:
read -s ANTHROPIC_API_KEY # Paste the key, then press Enter (nothing is displayed)
export ANTHROPIC_API_KEY
read -sreads a line silently, without echoing it, and stores it in a shell variable.exportturns that shell variable into an environment variable, so programs started from this shell can see it.
Persistent: Every New Terminal
To avoid setting the key every time, store it in your shell's startup file or in your user environment.
macOS (zsh is the default shell):
echo 'export ANTHROPIC_API_KEY="sk-ant-api03-..."' >> ~/.zshrc
source ~/.zshrc
Linux (bash is common):
echo 'export ANTHROPIC_API_KEY="sk-ant-api03-..."' >> ~/.bashrc
source ~/.bashrc
>>appends the line to the startup file. A single>would overwrite the whole file.- The single quotes around the full
echoargument keep the inner double quotes intact. sourcereloads the file in the current terminal. New terminals load it automatically.
Windows (PowerShell or Command Prompt):
setx ANTHROPIC_API_KEY "sk-ant-api03-..."
setx saves the variable to your user profile. It affects only new terminals, not the one where you ran it, so open a new terminal afterward. You can also set user environment variables through System Properties → Environment Variables.
Note: Startup files and user environment variables are stored as plain text on your computer. That is acceptable for a personal development machine with a key that has an expiration date. It is not appropriate for servers or shared machines, which should use a secrets manager or their platform's secret settings.
Verify Without Revealing the Key
Avoid commands that print the full key, such as echo $ANTHROPIC_API_KEY. Terminal output gets recorded in screen shares, recordings, and logs. Print a masked version instead:
python -c "import os; k = os.environ.get('ANTHROPIC_API_KEY', ''); print(f'{k[:10]}...{k[-4:]} ({len(k)} chars)' if k else 'NOT SET')"
sk-ant-api...x7Qa (108 chars)
The length shown depends on your key. What matters is that the prefix is sk-ant- and the value is not empty.
Use It in Code
With the variable set, the client needs no arguments:
from anthropic import Anthropic
client = Anthropic() # The key is read from ANTHROPIC_API_KEY
message = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Say: key loaded from the environment"}],
)
print("".join(block.text for block in message.content if block.type == "text"))
The answer is collected from all text blocks rather than read from message.content[0], because the first block can be a thinking block. max_tokens=1024 leaves room for any thinking before the short answer (Lessons 11 and 13).
Why this is the recommended approach: the same code runs unchanged on your laptop, a teammate's machine, a CI server, and production. Each environment supplies its own key. Nothing secret is ever stored in the code.
Method 2: Pass the Key Explicitly with api_key
The api_key argument is useful when the key does not come from ANTHROPIC_API_KEY. For example:
- your application loads secrets from a secrets manager (AWS Secrets Manager, Google Secret Manager, HashiCorp Vault, Azure Key Vault),
- your configuration system stores the key under a different name, or
- your application uses different keys for different purposes, such as one per tenant or one per workspace.
import os
from anthropic import Anthropic
# The key comes from a differently named variable in your configuration
client = Anthropic(api_key=os.environ["SUPPORT_BOT_ANTHROPIC_KEY"])
A more realistic example loads it from a secrets manager. The function below is a placeholder for your platform's secrets client:
from anthropic import Anthropic
def load_secret(name: str) -> str:
"""Placeholder: fetch a secret from your secrets manager."""
...
client = Anthropic(api_key=load_secret("anthropic/production/api-key"))
Key point: api_key= is not an excuse to hardcode. The value passed should always come from somewhere outside the source code.
Explicit Arguments Win
When you pass a credential explicitly, the SDK uses only that credential and does not look at environment variables:
import os
from anthropic import Anthropic
os.environ["ANTHROPIC_API_KEY"] = "sk-ant-environment-key" # For demonstration only
client = Anthropic(api_key="sk-ant-explicit-key")
# This client uses "sk-ant-explicit-key". The environment variable is ignored.
This is useful in tests and multi-key applications, because you know exactly which key a client uses regardless of the environment.
Method 3: Override the Key for Specific Requests
with_options() returns a copy of the client with some settings changed. A credential passed to it replaces the original client's credential for that copy:
from anthropic import Anthropic
default_client = Anthropic() # Uses ANTHROPIC_API_KEY
tenant_key = get_key_for_customer(customer_id) # Your own lookup function
tenant_client = default_client.with_options(api_key=tenant_key)
message = tenant_client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
default_clientkeeps using the original key.tenant_clientusestenant_key, but shares the other settings, such as timeouts and retries.
This pattern suits applications that bill each customer's usage to a different key or workspace.
How the SDK Resolves Credentials
Since SDK 2.0.0, a client holds exactly one credential. The rules are:
If you pass a credential in code, such as
api_key=orauth_token=, that is the only one used. The environment is not consulted.Passing two or more credentials raises
ValueError:Anthropic(api_key=key, auth_token=token) # ValueErrorPassing
Nonedoes not count as passing a credential.If you pass none, the SDK uses the first of these that is set:
| Order | Source | Typical use |
|---|---|---|
| 1 | ANTHROPIC_API_KEY | API keys: the method used in this tutorial |
| 2 | ANTHROPIC_AUTH_TOKEN | Bearer tokens, for example from a gateway or token service |
| 3 | The profile named by ANTHROPIC_PROFILE | Named credential profiles, such as those created with Anthropic's ant CLI |
| 4 | Workload Identity Federation variables | Short-lived credentials on cloud platforms and in CI |
| 5 | The active profile on disk | A default profile configured with the CLI |
If both ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN are set, the SDK uses the API key and logs a warning once.
Why there is an order: the SDK supports several authentication methods for different environments. A fixed order means behavior is predictable: the same environment always produces the same credential choice.
What happens if nothing is found: the client cannot authenticate, and you get an error instead of a response. The fix is always to set ANTHROPIC_API_KEY or pass api_key=.
Note: For this tutorial, you only need
ANTHROPIC_API_KEY. The other sources are covered with cloud deployment and security in later lessons.
Keys in Real Applications
Editors and Notebooks
Editors, notebook servers, and their terminals receive environment variables when they start:
- After
setxon Windows or editing~/.zshrc/~/.bashrc, restart VS Code, PyCharm, or Jupyter so they pick up the new variable. - A Jupyter kernel that was running before you set the key does not see it. Restart the kernel.
Web Applications (Django Example)
In a web framework, read the key once in configuration and create one shared client:
# settings.py
import os
ANTHROPIC_API_KEY = os.environ["ANTHROPIC_API_KEY"]
# services/claude.py
from anthropic import Anthropic
from django.conf import settings
client = Anthropic(api_key=settings.ANTHROPIC_API_KEY)
def ask_claude(question: str) -> str:
message = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": question}],
)
return "".join(block.text for block in message.content if block.type == "text")
Why it is structured this way:
os.environ["ANTHROPIC_API_KEY"]uses square brackets, not.get(). A missing key then raisesKeyErrorat startup, an obvious and early failure, instead of failing later on the first user request.- The client is created once, at module import, and reused for every request. Clients keep connection pools, so creating one per request wastes resources (Lesson 1).
- Views import
ask_claudeand never touch the key.
Servers, Containers, and Hosting Platforms
On servers, the key is provided by the platform rather than by your shell:
| Environment | Where the key is set |
|---|---|
| Linux service (systemd) | An Environment= line or an EnvironmentFile= in the service unit |
| Docker | docker run -e ANTHROPIC_API_KEY ... or an env file, never baked into the image |
| CI (GitHub Actions, GitLab CI) | The platform's encrypted secrets settings |
| PaaS hosting | The platform's environment variable or secrets dashboard |
The application code is identical in all of them. It calls Anthropic() or reads the key from its configuration.
Practical Example: A Key Status Checker
This script reports whether a key is configured, checks its format, and optionally verifies it against the API with a zero-token request. It never prints the full key.
"""key_status.py: check how the Anthropic API key is configured."""
import os
import sys
import anthropic
from anthropic import Anthropic
def mask(secret: str) -> str:
"""Show only enough of a secret to identify it."""
if len(secret) <= 14:
return "*" * len(secret)
return f"{secret[:10]}...{secret[-4:]}"
def inspect_key() -> str | None:
key = os.environ.get("ANTHROPIC_API_KEY")
if key is None:
print("[!] ANTHROPIC_API_KEY is not set in this process's environment.")
return None
problems = []
if key != key.strip():
problems.append("contains leading/trailing whitespace or a newline")
if key.startswith(("'", '"')) or key.endswith(("'", '"')):
problems.append("contains quotation marks (common with Windows 'set')")
if not key.strip().strip("'\"").startswith("sk-ant-"):
problems.append("does not start with 'sk-ant-'")
print(f"[+] ANTHROPIC_API_KEY found: {mask(key.strip())}")
for problem in problems:
print(f"[!] The value {problem}.")
if os.environ.get("ANTHROPIC_AUTH_TOKEN"):
print("[~] ANTHROPIC_AUTH_TOKEN is also set; the API key takes precedence.")
return key
def verify_with_api() -> bool:
try:
first_model = next(iter(Anthropic().models.list(limit=1)))
except anthropic.AuthenticationError:
print("[!] The API rejected the key (invalid, expired, disabled, or deleted).")
return False
except anthropic.APIConnectionError:
print("[!] Could not reach the API to verify the key.")
return False
except anthropic.APIStatusError as error:
print(f"[!] API error {error.status_code}: {error.message}")
return False
print(f"[+] The API accepted the key (first model listed: {first_model.id}).")
return True
def main() -> int:
if inspect_key() is None:
return 1
if "--verify" in sys.argv:
return 0 if verify_with_api() else 1
print(" Run with --verify to test the key against the API (uses no tokens).")
return 0
if __name__ == "__main__":
sys.exit(main())
Run a local check, then a live check:
python key_status.py
python key_status.py --verify
Example output with a correct setup:
[+] ANTHROPIC_API_KEY found: sk-ant-api...x7Qa
[+] The API accepted the key (first model listed: claude-opus-5-5).
Example output after using quotes with Windows set:
[+] ANTHROPIC_API_KEY found: "sk-ant-ap...7Qa"
[!] The value contains quotation marks (common with Windows 'set').
How the Script Works
mask()shows the first 10 and last 4 characters. That is enough to tell which key is in use without exposing it. Very short values are fully masked.inspect_key()checks for the three most common configuration errors before any network call: whitespace or newlines from copy-pasting, literal quotation marks, and a value that does not look like a Claude API key. It also warns whenANTHROPIC_AUTH_TOKENis set too, because only the API key will be used.verify_with_api()callsclient.models.list(limit=1), which authenticates without running a model.next(iter(...))takes the first result, and that forces the request to happen inside thetryblock.--verifymakes the network check optional, so the local check works even offline.
Common Mistakes
Setting the Variable in One Terminal, Running Code in Another
What happens: you export the key in one terminal window and run the script from another window, or from your editor's Run button. The key is missing.
Why: temporary variables exist only in the shell where they were set and in processes started from it.
Fix: run the script in the same terminal, or set the variable persistently and restart the editor.
Running setx and Testing in the Same Window
Why it fails: setx writes to your user profile but does not change the current session.
Fix: open a new terminal after setx.
Quotes Included in the Value (Windows Command Prompt)
What happens: set ANTHROPIC_API_KEY="sk-ant-..." stores the quotes as part of the key, and authentication fails.
Fix: in Command Prompt, write set ANTHROPIC_API_KEY=sk-ant-... with no quotes. PowerShell and bash need quotes, and handle them correctly.
Whitespace or Line Breaks from Copy-Paste
What happens: the copied key includes a trailing space or newline, and requests fail with authentication or invalid-header errors.
Fix: paste carefully, and check with key_status.py. When loading keys from files or secrets managers, call .strip() on the value.
Using the Wrong Variable Name
What happens: you set CLAUDE_API_KEY, ANTHROPIC_KEY, or anthropic_api_key, and Anthropic() cannot find a credential.
Fix: the SDK reads exactly ANTHROPIC_API_KEY. On macOS and Linux, environment variable names are case-sensitive. If your project uses another name, pass it explicitly: Anthropic(api_key=os.environ["MY_NAME"]).
Setting the Key Inside Python Code
os.environ["ANTHROPIC_API_KEY"] = "sk-ant-api03-..." # Still hardcoding
Why it is a mistake: the key is still written in source code. It has the same leak risk as Anthropic(api_key="sk-ant-...").
Fix: set the variable outside the program: in the shell, a .env file ignored by Git (Lesson 8), or a secrets manager.
Passing Two Credentials
What happens: Anthropic(api_key=key, auth_token=token) raises ValueError in SDK 2.x.
Fix: pass exactly one credential per client.
Printing or Logging the Full Key
What happens: the key appears in application logs, error trackers, or recorded terminal sessions.
Fix: never log the key. If you need to identify which key is in use, log a masked version.
Best Practices
- Use
ANTHROPIC_API_KEYby default, so thatAnthropic()with no arguments works everywhere. - Keep keys out of source code entirely, including
os.environ[...] = "..."assignments. - Use
api_key=only with values loaded from outside the code, such as a secrets manager or configuration. - Fail fast at startup when the key is missing (
os.environ["ANTHROPIC_API_KEY"]) rather than at the first user request. - Create the client once and reuse it. Use
with_options(api_key=...)for per-tenant keys. - Use different keys per environment: development, staging, production.
- Never print or log full keys. Mask them.
- Use your platform's secret management on servers and in CI instead of shell startup files.
- Restart editors, kernels, and services after changing environment variables.
Exercise
- Set
ANTHROPIC_API_KEYtemporarily in your terminal usingread -s(macOS/Linux) or$env:(PowerShell). Runkey_status.py --verify. - Open a new terminal and run
key_status.pyagain. What happens, and why? - Make the variable persistent for your operating system. Open a new terminal and confirm that
key_status.py --verifysucceeds. - Write a short script that creates two clients: one using
ANTHROPIC_API_KEYautomatically, and one created withwith_options()using a deliberately invalid key. Callmodels.list(limit=1)on both and handle the error from the second.
import anthropic
from anthropic import Anthropic
default_client = Anthropic()
bad_client = default_client.with_options(api_key="sk-ant-invalid-key-for-testing")
for name, client in [("default", default_client), ("invalid", bad_client)]:
try:
model = next(iter(client.models.list(limit=1)))
print(f"{name}: OK (first model: {model.id})")
except anthropic.AuthenticationError:
print(f"{name}: authentication failed")
Expected output:
default: OK (first model: claude-opus-5-5)
invalid: authentication failed
How it works: with_options(api_key=...) creates a copy of the client whose credential is replaced by the new value, while the original client is unchanged. The loop runs the same zero-token request with both clients. The invalid key produces AuthenticationError (HTTP 401), which is caught and reported instead of crashing the script.
For step 2: the new terminal does not have the variable, because a temporary variable exists only in the shell where it was set and in processes started from that shell.