Fundamentals

Fix the 401 Unauthorized Error in OpenAI Python

Fix the OpenAI 401 Unauthorized error in Python: diagnose a missing, mistyped, wrong, or unloaded API key with runnable checks and a minimal working call.

This guide shows you how to fix the OpenAI 401 Unauthorized error in Python in under ten minutes. A 401 always means one thing: the server could not confirm who you are. Your request arrived, but the API key it carried was missing, mistyped, wrong, expired, or aimed at the wrong account. The good news is that the model never ran, so a 401 costs you nothing and is safe to retry once fixed.

We will look at the exact message you see, then walk through numbered fixes. Each fix has runnable Python 3.10+ you can paste and run. If you are brand new to keys and requests, read Understanding LLM APIs first, since this page assumes you already have a key and the openai SDK installed.

The exact error you see

When the key fails, the openai SDK raises an AuthenticationError and prints something close to this:

openai.AuthenticationError: Error code: 401 - {'error': {'message': 'Incorrect
API key provided: sk-abcd****. You can find your API key at
https://platform.openai.com/account/api-keys.', 'type': 'invalid_request_error',
'param': None, 'code': 'invalid_api_key'}}

The key signal is the number 401 and the class name AuthenticationError. If you instead see 429, that is a rate or quota problem covered in Fix the 429 Rate-Limit Error in Python, not an auth problem. Confirm you really have a 401 before applying these fixes.

If the traceback carries no HTTP status code at all — an import that failed, a socket that never opened — the trouble sits earlier than authentication, and Read a Python Traceback in Five Minutes shows you how to find the line that really broke. Sorting the status code first saves you from spending an hour on the wrong repair. The triage below shows the three doors a failed call can go through, and which one this page sits behind.

Triaging a failed OpenAI call by its HTTP status code A decision tree that starts from a failed API call and branches on the status code: 401 leads to the identity fixes on this page, 429 leads to the rate-limit guide, and any other code leads to reading the traceback. The call failed check the status code 401 Unauthorized identity problem 429 Too Many rate or quota cap Any other code or no code at all Fixes 1 to 5 here key, org, base_url Rate-limit guide back off and retry Traceback guide find the real line
Read the status code before you touch anything: only the left-hand branch is an authentication problem, and only that branch is what the fixes on this page repair.

Prerequisites

You only need what differs from the parent section: the openai SDK, python-dotenv to load your key, and a recent Python. Pin versions so your results match this guide.

python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install "openai>=1.40" "python-dotenv>=1.0"

If import openai itself fails after that install, the package landed in a different interpreter and you never reached the authentication stage at all; Fix ModuleNotFoundError: No Module Named openai covers that case, which looks alarming but is not a 401.

Create a .env file next to your script with your key on one line, no quotes and no spaces:

OPENAI_API_KEY=sk-your-real-key-goes-here

Add .env to your .gitignore so the key never lands in version control. A leaked key can be used by strangers and billed to you.

echo ".env" >> .gitignore

Fix 1 — Confirm the key is actually loaded

The most common cause of a 401 is that your program never read the key at all. A .env file does nothing by itself; you must load it. This check prints the key's length and a masked prefix so you can see whether it arrived without ever exposing the full secret.

import os
from dotenv import load_dotenv

load_dotenv()  # reads .env from the current working directory

key = os.environ.get("OPENAI_API_KEY")
if not key:
    print("No key found. .env not loaded, or the variable name is wrong.")
else:
    print(f"Key loaded: length {len(key)}, starts with {key[:6]}...")

A healthy key prints a length in the dozens and starts with sk-. If you see "No key found", the .env file is in a different folder than where you run the script, or the variable is misnamed. Run the script from the folder that holds .env, or pass an explicit path: load_dotenv("/full/path/to/.env").

It helps to picture the key as a parcel that changes hands five times between your project folder and OpenAI's servers. Every box below hands the key to the next one, and a 401 means it was dropped somewhere along that chain — most often at the second hop, where load_dotenv() ran too late or read the wrong folder.

