Hi! You’ve got a Python environment that might be failing you, or want to see if an API key is really working?
First see if you can “chat” on the OpenAI Playground. Use the highly-available model gpt-4.1-mini.
https://platform.openai.com/chat/edit?models=gpt-4.1
This uses a session key, not your generated API key. An API call uses the selected organization and project at upper-left of the platform site interface.
Working there?
Then its time to investigate.
Here’s what ChatGPT says about the script I pieced together, so you have a bit of assurance what its up to:
This script is a small OpenAI environment/credentials diagnostic tool to help you debug “invalid API key” and related auth problems.
At a high level, when you run it:
-
It checks that httpx and openai are installed and errors out clearly if they aren’t.
-
It reads OPENAI_API_KEY, and optionally OPENAI_ORG_ID / OPENAI_PROJECT_ID, from your environment.
-
The “mini” helper prints a masked version of your API key (first/last characters only) plus the org/project IDs, and returns the exact headers it would send to the OpenAI API.
-
The full helper can also load a .env file via python-dotenv (if installed), optionally overriding your existing environment variables, then does the same masking/printing and returns headers.
-
It constructs an openai.Client() that uses whatever credentials are currently in your environment and prints out which key/org/project that client ended up using.
-
Finally, it calls client.models.list() and:
- On success, prints a filtered list of model IDs (showing that the key is valid and has working access).
- On failure (e.g., invalid key, wrong org/project, revoked key), it prints the error body from the API so you can see the exact server-side reason.
Safety considerations:
- The script prints sensitive information to stdout, including the full API key used by the client (
client.api_key). Only run it in a trusted environment where console logs are not being shared or stored insecurely.
- You can try, but don’t keep real API keys hard-coded in this file (even commented out). Any keys that have ever been committed or shared should be considered compromised and rotated.
- Prefer using environment variables or a local, uncommitted
.env file to hold secrets.
In short, this tool shows you exactly which credentials your code is using and exercises a real API call so you can pinpoint why OpenAI is returning “invalid API key” or related auth errors.
'''OpenAI Python environment variable diagnosis util'''
try:
import httpx # required for demo, RESTful calls
except Exception as e:
raise ImportError("Missing dependency: install with `pip install httpx`") from e
try:
import openai # required for demo, RESTful calls
except Exception as e:
raise ImportError("Missing dependency: install with `pip install openai`") from e
def get_api_key_headers_mini(printing=True):
import os
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
raise ValueError("ERROR: Set the OPENAI_API_KEY environment variable.")
org_id = os.environ.get("OPENAI_ORG_ID")
project_id = os.environ.get("OPENAI_PROJECT_ID")
if printing:
print(f"Using OPENAI_API_KEY {api_key[:10]}...{api_key[-4:]}")
print(f"Using optional OPENAI_ORG_ID {org_id}")
print(f"Using optional OPENAI_PROJECT_ID {project_id}")
return {
"Authorization": f"Bearer {api_key}",
**({"OpenAI-Organization": org_id} if org_id else {}),
**({"OpenAI-Project": project_id} if project_id else {})
}
def get_api_key_headers(
api_key: str | None = None,
printing: bool = True,
dotenv_override: bool = True,
env_path: str | None = None,
) -> dict[str, str]:
"""
Returns OpenAI API auth headers.
Behavior:
- A passed `api_key` bypasses all env variable methods
- Attempts to load variables from a .env file (if python-dotenv is available).
- By default, .env VALUES OVERRIDE existing os.environ entries (dotenv_override=True).
- You can point to a specific file via env_path; otherwise we use find_dotenv(usecwd=True).
- Each .env path is loaded at most once per process (cached).
Notes:
- This updates process environment variables; in multi-account, single-process scenarios,
prefer passing explicit headers/keys per call rather than relying on global env.
- Returns dict, thus not duplicate header keys.
Usage: To avoid cross-call bleed, pass a *copy* before mutation is possible, e.g.:
api_call(url, headers = {**get_api_key_headers(), "OpenAI-Beta": "responses=v99"}, ...)
"""
if api_key:
return {"Authorization": f"Bearer {api_key}"}
# One-time cache per path
if not hasattr(get_api_key_headers, "_dotenv_loaded_paths"):
get_api_key_headers._dotenv_loaded_paths = set()
# Best-effort .env loading (optional dependency)
try:
if env_path is None:
from dotenv import load_dotenv, find_dotenv # lazy import
dotenv_path = find_dotenv(usecwd=True)
else:
from dotenv import load_dotenv # lazy import
dotenv_path = env_path
if dotenv_path and dotenv_path not in get_api_key_headers._dotenv_loaded_paths:
load_dotenv(dotenv_path=dotenv_path, override=dotenv_override)
get_api_key_headers._dotenv_loaded_paths.add(dotenv_path)
except Exception:
# Missing python-dotenv or any load failure is a no-op.
pass
import os
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
raise ValueError(
"ERROR: Set the OPENAI_API_KEY environment variable (or in your .env)."
)
org_id = os.environ.get("OPENAI_ORG_ID")
project_id = os.environ.get("OPENAI_PROJECT_ID")
if printing:
print(f"Using OPENAI_API_KEY {api_key[:10]}...{api_key[-4:]}")
print(f"Using optional OPENAI_ORG_ID {org_id}")
print(f"Using optional OPENAI_PROJECT_ID {project_id}")
headers: dict[str, str] = {"Authorization": f"Bearer {api_key}"}
if org_id:
headers["OpenAI-Organization"] = org_id
if project_id:
headers["OpenAI-Project"] = project_id
return headers
# END: get_api_key_headers() ----------------------
print("*"*30 + "\n-- get_api_key_headers_mini() says:")
get_api_key_headers_mini()
print("*"*30 + "\n-- get_api_key_headers() (full, with dotenv) says:")
get_api_key_headers() # can pass `api_key` into it also
from openai import Client
client=Client() # automatically imports from env variables
print("*"*30 + f"\n-- OpenAI client api_key before run scraped:\n{client.api_key}")
# make a "free" call - to the models endpoint, with some rules for chat models only
starts_with = ["gpt-", "o", "ft:gpt", "co"]
blacklist = ["instruct", "moderation"]
model_response = None
try:
print("*** Calling OpenAI 'models' API with OpenAI SDK module")
model_response = client.models.list() # Makes the API call
model_obj_list = model_response.model_dump().get('data', [])
model_list = sorted([model['id'] for model in model_obj_list])
filtered_models = [
model for model in model_list
if any(model.startswith(prefix) for prefix in starts_with)
and not any(bl_item in model for bl_item in blacklist)
]
printable_string:str = ", ".join(filtered_models)
print(f"Success! Got a list of models like {filtered_models[0:2]}\n")
except Exception as err:
errormsg=err
print("FAIL!\n".join(map(str, errormsg.body.values())))
finally:
print(f"Client used OPENAI_API_KEY {client.api_key}")
print(f"Client Used optional OPENAI_ORG_ID {client.organization}")
print(f"Client Used optional OPENAI_PROJECT_ID {client.project}")
print(f"OpenAI version {openai.__version__} used. (at least '1.101.0' is a good idea)")
One of the first two functions would be useful if you are writing your own API requests to make the headers for those OpenAI API calls. Here, they just print.
Then for the actual call, we try the OpenAI models endpoint with the SDK module’s native retrieval of environment variables, for a final report.
Forgotten files or system variables can often occur, or mismatch between an old project_id and the employed api_key.