Troubleshooting · as of 2026-10-07

Codex 401 Unauthorized: Causes and Fixes

As of 2026-10-07, when the Codex CLI or IDE extension returns 401 Unauthorized, the official docs come down to three places to check: your active sign-in method (codex login status), where credentials are cached (~/.codex/auth.json or the OS credential store), and, for a custom provider, how model_provider, env_key and requires_openai_auth combine in config.toml. The fixes are to run codex login again, switch to an API key with printenv OPENAI_API_KEY | codex login --with-api-key, or put the key where the provider actually reads it. This page follows the wording of the OpenAI Codex docs and changelog and shows the QCode setup.

Updated 2026-10-08

#codex login#auth.json#env_key#401 Unauthorized

Four things to pin down first

2 ways

Official sign-in methods

Sign in with ChatGPT uses your subscription; sign in with an API key is usage-based, and the docs say API key sign-in uses standard API pricing instead of included ChatGPT plan credits. The CLI, IDE extension and desktop app support both.

auth.json

Cached credentials

Login details are cached in ~/.codex/auth.json or the OS credential store and shared by the CLI and IDE extension; log out of either one and you have to sign in again the next time you start the other.

env_key

Where a custom provider gets its key

env_key names the environment variable Codex reads the key from; per the docs, when requires_openai_auth = true, Codex ignores env_key and uses OpenAI authentication.

401 / 404

How the QCode docs read them

In the QCode docs, 401 means a key problem and 404 means a wrong path prefix; a Codex base_url with one segment too many or too few returns 404.

What 401 means and what to check first

As of 2026-10-07, a 401 Unauthorized from Codex means the server did not accept the credentials on the request, or the request carried none. The official docs list two sign-in methods: Sign in with ChatGPT (subscription access) and Sign in with an API key (usage-based access); the desktop app, Codex CLI and IDE extension support both, while Codex cloud requires ChatGPT. When no valid session is available, codex login with its browser flow is the default path, and codex login status shows the active method. With a custom provider, config.toml matters too: model_provider defaults to openai; a provider with requires_openai_auth = true uses OpenAI authentication and ignores env_key; a provider with only env_key reads the key from that variable; and with neither set, the docs say Codex assumes the provider needs no authentication.

Recent changes and user reports

The official changelog has several sign-in-related entries (see the timeline below); with 0.160.0 on 2026-10-01 the docs were clarified on how provider credentials use the configured storage backend and how env_key identifies the API-key environment variable. Separately, an issue titled "unexpected status 401 Unauthorized issue" was opened on GitHub openai/codex on 2026-09-25, where users report seeing Incorrect API key provided even in ChatGPT sign-in mode (user reports; OpenAI has not documented a cause). This page treats such issues only as a sign that many people hit the error; the steps follow the official docs.

Three sign-in entries in the official changelog

2026-09-22

Codex CLI 0.156.0: login can recover through system proxies, and the same entry refreshes MCP credentials when OAuth discovery returns a 503 error.

2026-09-29

Codex CLI 0.159.0: local ChatGPT sign-in opens the browser reliably, and onboarding adds a shortcut to copy the login link.

2026-10-01

Codex CLI 0.160.0: the docs clarify how provider credentials use the configured storage backend and how env_key identifies the API-key environment variable.

Confirmed vs not verified

Confirmed (verbatim in the official docs)

All of the following can be checked word for word in the official docs: the two sign-in methods and where each works; what codex login, printenv OPENAI_API_KEY | codex login --with-api-key, codex login --device-auth, codex login status and codex logout do; login details cached in ~/.codex/auth.json or the OS credential store (the official sample config marks file, which means auth.json, as the default for cli_auth_credentials_store) and shared by the CLI and IDE extension, which also share the same configuration layers; ChatGPT sessions refreshing tokens automatically before they expire; model_provider defaulting to openai; env_key being ignored when requires_openai_auth = true; model_provider, model_providers and openai_base_url being ignored, with a startup warning, in a project-local .codex/config.toml; --profile no longer reading [profiles.name] tables from config.toml since 0.134.0; responses being the only supported wire_api; and [model_providers.openai] not being allowed, with openai_base_url used instead.

