The API key header: Authorization Bearer or x-api-key and where each goes
OpenAI clients send the key in Authorization, Anthropic clients in x-api-key. What the gateway accepts and how to tell from the response where the key got lost.
5 min · · errors · API keys
The first 401 error when connecting a new client is often about how the key is passed, not the key itself. Different APIs have different conventions: in one the key goes in the Authorization header, in another in x-api-key. Below we cover which OpenAI API key header is needed, what x-api-key is in Claude and which of them the gateway accepts. At the end there is a diagnostic order if the request still gets 401.
Two conventions
OpenAI: Authorization Bearer
The official OpenAI API and everything that imitates its format expects the key in the standard HTTP header:
Authorization: Bearer <key>
The word Bearer, a space, then the key itself. The OpenAI SDK does this on its own: you pass api_key and it fills in the header. OpenAI has no separate x-api-key header, so queries like "openai x api key" usually arise when someone mixes up the two formats.
Anthropic: x-api-key and anthropic-version
The Anthropic API uses its own header:
x-api-key: <key>
anthropic-version: 2023-06-01
The second header sets the API version. The official Anthropic SDK sends both on its own. So "anthropic x api key" is the normal way to pass the key in the Anthropic format, not something extra.
HTTP header names are case-insensitive, so X-API-Key, X-Api-Key and x-api-key are the same header.
What the Zukko gateway accepts
On Zukko one key of the form fetch_... works in all formats, and both variants are accepted:
Authorization: Bearer fetch_YOUR_KEY
x-api-key: fetch_YOUR_KEY
According to the authentication documentation, Authorization: Bearer is used by the OpenAI SDK and most IDEs, while x-api-key is sent by the Anthropic SDK and other Anthropic clients (including Roo Code, although that extension was discontinued on May 15, 2026). You do not need to change their settings. The request format and the key header are unrelated: any model can be called in either format, and the key is accepted either way.
What is not supported: passing the key in the query string (?api_key=...); such a request gets 401. Other header names (for example api-key) are not described in the documentation, so do not count on them.
Examples for both formats
OpenAI format (/v1/chat/completions), Bearer header:
curl https://api.zukko.pro/v1/chat/completions \
-H "Authorization: Bearer $ZUKKO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-6.1-sol","messages":[{"role":"user","content":"Hello"}]}'Anthropic format (/v1/messages), x-api-key header:
curl https://api.zukko.pro/v1/messages \
-H "x-api-key: $ZUKKO_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{"model":"claude-sonnet-5-5","max_tokens":256,"messages":[{"role":"user","content":"Hello"}]}'For the Anthropic format, send anthropic-version: 2023-06-01: SDKs set it themselves, and in curl add it by hand, as in the example above and in Anthropic's official examples.
Now the same through SDKs. In Python each SDK has its own base URL: the OpenAI SDK takes it with /v1 and sends the key in Authorization: Bearer; the Anthropic SDK takes it without /v1 (it adds it itself) and sends the key in x-api-key.
import os
from openai import OpenAI
import anthropic
oa = OpenAI(base_url="https://api.zukko.pro/v1", api_key=os.environ["ZUKKO_API_KEY"])
print(oa.chat.completions.create(
model="gpt-6.1-sol",
messages=[{"role": "user", "content": "Hello"}],
max_tokens=50,
).choices[0].message.content)
an = anthropic.Anthropic(base_url="https://api.zukko.pro", api_key=os.environ["ZUKKO_API_KEY"])
print(an.messages.create(
model="claude-sonnet-5-5",
max_tokens=50,
messages=[{"role": "user", "content": "Hello"}],
).content[0].text)Note the addresses: clients that append /v1 themselves get the address without it, the others with it. If you mix them up, the error will be about the path (most often 404), not the header. More in Base URL with or without /v1.
What popular clients use
| Client | What it sends |
|---|---|
| OpenAI SDK, LangChain (OpenAI), Cursor, Cline in OpenAI mode | Authorization: Bearer |
| Anthropic SDK | x-api-key and anthropic-version |
| Roo Code in Anthropic mode (discontinued on May 15, 2026, but installed copies are still around) | x-api-key |
Claude Code with the ANTHROPIC_AUTH_TOKEN variable | Authorization: Bearer |
The last row matters: in Claude Code the token from ANTHROPIC_AUTH_TOKEN goes out as Bearer, and that is the variable we recommend for Zukko.
Diagnosis: the key went to the wrong place
A 401 response from the gateway looks like this:
{
"type": "error",
"error": {
"type": "authentication_error",
"message": "Invalid API key. Use Authorization: Bearer fetch_*. ..."
}
}Go through the causes from top to bottom:
- The key did not reach the header. The environment variable is empty (
echo ${#ZUKKO_API_KEY}prints the length; 0 means the variable is empty). Incurlthe quotes are needed, otherwise the shell may eat part of the string. - A space or newline at the end. A common problem when copying from a messenger. A header with such a key will not match.
- No
Bearerword or a double prefix.Authorization: fetch_...withoutBearerandAuthorization: Bearer Bearer fetch_...both give 401. - The key in the query string. A
?api_key=...string is not supported. - The key is revoked or deleted. Check the key list in the dashboard. The full key is shown once at creation, so a lost key has to be created again.
- A key from another system. An OpenAI or Anthropic key will not work: the gateway has its own
fetch_...keys.
A trick with /v1/models
By the documentation, GET /v1/models is open and the key is ignored there. So the check "the model list comes back, so the key is valid" is wrong. To test a key, call an endpoint that needs it, for example GET /v1/balance:
curl https://api.zukko.pro/v1/balance -H "Authorization: Bearer $ZUKKO_API_KEY"A 200 means the key is recognized. If you get 401 here, the problem is the key or the header itself.
Not 401 but another error
The header can be fine and the error elsewhere:
- 402: not enough balance;
- 403 with the code
email_unverified: the email is not confirmed; - 403 with the code
model_not_allowed_for_key: the model is not in this key's allowed list; - 404: wrong base URL or an unknown model name (
model_not_found); - 429: the key's daily or monthly limit is exhausted.
We covered the general error table in 401, 402, model not found.
Header security
- Never send the key over plain HTTP: use only
https://api.zukko.pro. - Do not put the key in code that runs in a visitor's browser: the header is visible in developer tools. Keep the key on the server.
- Create a separate key per application and give it a daily limit. See the key limits documentation.
If you need a key, create it in the Zukko dashboard and test it with the requests above.
FAQ
Is x-api-key needed for OpenAI-compatible requests?
No. For /v1/chat/completions the standard way is Authorization: Bearer. The gateway accepts x-api-key too, but the OpenAI SDK does not send it and you do not need to change that.
Can I send Bearer in the Anthropic format?
Yes. The gateway accepts Bearer for /v1/messages as well, which is why ANTHROPIC_AUTH_TOKEN works in Claude Code. The official Anthropic SDK uses x-api-key by default, and that works too.
Is the anthropic-version header needed?
For the Anthropic format, yes. SDKs add it themselves, and in curl specify anthropic-version: 2023-06-01 by hand.
Why does the model list respond while chat gives 401?
Because GET /v1/models does not require a key. Test the key with GET /v1/balance or a real model request.
zukko
Pay 25×
less
for Claude Code & ChatGPT
One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.
Related articles
- OpenAI API Keys in the Open: Why You Should Not Use ThemWhere free keys on GitHub come from, why using them is dangerous for you and for the owner, and how to protect your own key from the same leak.
- GLM API Errors: Why the Request Fails and How to Fix ItA wrong model id, an overflowing 128K context, the key, the balance, limits: the typical causes of GLM API errors and a quick way to find yours.
- This organization has been disabled in the Claude API: What to DoAn Anthropic key suddenly answers "organization has been disabled". What the error is, what to check, and how to move the SDK and Claude Code to another provider.
- Gemini API not working: region, key, limits and model nameStep-by-step Gemini API diagnostics: from checking status and region to a leaked key, 429 and 503 errors and a typo in the model name.