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.
Search across all documentation pages
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.
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.
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:
api_key argument (Option 2).x-api-key header manually (see Working Example).ANTHROPIC_API_KEY via your platform's secrets mechanism, not a checked-in file.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:
401 surface later.api_key explicitly to the constructor, which works identically to letting the SDK read the environment variable itself.anthropic.Anthropic() constructor checks for an explicit api_key argument first; if none is given, it falls back to reading ANTHROPIC_API_KEY from the process environment.x-api-key header on every outgoing HTTPS request, you never have to set it per call.anthropic.AuthenticationError in Python, mapped from a 401 HTTP response.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.
| 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 |
# 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 missing.env to .gitignore before the first commit that touches it..env file by accident. A missing or misconfigured .gitignore entry silently leaks the key. Fix: add .env to .gitignore first, then verify with git status that it's untracked before your first commit.401 (bad key) with 403 (valid key, no permission). Both are auth-stage errors but need different fixes. Fix: check the exception's status code; 401 means regenerate or fix the key, 403 means check the key's permissions/scope on your account.x-api-key header when logging requests.anthropic-version header 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.| 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 |
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.
The SDK raises an error when you try to construct the client or make a call, since it has no key to send.
401 means the key itself is missing, malformed, or invalid.403 means the key is valid but doesn't have permission for what you're trying to do.Yes, instantiate multiple anthropic.Anthropic(api_key=...) clients, each with its own key, rather than relying on a single shared environment variable.
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.
An anthropic-version header specifying the API version, and a content-type: application/json header for the request body.
Yes, treat it like any credential; rotating periodically limits the damage window if a key is ever exposed without your knowledge.
Scrub headers before logging full request objects, and avoid logging raw request/response dumps in production without redaction.
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.
No, authentication is identical either way; only the stream field in the request body and the response shape differ.
A gitignored .env file loaded with a library like python-dotenv, kept out of version control from the very first commit.
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 18, 2026