Unverified or not documented

Two things this page could not verify, so do not plan around them: ① why some users see 401 even with ChatGPT sign-in, since the GitHub issue only has user guesses and the docs say nothing; ② this page found no official table of every Codex 401 error string, so except where a string is attributed to the docs, the error strings quoted here come from user reports.

Two pairs of settings people mix up

ChatGPT sign-in vs API key

ChatGPT sign-in uses your subscription and a browser flow (codex login); on a remote or headless machine use codex login --device-auth. API key sign-in is billed at standard API rates: in the CLI, printenv OPENAI_API_KEY | codex login --with-api-key; in the IDE extension, choose Use API Key on the signed-out screen. The docs call API keys the recommended default for automation, and CODEX_API_KEY provides a key to a non-interactive Codex process. To switch, run codex logout first to clear the current credentials.

env_key vs requires_openai_auth

These are the two authentication options for a custom provider. env_key = "NAME" makes Codex read the key from that environment variable; you choose the name, and it has to match the variable you actually export. requires_openai_auth = true makes the provider use OpenAI authentication (ChatGPT or an API key), and the docs say Codex then ignores env_key. With neither set, Codex assumes no authentication is needed. There is also experimental_bearer_token for a direct token, which the docs mark as discouraged in favor of env_key.

Work through it in this order

① Run codex login status to see whether you are on ChatGPT or an API key (it exits with 0 when logged in). ② For OpenAI's own service: run codex logout, then codex login (ChatGPT in the browser), printenv OPENAI_API_KEY | codex login --with-api-key (switch to an API key) or codex login --device-auth (remote or headless machines); the IDE extension shares the CLI's login cache, so it picks up the fix. ③ For a custom provider: put model_provider and the [model_providers.name] table in ~/.codex/config.toml, because those keys are ignored in a project-local .codex/config.toml; with --profile, put the settings in ~/.codex/name.config.toml. ④ For QCode, the docs give this config.toml: model_provider = "crs", model = "gpt-6-sol", model_reasoning_effort = "high", preferred_auth_method = "apikey", plus a [model_providers.crs] table with name = "crs", base_url = "https://api.qcode.cc/openai", wire_api = "responses", requires_openai_auth = true, env_key = "CRS_OAI_KEY" (the provider and variable names are up to you; the docs example uses crs and CRS_OAI_KEY; because requires_openai_auth = true is set, the official docs say Codex ignores env_key, so the key that counts is the one in auth.json below). In ~/.codex/auth.json write {"OPENAI_API_KEY": "cr_xxxxxxxxxx"} with your own cr_ key, and set the file permissions to 600. ⑤ Self-test: request https://api.qcode.cc/openai/v1/models with Authorization: Bearer and your key; a JSON list back means the address and key are both fine.

On QCode

On QCode, Codex works with GPT models only; Claude and the DeepSeek, GLM, Kimi and Qwen families are not served over the OpenAI Responses protocol that Codex uses. In config.toml, set base_url to https://api.qcode.cc/openai with wire_api = "responses"; the key is your QCode key starting with cr_ from the dashboard, written into OPENAI_API_KEY in ~/.codex/auth.json as the docs show. The docs also list an optional alternative: set only the environment variable named by env_key and set OPENAI_API_KEY in auth.json to null. But this config has requires_openai_auth = true, and the official docs say Codex then ignores env_key, so relying on the environment variable alone is not recommended. The docs default model is gpt-6-sol; you can switch to gpt-6.1-sol or gpt-5.6-terra. On a 401, check what the docs list: the key starts with cr_, the key in auth.json is complete with no extra spaces or line breaks, and the key status and remaining quota look right in the dashboard; a wrong key returns Invalid API key. A base_url with one segment too many or too few returns 404, not 401. Billing is per token; see /models for each model's price.

Frequently asked questions

Codex says 401 Unauthorized: what do I check first?

Run codex login status first. Per the official docs it prints the active authentication mode and exits with 0 when logged in. Once you know whether you are on ChatGPT or an API key, check whether Codex talks to OpenAI or to a custom provider: for OpenAI, sign in again or switch keys; for a custom provider, check which authentication the provider named by model_provider uses (env_key or requires_openai_auth) and whether the key is where that provider reads it from.