How the API key travels from the .env file to OpenAI A data-flow diagram tracing the API key through six stages: the .env file, the load_dotenv call, the process environment, the OpenAI client object, the Authorization header on the request, and finally OpenAI's check that returns 200 OK or 401. .env file sits beside the script load_dotenv() copies keys to env os.environ OPENAI_API_KEY OpenAI(api_key=) the client is built Authorization header sent to API OpenAI checks it 200 OK or 401
The key has to survive all five handovers; the masked print in Fix 1 tells you whether it made it as far as the third box, which is where most broken setups stop.

Fix 2 — Catch the error and read its details

Wrap your call so the program tells you precisely what went wrong instead of crashing. The openai SDK gives the status code and an exact message you can act on.

import os
from dotenv import load_dotenv
from openai import OpenAI, AuthenticationError

load_dotenv()
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

try:
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "ping"}],
    )
    print(resp.choices[0].message.content)
except AuthenticationError as err:
    print(f"401 auth failed (status {err.status_code}): {err.message}")

Read the printed message. Phrases like "Incorrect API key provided" point to a wrong or mistyped key (Fix 3). Phrases mentioning an organization or project point to an account mismatch (Fix 4).

Fix 3 — Check the key is correct, current, and clean

A 401 with "Incorrect API key" means the string is wrong. Three things cause this: a typo, a key that was revoked or regenerated, or hidden characters copied along with the key. This check catches the hidden-character case, which is easy to miss.

import os
from dotenv import load_dotenv

load_dotenv()
key = os.environ.get("OPENAI_API_KEY", "")

problems = []
if not key.startswith("sk-"):
    problems.append("key does not start with 'sk-'")
if key != key.strip():
    problems.append("key has leading or trailing whitespace")
if '"' in key or "'" in key:
    problems.append("key contains quote characters from the .env file")

print("Problems:" if problems else "Key format looks clean.")
for p in problems:
    print(" -", p)

If the format looks clean but the call still fails, the key itself is likely wrong or revoked. Open the API keys page in your OpenAI account, create a fresh key, and paste it into .env with no surrounding quotes. Never wrap the value in quotes in a .env file; python-dotenv keeps them as part of the string.

Fix 4 — Match the organization, project, and provider

A correctly typed, current key can still return 401 if it points at the wrong place. This happens when your account has more than one organization or project, or when base_url is aimed at a different provider whose servers reject an OpenAI key.

import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

client = OpenAI(
    api_key=os.environ["OPENAI_API_KEY"],
    organization=os.environ.get("OPENAI_ORG_ID"),    # optional, set only if needed
    project=os.environ.get("OPENAI_PROJECT_ID"),     # optional, set only if needed
    base_url="https://api.openai.com/v1",            # the real OpenAI endpoint
)
print("Talking to:", client.base_url)

Confirm base_url reads https://api.openai.com/v1. If you earlier set it to a free or third-party gateway, an OpenAI key will be rejected there. Mixing providers is a frequent trap when following tutorials that compare services such as Groq vs OpenRouter Free Tier; each provider needs its own matching key. Likewise, only set organization and project if your key belongs to that exact org and project. A mismatched org or project ID is just as fatal as a bad key.

Four combinations account for nearly every mismatch of this kind. Only the first one authenticates; the other three come back as the identical 401, which is precisely why the error text alone rarely tells you which situation you are in.

Four key and endpoint combinations and the status each returns A comparison matrix with four rows. Each row names a situation, what the receiving server actually sees, and the resulting status: a clean key on the OpenAI endpoint returns 200 OK, while a gateway base_url, a mismatched organization, and a quoted key value all return 401. Situation Server sees Result Clean current key sk- prefix, no quotes api.openai.com matching account 200 OK the model runs Key to a gateway base_url changed third-party host does not know key 401 wrong provider Key from other org org or project set OpenAI, wrong org header mismatch 401 wrong account Key with quotes copied from .env quoted sk- value no key matches it 401 incorrect API key
Three different mistakes produce one identical status, so work the rows top to bottom rather than trusting the error text to name your cause.

