Claude API Authentication Header Format Explained
Claude's API does not use the Authorization: Bearer header that most REST APIs expect. Instead, Anthropic's native API uses a custom header called x-api-key, combined with a required anthropic-version header. If you're getting 401 or 400 errors despite having a valid key, the cause is almost always a missing or malformed header, not an invalid key.
Here is the exact format for a correct request to the native Claude API:
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello, Claude"}]
}'
Three headers matter here, and missing any one of them produces a different failure mode.
The three required headers
x-api-key This replaces the Authorization header entirely. Do not send Authorization: Bearer sk-ant-... to api.anthropic.com — it will be ignored or rejected depending on the endpoint. The key goes in x-api-key as a raw string, no Bearer prefix, no quotes around the value in the header itself.
anthropic-version This is a required date-stamped version string (e.g. 2023-06-01) that tells the API which request/response schema to use. Omitting it returns a 400 error, not a 401, which confuses people who assume the problem is their key. Anthropic rolls out breaking changes behind new version strings, so pinning this value in your config is good practice — don't let it drift unintentionally across environments.
content-type: application/json Required for any POST with a JSON body. Without it, some HTTP clients (older versions of requests in Python, for instance) will silently send text/plain and the request will fail with a generic 400.
A minimal JavaScript example
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"content-type": "application/json",
"x-api-key": process.env.ANTHROPIC_API_KEY,
"anthropic-version": "2023-06-01",
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello, Claude" }],
}),
});
const data = await response.json();
console.log(data);
If you're using Anthropic's official SDKs (@anthropic-ai/sdk for Node, anthropic for Python), the SDK sets all three headers for you automatically once you pass the API key to the client constructor. Most header-format bugs happen when people build raw HTTP requests by hand or port a cURL snippet from a different API's documentation.
Common mistakes that cause auth failures
- Using
Authorization: Bearer <key>instead ofx-api-key. This is the single most common mistake, usually carried over from OpenAI-style integrations. - Forgetting
anthropic-version. Produces a 400 with a message about a missing header, easy to miss in logs if you're only checking status codes. - Quoting the key value incorrectly in shell scripts, e.g. wrapping an already-quoted env var in extra quotes, which appends literal quote characters to the key.
- Using a key from the wrong workspace or organization. Keys are scoped; a key created under one workspace won't authenticate requests meant for a different one's rate limits or billing.
- Leading or trailing whitespace in the key string after copying from a
.envfile or secrets manager — this silently breaks the header value.
Why this trips people up
Most developers' mental model of API auth is Authorization: Bearer <token>, because that's the convention used by OAuth2, Stripe, GitHub, OpenAI, and dozens of other APIs. Claude's native API deliberately deviates from that convention. If you're switching a codebase from OpenAI's API to Claude's, or building a provider-agnostic LLM client, this is the header change that catches people most often — not the request body format, not the model names, the headers.
If you want standard Bearer-token auth instead
If your use case is to expose Claude access as an internal or external API for your own application — giving teammates or services their own scoped keys, usage tracking, and standard Authorization: Bearer auth — building and maintaining that gateway yourself means handling key issuance, per-key usage metering, rate limiting, and streaming reconnection logic on top of the native header format above.
SubToAPI does this for you: it sits in front of your Claude access and issues standard sub_live_... application keys that your own apps or teammates use with a normal Authorization: Bearer $SUBTOAPI_KEY header, over plain HTTPS. You keep the x-api-key / anthropic-version complexity on the SubToAPI side, and your application code gets a familiar Bearer-token interface, streaming, tool use, and per-key usage metadata in one dashboard. See the quickstart or the messages endpoint docs for the exact request shape, and /pricing for plan details (Solo €9, Team €19/seat, Scale €49/seat, free trial at signup).
Quick checklist for debugging a 401/400
- Confirm you're sending
x-api-key, notAuthorization, toapi.anthropic.com. - Confirm
anthropic-versionis present and set to a valid date string. - Print the raw header value in your client code and check for stray whitespace or quote characters.
- Confirm the key belongs to the workspace you think it does — test it with a trivial
curlrequest in isolation before blaming your application code. - Check
content-type: application/jsonis set on POST requests with a body.
Questions
Does Claude's API support Authorization: Bearer at all? Not on Anthropic's native api.anthropic.com endpoints — those require x-api-key. Some gateways and proxies, including SubToAPI, accept standard Authorization: Bearer keys and translate them internally.
What happens if I omit anthropic-version? You get a 400 error indicating a missing required header, not a 401. It's easy to mistake this for an invalid key if you're not reading the error body closely.
Can I rotate my API key without changing the header format? Yes. The header name and structure (x-api-key: <value>) stay the same; only the key value changes. Update the value in your secrets manager or environment variable and redeploy — no code changes needed.