How do I switch between ChatGPT sign-in and an API key?

Run codex logout to clear the current credentials, then sign in the other way. Per the docs: for ChatGPT, run codex login and finish in the browser; for an API key, pipe it in with printenv OPENAI_API_KEY | codex login --with-api-key; in the IDE extension, choose Sign in with ChatGPT or Use API Key on the signed-out screen. The CLI and the extension share the same cached login. If an admin has set forced_login_method, Codex logs you out and exits when your credentials don't match.

It worked for a while and now returns 401. Has the token expired?

If you use ChatGPT sign-in, treat it as a stale session first: run codex logout, then codex login. The docs say ChatGPT sessions refresh tokens automatically during use before they expire, so active sessions usually need no new browser login. On GitHub, users report seeing Provided authentication token is expired. Please try signing in again. or Your access token could not be refreshed because your refresh token was revoked. Please log out and sign in again. once the sign-in has gone stale (both user reports). On a remote or headless machine, sign in with codex login --device-auth.

My custom provider sets env_key and I exported the key. Why is it still 401?

First check whether the same provider has requires_openai_auth = true: the docs say Codex then ignores env_key and uses OpenAI authentication (ChatGPT or an API key). If it doesn't, make sure the env_key value matches the variable name you exported exactly; the docs say Codex reads the variable named by that config, so the name itself is not fixed. On GitHub, users report seeing 401 Unauthorized: Missing bearer or basic authentication in header when no credentials are configured (user report).

I edited config.toml, but Codex still seems to use OpenAI?

Check that the provider settings sit where Codex reads them. The docs describe three traps: model_provider, model_providers and openai_base_url in a project-local .codex/config.toml are ignored with a startup warning; since Codex 0.134.0, --profile no longer reads [profiles.name] from config.toml, so use ~/.codex/name.config.toml; and the built-in IDs openai, ollama and lmstudio cannot be reused by a custom provider, so to change only the OpenAI address, use openai_base_url. If model_provider is not set, it defaults to openai.

How do I troubleshoot a 401 with QCode?

Follow the QCode docs: confirm the key starts with cr_, the key in ~/.codex/auth.json is complete with no extra spaces or line breaks, and the key status and remaining quota look right in the dashboard; the docs also list an expired subscription as a cause of 401. If you used the docs' optional alternative (environment variable only, null in auth.json), put the key back into auth.json first: with requires_openai_auth = true in the config, the official docs say Codex ignores env_key. Leftover environment variables can override your current settings, so run env | grep -i openai and clear the ones you no longer use. Then self-test with Authorization: Bearer and your key against https://api.qcode.cc/openai/v1/models: a JSON list means the address and key are fine. The docs read 401 as a key problem and 404 as a wrong path prefix; the Codex base_url should be https://api.qcode.cc/openai.

Sources

OpenAI side: the official Codex docs pages Authentication, Config basics, Advanced Configuration, Configuration Reference, Environment variables, command line options and IDE extension settings (the same docs are served on developers.openai.com/codex and learn.chatgpt.com), plus the Codex changelog and the GitHub openai/codex release notes for 0.156.0, 0.159.0 and 0.160.0, all fetched on 2026-10-07. QCode side: the QCode docs pages for the Codex tutorial (updated 2026-09-30), endpoints and API formats, error codes, troubleshooting and the Cursor setup, fetched the same day. Background: GitHub openai/codex issue 48237 and several other 401-related issues, quoted only as user reports.

One cr_ key for Codex and Claude Code

Codex reaches gpt-6-sol, gpt-6.1-sol and other GPT models through https://api.qcode.cc/openai, billed per token; see /models for each model's price.

Related reading

The steps and quotes on this page were checked on 2026-10-07 against the OpenAI Codex docs and changelog and the QCode docs; go by the official pages where they differ, and upstream changes may happen without notice. Error strings and descriptions from GitHub issues are user reports, not official findings. Model availability is whatever /models shows.

Try first, then decide

Not sure which tier? Start with Starter ($8.57/mo) and upgrade when you're happy — the unused value of the old plan goes back to your balance.