Authenticating Claude API Calls with the x-api-key Header
Every request to the Claude API has to prove who is calling before the server will do anything else.
That proof is a single header: x-api-key, carrying your API key.
This page covers where to get a key, how to configure it for the Python SDK, and how to send it manually when you're not using the SDK.
Summary
The x-api-key header is how the Claude API authenticates every request, checked before the request body is even inspected.
The official Python SDK reads your key from the ANTHROPIC_API_KEY environment variable by default, so most code never touches the header directly.
You can also pass the key explicitly to the client constructor, which is useful for multi-key setups or per-request key rotation.
Raw HTTP callers (curl, requests, or another language's HTTP client) must set x-api-key themselves, along with a couple of other required headers.
Treating API keys like any other secret, never hardcoded, never committed, rotated on a schedule, is the difference between a minor scare and a real incident.
Recipe
Quick-reference recipe card - copy-paste ready.
import os
import anthropic
# Option 1: implicit - SDK reads ANTHROPIC_API_KEY from the environment
client = anthropic.Anthropic()
# Option 2: explicit - pass the key directly
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])When to reach for this:
- Setting up a new project: use the environment variable approach (Option 1).
- Supporting multiple accounts or key rotation in one process: use the explicit
api_keyargument (Option 2). - Calling the API without the SDK: set the
x-api-keyheader manually (see Working Example). - Running in CI or a container: inject
ANTHROPIC_API_KEYvia your platform's secrets mechanism, not a checked-in file.
Working Example
import os
import anthropic
def get_client() -> anthropic.Anthropic:
api_key = os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
raise RuntimeError(
"ANTHROPIC_API_KEY is not set. Export it before running this script."
)
return anthropic.Anthropic(api_key=api_key)
def main() -> None:
client = get_client()
response = client.messages.create(
model="claude-sonnet-5-20260630",
max_tokens=100,
messages=[{"role": "user", "content": "Confirm that authentication is working."}],
)
print(response.content[0].text)
if __name__ == "__main__":
main()What this demonstrates:
- Failing fast with a clear error message when the key is missing, rather than letting a cryptic
401surface later. - Reading the key from the environment explicitly, which makes the dependency visible at the top of the script.
- Passing
api_keyexplicitly to the constructor, which works identically to letting the SDK read the environment variable itself.
Deep Dive
How It Works
- The SDK's
anthropic.Anthropic()constructor checks for an explicitapi_keyargument first; if none is given, it falls back to readingANTHROPIC_API_KEYfrom the process environment. - Internally, the SDK attaches your key as the
x-api-keyheader on every outgoing HTTPS request, you never have to set it per call. - The server validates this header before parsing the rest of the request body, which is why authentication failures return quickly and independently of whether your prompt or parameters are valid.
- A missing or invalid key raises
anthropic.AuthenticationErrorin Python, mapped from a401HTTP response.
Raw HTTP Without the SDK
If you're calling the API directly (for a language without an SDK, or to debug at the HTTP level), set the header yourself:
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2026-01-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-5-20260630",
"max_tokens": 100,
"messages": [{"role": "user", "content": "Hello"}]
}'Note that raw calls need both x-api-key and an anthropic-version header; the SDK sets the version header for you automatically.
Key Storage Options
| Approach | Good For | Risk |
|---|---|---|
.env file + python-dotenv, gitignored | Local development | Accidental commit if .gitignore is misconfigured |
| Platform secrets manager (e.g. a cloud secrets store, CI secret variables) | Production, CI/CD | Requires initial setup, but the safest long-term option |
| Hardcoded string in source | Never | Key leaks to version control, logs, and anyone with repo access |
Python Notes
# Prefer os.environ[...] (raises KeyError loudly) over os.environ.get(...)
# with a silent None fallback, when the key is genuinely required:
import os
api_key = os.environ["ANTHROPIC_API_KEY"] # raises clearly if missingGotchas
- Hardcoding the key in source. It ends up in version control history permanently, even if you delete it in a later commit. Fix: use environment variables or a secrets manager, and add
.envto.gitignorebefore the first commit that touches it. - Committing a
.envfile by accident. A missing or misconfigured.gitignoreentry silently leaks the key. Fix: add.envto.gitignorefirst, then verify withgit statusthat it's untracked before your first commit. - Confusing
401(bad key) with403(valid key, no permission). Both are auth-stage errors but need different fixes. Fix: check the exception's status code;401means regenerate or fix the key,403means check the key's permissions/scope on your account. - Logging the full request object, including headers, in debug output. This can leak the key into log files or observability tools. Fix: scrub or omit the
x-api-keyheader when logging requests. - Sharing one key across every environment (dev, staging, prod). A leak or bug in one environment compromises all of them. Fix: issue separate keys per environment so you can revoke one without affecting the others.
- Forgetting the
anthropic-versionheader on raw HTTP calls. Raw callers sometimes copy the auth header but forget this one, causing confusing failures unrelated to authentication itself. Fix: always send both headers together for non-SDK calls.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Environment variable (ANTHROPIC_API_KEY) + implicit SDK pickup | Single-key apps, local dev, most production services | You need multiple keys active in one process |
Explicit api_key argument per client instance | Multi-tenant apps, key rotation, per-customer keys | A single static key covers your use case; adds unneeded complexity |
| Secrets manager injecting the env var at deploy time | Production and CI/CD | Local development, where a .env file is simpler |
FAQs
Do I have to set the x-api-key header manually when using the Python SDK?
No. anthropic.Anthropic() attaches it automatically from either the ANTHROPIC_API_KEY environment variable or an explicit api_key argument; you only set the header yourself for raw HTTP calls.
What happens if ANTHROPIC_API_KEY is not set and I don't pass api_key explicitly?
The SDK raises an error when you try to construct the client or make a call, since it has no key to send.
What's the difference between a 401 and a 403 response?
401means the key itself is missing, malformed, or invalid.403means the key is valid but doesn't have permission for what you're trying to do.
Can I use a different API key for different parts of my application?
Yes, instantiate multiple anthropic.Anthropic(api_key=...) clients, each with its own key, rather than relying on a single shared environment variable.
Is it safe to put my API key directly in my Python script for a quick test?
Only for a throwaway local test you will never commit; even then, an environment variable is just as fast to set up and avoids the risk of an accidental commit.
What header do raw HTTP callers need besides x-api-key?
An anthropic-version header specifying the API version, and a content-type: application/json header for the request body.
Should I rotate my API key on a schedule?
Yes, treat it like any credential; rotating periodically limits the damage window if a key is ever exposed without your knowledge.
How do I avoid leaking my key through logging?
Scrub headers before logging full request objects, and avoid logging raw request/response dumps in production without redaction.
Can I revoke a single API key without affecting others?
Yes, this is exactly why separate keys per environment or per service are worth the setup cost, a compromised key can be revoked in isolation.
Does the x-api-key header change between streaming and non-streaming requests?
No, authentication is identical either way; only the stream field in the request body and the response shape differ.
What's the safest way to store a key for local development?
A gitignored .env file loaded with a library like python-dotenv, kept out of version control from the very first commit.
Related
- Understanding the Claude API Request Lifecycle - where authentication fits in the overall request flow.
- Installing and Configuring the Anthropic Python SDK - client setup beyond just the key.
- Claude API Error Codes and Troubleshooting Reference - full list of auth-related error codes.
- Building a Reusable Claude API Client Wrapper - centralizing key handling across an app.
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.