Fix 5 — Run a minimal working call

Once the checks pass, prove the fix with the smallest possible request. A clean reply here means your authentication is fully working.

import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Reply with the single word: working"}],
)
print(resp.choices[0].message.content)

If this prints working, the 401 is solved. Build the rest of your program on top of this proven client.

Cause and fix quick reference

CauseFix
.env never loadedCall load_dotenv() before reading the key; run from the folder holding .env
Wrong variable nameUse OPENAI_API_KEY exactly, in both .env and os.environ
Key mistyped or has quotes/spacesRe-paste cleanly with no quotes; check key.strip() and the sk- prefix
Key revoked or expiredGenerate a new key in your OpenAI account and update .env
Wrong org or projectSet organization and project to match the key, or remove them
base_url points at another providerReset base_url to https://api.openai.com/v1 and use that provider's own key

Troubleshooting

  1. KeyError: 'OPENAI_API_KEY' — The variable is not in the environment. Cause: load_dotenv() ran from the wrong folder, or the .env line is misspelled. Fix: run the script from the folder containing .env, or load an absolute path with load_dotenv("/full/path/.env").
  2. 401 only when running from cron or a server — The shell that loads .env differs from the one your job uses. Cause: a hardcoded environment variable on the machine overrides .env. Fix: add load_dotenv(override=True) so the file wins, or unset the stale variable with unset OPENAI_API_KEY. On a real server, stop shipping .env files altogether — Manage API Keys Safely in Production covers the sturdier patterns.
  3. Key works in the browser playground but not in code — You copied a session token, not an API key, or the wrong account's key. Fix: copy the key from the API keys page (it starts with sk-) and confirm you are signed into the right account.
  4. 401 right after it worked yesterday — The key was rotated or revoked, or a free trial expired. Fix: generate a new key and confirm your account has active billing or remaining quota.

Still failing? Final checklist

Work down this list and the 401 almost always falls:

  • The key prints a non-zero length and a sk- prefix at runtime.
  • .env has no quotes, no trailing spaces, and uses the name OPENAI_API_KEY.
  • You ran the script from the folder that holds .env, or loaded an absolute path.
  • The key is current: not revoked, not from an expired trial.
  • base_url is https://api.openai.com/v1, not a third-party gateway.
  • Any organization or project value matches the key, or is left unset.
  • Your account has billing set up or free quota remaining.

If you work down all seven and the call still refuses you, the failure is probably not authentication after all. The Debugging Python AI Errors section walks through the other failures a first AI script hits — missing packages, certificate errors, and connections that never open — each of which can surface at the same moment in your script as a genuine 401.

When to use this vs. alternatives

Back to Understanding LLM APIs.

Frequently asked questions

What does a 401 Unauthorized error from OpenAI mean?

It means the OpenAI server could not verify who you are. Your request reached the server, but the API key it carried was missing, wrong, expired, or revoked. The model never ran, so you are not charged for a 401.

Why do I get 401 even though my key is in a .env file?

Usually the key was never loaded into the program. The .env file does nothing on its own. You must call load_dotenv() before you read the key, and the .env file must sit in the folder where you run the script.

Can a 401 happen with a valid key?

Yes. A valid key still fails if it belongs to a different organization or project than the one your request targets, or if your base_url points at a different provider that does not recognize the key. Match the key to the right org, project, and provider.

How do I know if my key is mistyped or actually wrong?

Print the key's length and first few characters at runtime. An OpenAI key starts with sk- and is long. If it is empty, short, or shows quotes or spaces, it was loaded incorrectly rather than being a bad key.

Is it safe to print my API key while debugging?

Never print the whole key. Print only its length and a masked prefix such as the first six characters. A full key in your terminal history or logs is a leaked secret that strangers can bill to you.