TL;DR — A 401 invalid_api_key almost never means you typed the key wrong. In practice it is: a stale env var overriding the key you think you're using, whitespace or a newline captured with the key, a revoked or deleted key, or — after a base-URL change — one provider's key being sent to another provider's endpoint. Print the first and last few characters of the key your process actually loaded; that single check identifies the cause most of the time.
The error
HTTP 401
{
"error": {
"message": "Incorrect API key provided: sk-proj-**********************. You can find your API key at https://platform.openai.com/account/api-keys.",
"type": "invalid_request_error",
"code": "invalid_api_key"
}
}
Note that the message echoes a masked version of the key it received. That echo is the most useful debugging signal in the whole response — if the visible prefix isn't the key you expect, you already have your answer.
Check this first: which key actually loaded?
# Do NOT print the whole key. Prefix + length is enough to identify it.
echo "prefix=${OPENAI_API_KEY:0:12} length=${#OPENAI_API_KEY}"
Compare against the key you intended. Two things this catches immediately:
- Wrong key entirely → an env var from a shell profile,
.env, or CI secret is winning over the one you edited. - Unexpected length → whitespace or a newline came along for the ride (next section).
The five real causes
1. A stale environment variable is overriding your config. The classic: you update a .env or a settings file, but an OPENAI_API_KEY exported in ~/.zshrc (or inherited from a parent process, or set in the CI environment) takes precedence. This is the same shape as the Claude Code double-env-var problem in the custom base-URL 401. Fix: unset OPENAI_API_KEY, confirm it's gone, then run again.
2. Whitespace or a newline in the key. Copying from a terminal, a wrapped web page, or a file read with a naive read/cat can append \n or a trailing space. The key looks right and fails anyway. Fix: strip it — OPENAI_API_KEY=$(tr -d '[:space:]' < keyfile) — and check the length matches.
3. The key was revoked, deleted, or rotated. Keys disappear when someone rotates credentials, when a leaked key is auto-revoked after being pushed to a public repo, or when a project is deleted. The API cannot distinguish "never existed" from "revoked" in the message. Fix: create a fresh key and update every place the old one lives.
4. Wrong provider's key at an OpenAI-compatible endpoint. After pointing a client at a different base URL, the key must be that provider's key. Sending an OpenAI sk-... key to a Gemini, DeepSeek, or self-hosted endpoint — or the reverse — yields an auth failure. If the 401 started right after a base-URL change, this is almost certainly it. The Gemini-specific version (AI Studio key vs Vertex service account) is in the Google AI Studio base-URL guide.
5. Project-scoped key, wrong project. Project keys (sk-proj-...) are bound to one project. Using one against resources in another project fails auth even though the key itself is valid.
Related but different: 401 vs the other failures
| Symptom | What it actually is |
|---|---|
| invalid_api_key (401) | Auth — the key is wrong, stale, revoked, or for another provider |
| insufficient_quota (429) | Billing, not auth — the key is fine, the balance is not |
| model_not_found (404) | Access/scope — key is valid, model is not visible to it |
Distinguishing these three saves most of the time people lose to "my API key doesn't work."
Prevention
- One source of truth per environment. Keep keys in one place and avoid exporting them globally in shell profiles, which is what creates the stale-override problem.
- Never commit keys. A key pushed to a public repo is typically auto-revoked, and the 401 that follows looks mysterious if you don't know it happened.
- Assert at startup, not at request 500: make one cheap authenticated call on boot so bad credentials fail immediately and visibly.
- Do not retry a 401. It is terminal until the credential changes; a backoff loop just delays the real fix.
Part of the LLM API Error Reference — errors indexed by their exact strings.