invalid_api_key Incorrect API key provided: undefined. You can find your API key at https://platform.openai.com/account/api-keys
Read the message literally: the key that was provided was the nine characters
undefined. That is not a typo, not a revoked credential and not a key from the
wrong organization — it is a JavaScript variable that had no value, converted
to a string and sent as if it were a secret. The failure happened in your
configuration loading, one layer above the HTTP call, and the API is simply
reporting what arrived.
Two things follow immediately, and both narrow the search. The request left your machine and reached OpenAI, so your base URL, your network path, your proxy and your DNS are all fine — they are ruled out by the fact that you got this message at all. And nothing is wrong with the key in your dashboard; it was never consulted.
Why the message can only have come from an interpolation
A client that receives no credential has two options: refuse to send, or send whatever it was handed. What produced this error took the second path — somewhere a value was pushed into a string context rather than checked for existence. A template literal, a concatenation, a header built by hand, or a config object whose field was read off an object that did not have it.
The exact spelling is a language fingerprint, and it is worth a second of attention:
undefined— JavaScript or TypeScript. A property or environment lookup that returned nothing.None— Python. The same bug, different runtime.- An empty string, or a message with nothing after the colon — the variable existed and was blank, which is a different fix: something set it to nothing rather than never setting it.
- A masked or partial key — then this page is not yours. A real credential was sent and refused, and the causes are the ordinary ones.
So the diagnostic value of this error is that it points at a line of code, not at an account. Find the place where the credential is read, and you will find either a lookup against a name that does not exist or a value that was never present in the environment that ran.
Is retrying useful?
No. The same absent variable will be absent on the next attempt, and will stringify identically.
The SDKs agree by omission: automatic retry covers connection errors, 408, 409,
429 and 5xx. A 401 is deliberately not in that set, so nothing was retried before
you saw this and no max_retries setting changes anything.
There is a cost to looping on it beyond the wasted seconds. The docs state plainly that failed requests still count against your per-minute limit, so a retry decorator, a job runner or an agent re-invoking a failed tool can manufacture a throttling problem on top of the configuration problem you are already debugging. Turn the loop off for this class first.
The one re-run worth doing is a single attempt with the resolved value’s properties logged — see below — because the answer arrives in that log rather than in the response.
Restarting is not the same as rebuilding
This is where the afternoons go. Environment variables reach code in two completely different ways, and the repair is different for each.
At runtime, a server process reads the variable when it executes. Setting the variable and restarting the process fixes it, and a process started before you edited your shell profile will keep using the old environment until it is restarted — which is why “it works in my terminal but not in the running app” is so common.
At build time, a bundler substitutes the variable’s value into the output
as a literal. If the variable was absent when the bundle was produced, the
compiled artifact contains the literal undefined — permanently. Setting the
variable in a deployment dashboard afterwards and restarting changes nothing,
because there is no lookup left in the code to change its mind. Only a rebuild
with the variable present in the build environment fixes that one.
The tell is where it fails. If the same code works in local development and fails only in a preview or production deployment, suspect build-time substitution before you suspect anything else. Bundlers also restrict which variables they are willing to expose, usually by requiring a designated name prefix; a variable that does not carry that prefix is silently omitted rather than reported, which produces exactly this error with no warning anywhere in the build log. The rule is per-bundler, so check your build tool’s documentation for the current requirement rather than copying a prefix from a blog post.
The fix that must not be the fix
If your failing request originates in a browser, do not resolve this by making the key available to the bundle. A key inlined into client-side JavaScript is shipped to every visitor, readable in the network tab and in the source, and is compromised from the first page view. The correct repair is to move the call to a server you control and have the browser talk to that.
You can tell which situation you are in by where the request appears. If the call to the API shows up in the browser’s own network panel, the key would be in the browser too. Any key that has already shipped in a bundle should be rotated in the vendor console before anything else, because it has been public for as long as that build has been live.
More generally: never paste a key into a chat window, an issue tracker, a web form, or a third-party “key checker” to find out whether it is valid. You do not need its contents to debug this — you need to know whether a value exists at all, which is the one question this error has already answered for you.
Fix by scenario
- Server-side, runtime lookup — set the variable in the environment the process actually reads, then restart that process. Verify from inside the process, not from your shell.
- Bundled or compiled artifact — set the variable in the build environment and rebuild. Redeploying the same artifact will reproduce the error exactly.
- Wrong name — compare the name in the code against the name in the
environment character by character, including case. A hyphen-versus-underscore
or a plural is invisible at a glance and produces precisely
undefined. - Loaded from a file — check the working directory the process starts in. A
.envat the repository root does nothing for a process launched from a subdirectory, and a monorepo makes this the default outcome rather than the exception. - Container or CI — confirm the secret is injected into the step that runs, not only defined in the project settings. A secret that exists but is not mapped into the job is indistinguishable from one that was never created.
- Everywhere — stop interpolating. Read the value once at startup, assert it
is a non-empty string, and fail with a message naming the variable. A process
that refuses to start is cheaper than a request that reaches a vendor with the
word
undefinedin its authorization header.
How to confirm it’s fixed
Check properties, never contents. Three booleans and a length are enough and expose nothing:
const key = process.env.OPENAI_API_KEY;
console.log(key === undefined, (key ?? "").length, key === key?.trim());
That catches the three real causes at once: the variable that is not set, the value with a trailing newline picked up from a file or a heredoc, and the value whose length is obviously wrong because something truncated it.
Then use a two-stage confirmation, because the first stage is the one this page
is about. Stage one: the message stops saying undefined. Even if
authentication still fails, a different error text proves the loading bug is
gone. Stage two: a request succeeds from a freshly started process — or, for
a bundled app, from a freshly built and deployed artifact, not a restarted one.
Confirming only stage two on your laptop leaves the build-time case completely
untested.
Related errors
- openai.error.AuthenticationError: <empty message> is the opposite evidence: a 401 with no message body, which says something other than OpenAI answered. Here the message is intact and names the value you sent.
- Invalid API key · Please run /login is the case where a real credential resolved and won a precedence fight it should have lost. That page is about which key was sent; this one is about a key that was never a key.
- Missing API key · Run /login is the third point on that triangle: nothing resolved and the request was never sent, so no vendor ever saw it.
- HTTP 400 Bad Request: invalid_redirect_uri is worth a look if you reached for a hand-wired key because a sign-in flow would not complete.
- The AI coding error triage tool sorts auth failures by what actually left your machine: nothing, a placeholder, or a credential.