Set API Keys

Su Suriya Ravichandran Updated 10 Nov 2026
15 min read ·Lesson 7 of 200

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:

  1. Automatically, from the ANTHROPIC_API_KEY environment variable. This is the recommended default.
  2. 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.


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 -s reads a line silently, without echoing it, and stores it in a shell variable.
  • export turns 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 echo argument keep the inner double quotes intact.
  • source reloads 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_client keeps using the original key.
  • tenant_client uses tenant_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:

  1. If you pass a credential in code, such as api_key= or auth_token=, that is the only one used. The environment is not consulted.

  2. Passing two or more credentials raises ValueError:

    Anthropic(api_key=key, auth_token=token)   # ValueError

    Passing None does not count as passing a credential.

  3. If you pass none, the SDK uses the first of these that is set:

OrderSourceTypical use
1ANTHROPIC_API_KEYAPI keys: the method used in this tutorial
2ANTHROPIC_AUTH_TOKENBearer tokens, for example from a gateway or token service
3The profile named by ANTHROPIC_PROFILENamed credential profiles, such as those created with Anthropic's ant CLI
4Workload Identity Federation variablesShort-lived credentials on cloud platforms and in CI
5The active profile on diskA 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 setx on 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 raises KeyError at 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_claude and never touch the key.

Servers, Containers, and Hosting Platforms

On servers, the key is provided by the platform rather than by your shell:

EnvironmentWhere the key is set
Linux service (systemd)An Environment= line or an EnvironmentFile= in the service unit
Dockerdocker 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 hostingThe 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 when ANTHROPIC_AUTH_TOKEN is set too, because only the API key will be used.
  • verify_with_api() calls client.models.list(limit=1), which authenticates without running a model. next(iter(...)) takes the first result, and that forces the request to happen inside the try block.
  • --verify makes 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_KEY by default, so that Anthropic() 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

  1. Set ANTHROPIC_API_KEY temporarily in your terminal using read -s (macOS/Linux) or $env: (PowerShell). Run key_status.py --verify.
  2. Open a new terminal and run key_status.py again. What happens, and why?
  3. Make the variable persistent for your operating system. Open a new terminal and confirm that key_status.py --verify succeeds.
  4. Write a short script that creates two clients: one using ANTHROPIC_API_KEY automatically, and one created with with_options() using a deliberately invalid key. Call models.list(limit=1) on both and handle the error from the second.
Show a possible solution for step 4
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.

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 Set API Keys and get answers drawn from it.

Signed-in readers only.