Claude API Authentication Headers Example (Full Guide)
Every request to a Claude-compatible API needs a specific set of HTTP headers to authenticate and format correctly. Get one wrong — a missing header, a bad version string, the wrong auth scheme — and you get a 401 or 400 before your prompt ever reaches the model. This article shows exactly which headers to send, with working examples, plus the mistakes that cause most authentication failures.
The short answer: Anthropic's native API expects an x-api-key header plus an anthropic-version header and a JSON content-type. If you're calling through SubToAPI instead, authentication is simpler — a single standard Authorization: Bearer header, the same pattern used by most modern REST APIs. Below we cover both, so you know exactly what to send depending on which API you're integrating.
The Core Headers You Need
Regardless of which backend you're calling, a Claude request almost always needs three things:
- An authentication header — proves who's calling.
- A content-type header — tells the server you're sending JSON.
- A version or API identifier — some backends require this to pick the correct request/response schema.
Here's what that looks like against Anthropic's native endpoint:
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-20250514",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello, Claude"}]
}'
Three details trip people up here:
- The auth header is
x-api-key, notAuthorization: Bearer. Anthropic's API doesn't use the Bearer scheme natively. anthropic-versionis mandatory. Omit it and you'll get a 400, not a helpful "please add this header" message.content-type: application/jsonis required even though it seems obvious — some HTTP clients don't set it by default on POST bodies.
The Same Request via SubToAPI
If you're using SubToAPI to turn Claude into a standard HTTPS API for your app, authentication is simplified to the pattern most developers already know from Stripe, OpenAI, and similar APIs:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-20250514",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello, Claude"}]
}'
No x-api-key, no version header to remember, no separate account-level credentials to manage per environment. You create an application key (sub_live_...) in the dashboard, put it in your server-side environment, and send it as a standard Bearer token on every request. That's the whole authentication model. See the quickstart for the full setup and messages docs for request/response details.
Setting Headers in JavaScript
Here's the same call from Node.js using fetch, which is how most backend integrations actually look in production:
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
model: "claude-opus-4-20250514",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello, Claude" }],
}),
});
if (!response.ok) {
throw new Error(`Auth or request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
Two habits worth adopting from day one:
- Never hardcode the key. Read it from
process.envand load it via a.envfile locally, or your platform's secret manager in production. - Check
response.okbefore parsing. A 401 with a JSON error body will otherwise get silently swallowed if you jump straight to.json().
Common Authentication Header Mistakes
Most "it doesn't work" reports trace back to one of these:
- Wrong auth scheme. Sending
Authorization: Bearerto an endpoint that expectsx-api-key, or vice versa. Check which backend you're targeting. - Leading/trailing whitespace in the key. Copy-pasting from a dashboard sometimes grabs a trailing newline. This produces a 401 that looks identical to an invalid key.
- Using a client-side key in a browser app. API keys should never ship in frontend JavaScript — anyone can read them from dev tools. Proxy requests through your own backend.
- Missing
content-type. Some HTTP libraries silently default totext/plain, and the server can't parse the body correctly even if auth succeeds. - Reusing a test key in production. Keep separate keys per environment so a leaked staging key doesn't expose production traffic.
Verifying Your Headers Are Correct
Before wiring authentication into application code, test it in isolation with curl -v to see the raw request and response:
curl -v https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "content-type: application/json" \
-d '{"model":"claude-opus-4-20250514","max_tokens":10,"messages":[{"role":"user","content":"ping"}]}'
A 200 with a valid JSON body confirms your headers are correct. A 401 means the auth header itself is wrong — check the key value and scheme. A 400 usually means the headers are fine but the request body is malformed.
If you're evaluating providers, SubToAPI gives every application its own sub_live_... key, tracks usage per key on the dashboard, and supports streaming and tool use with the same header pattern shown above — see streaming and tool use docs for those request shapes. Plans start at €9/month for solo use, with team pricing at /pricing, and a free trial at /signup.
Questions
Which header does Claude's API use for authentication — Bearer or API key? Anthropic's native API uses x-api-key with your raw API key value, not the Authorization: Bearer scheme. If you're calling through a proxy or wrapper API like SubToAPI, check its docs — many use the more common Bearer token pattern instead.
Why am I getting a 401 even though my API key looks correct? The most common causes are a copy-pasted key with hidden whitespace, using the wrong header name for your target API, or a key that's been revoked or belongs to the wrong environment (test vs. live).
Do I need to send a version header with every request? Anthropic's native API requires anthropic-version on every call. APIs built on top of it, like SubToAPI, may abstract this away entirely so you only manage a single auth header — check the relevant docs to confirm.