- Regenerate the key in the API keys dashboard and paste the new one — revoked, deleted, or mistyped keys are the #1 cause.
- Confirm the env var is actually loaded — a missing
OPENAI_API_KEYsendsBearer None/Bearer undefined, which 401s. - Match the key to the right org/project — a key scoped to one project 401s against another. Strip stray whitespace and quotes.
HTTP 401 Unauthorized means OpenAI could not authenticate your request. Unlike a 429, retrying won’t help — the credential itself is wrong, missing, malformed, or not authorized for what you’re calling. The fix is almost always a five-minute configuration change once you know which of the handful of causes you’ve hit.
What this error means
A 401 is returned before your request is processed. OpenAI never even looks at your prompt — it rejects you at the door. The body usually carries code: "invalid_api_key":
{
"error": {
"message": "Incorrect API key provided: sk-abc***. You can find your API key at https://platform.openai.com/account/api-keys.",
"type": "invalid_request_error",
"code": "invalid_api_key"
}
} Note what this is not: it’s not a rate limit (429), not a permissions/region problem (403), and not a server fault (5xx). A 401 is purely “I don’t believe you are who your key says you are.” See the table below for how to tell them apart fast.
Common causes
Identify which one applies before you change anything — the fix differs.
- Revoked, rotated, or deleted key. The key worked yesterday, then someone rotated it (or it was auto-revoked after being committed to a public repo — OpenAI scans GitHub and disables leaked keys). Generate a fresh one.
- Env var not loaded. Your code reads
os.environ["OPENAI_API_KEY"]orprocess.env.OPENAI_API_KEY, but the.envfile wasn’t loaded, the variable name is misspelled, or your deploy platform didn’t inject it. The SDK then sends an empty bearer token. - Whitespace, quotes, or a truncated key. A trailing newline from
echo "key" > .env, surrounding quotes pasted into a secrets UI, or a key cut short on copy all corrupt the header silently. - Wrong organization or project header. A project-scoped key (
sk-proj-…) used with a mismatchedOpenAI-Project/OpenAI-Organizationvalue fails auth. This bites teams whose members belong to several orgs. - Using the wrong key type or account. A session token, a frontend “publishable” placeholder, or a key from a different account/tenant won’t authenticate against the API.
- Pointing at the wrong endpoint. Using a standard OpenAI key against an Azure OpenAI endpoint (or vice versa) 401s — Azure uses a different auth scheme (
api-keyheader) and base URL.
How to fix it
Work from “is the key valid at all?” outward to “is it scoped correctly?”
Step 1 — Prove the key works in isolation
curl https://api.openai.com/v1/models \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-o /dev/null -w "HTTP %{http_code}\n" -s
# 200 => the key is valid; your app is mangling it or sending the wrong one.
# 401 => the key itself is the problem; regenerate it. If the bare key 401s here, the problem is the credential — regenerate it in the dashboard. If it returns 200, the key is fine and your application is corrupting it or sending a different value.
Step 2 — Fail fast in your app
Validate the key at startup so a misconfiguration crashes at boot with a clear message instead of mid-request in production.
import os
from openai import OpenAI, AuthenticationError
key = os.environ.get("OPENAI_API_KEY")
if not key:
raise RuntimeError("OPENAI_API_KEY is not set in this environment.")
if key != key.strip():
print("Warning: key had surrounding whitespace; stripping it.")
client = OpenAI(api_key=key.strip())
try:
client.models.list() # cheap authenticated call
print("Auth OK")
except AuthenticationError as e:
raise SystemExit(f"OpenAI auth failed — check the key/org/project: {e}") import OpenAI from "openai";
if (!process.env.OPENAI_API_KEY) {
throw new Error("OPENAI_API_KEY missing — is your .env loaded in this environment?");
}
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY.trim(),
// Set these ONLY if your key is scoped to a specific org/project.
// A mismatched value here causes a 401 even with a valid key.
organization: process.env.OPENAI_ORG_ID,
project: process.env.OPENAI_PROJECT_ID,
});
try {
await client.models.list();
console.log("Auth OK");
} catch (err) {
console.error("OpenAI auth failed:", err?.status, err?.code, err?.message);
process.exit(1);
} Step 3 — Azure OpenAI uses different auth
If you’re on Azure, the standard SDK config won’t work — Azure authenticates with an api-key header against your resource endpoint and a deployment name:
from openai import AzureOpenAI
client = AzureOpenAI(
api_key=os.environ["AZURE_OPENAI_API_KEY"], # NOT your platform.openai.com key
api_version="2024-10-21",
azure_endpoint="https://YOUR-RESOURCE.openai.azure.com",
)
# Use your *deployment* name as the model:
resp = client.chat.completions.create(model="my-gpt-4o-deployment",
messages=[{"role": "user", "content": "Hi"}]) How 401 relates to 403 and 429
These three get confused constantly. The code field and status tell you which lane you’re in:
| Status | Meaning | Retry? | Fix |
|---|---|---|---|
| 401 | Key invalid / missing / unauthenticated | No | Fix or regenerate the key; check org/project |
| 403 | Authenticated but not allowed (region, model, permission) | No | Enable access / use a supported region or model |
| 429 | Authenticated, but rate-limited or out of quota | Yes (rate) / No (quota) | Back off, or add credits — see the 429 guide |
If you authenticated successfully but still can’t call a specific model, that’s a 403, not a 401 — different fix.
How to prevent it
- Store keys in a secret manager, never in source control. Rotate on a schedule and after any suspected leak.
- Validate on startup (Step 2) so misconfig fails loudly at deploy time, not during a user request.
- Use project-scoped keys per environment (dev/staging/prod) so a leaked dev key can be revoked without taking prod down.
- Avoid
echofor.envfiles — it appends a newline. Use your secret store’s UI orprintfwithout a trailing\\n.
Related providers
Auth failures look similar across providers but use different headers:
- Anthropic uses an
x-api-keyheader (plusanthropic-version), notAuthorization: Bearer— a401there usually means a missing or wrongx-api-key. See Anthropic Claude rate limits for the header layout, and the OpenAI → Anthropic migration guide for the full auth remap. - Google Gemini authenticates via an API key query param or OAuth; a
401/403there typically means the key isn’t enabled for the Generative Language API.
What to do next
- Run the cURL check (Step 1) to isolate key vs. app.
- If the key 401s, regenerate it; if it passes, fix env loading / whitespace / org-project scoping in your app.
- Add startup validation so this never reaches production silently again.
- Auth fixed but requests still failing? The next most common error is rate limiting — see the OpenAI 429 fix and the OpenAI rate limits reference.
Frequently asked questions
Why does my key work locally but 401 in production?
OPENAI_API_KEY (correct name, correct environment) and that nothing strips, quotes, or truncates it. A startup validation check surfaces this at deploy time.Can a 401 be a rate-limit issue?
429. A 401 is always an authentication problem — the credential is wrong, missing, or malformed. If you authenticated but lack access to a model or region, that's a 403.Do I need the organization and project headers?
sk-proj-…), or your account belongs to multiple organizations. A mismatched header on a scoped key causes a 401 even though the key is valid.OpenAI disabled my key after I pushed it to GitHub — what now?
I'm on Azure OpenAI and getting 401s with my normal key — why?
api-key header against your resource endpoint, using a deployment name). Your platform.openai.com key won't authenticate there; use the Azure resource key and endpoint.How do I verify the key without spending tokens?
client.models.list() or GET /v1/models). It's authenticated but doesn't run a completion, so it confirms the credential without incurring generation cost.