llm-auth is a local credential manager for LLM developer tools. It keeps
API-key and subscription-OAuth credentials in an ignored project .env file,
with metadata that makes each auth surface discoverable, testable, and
refreshable.
It is built for local workflows where scripts, benchmarks, and agent tools need model access without committing secrets or scattering credentials across many ad hoc files.
- API-key surfaces for providers such as OpenAI and OpenRouter.
- ChatGPT subscription OAuth through LiteLLM's ChatGPT provider.
- Redacted credential status checks.
- Lightweight auth validation.
- OAuth token refresh for renewable surfaces.
- Repo-local
.envauth storage with explicit metadata envelopes.
pip install llm-authAfter installation, use the llm-auth command:
llm-auth statusA surface is a named auth target, such as chatgpt, research, or evals.
Each surface has an auth mode:
api-key: a provider key stored in a named env var.subscription-oauth: a renewable OAuth record managed through LiteLLM.
llm-auth stores those surfaces in .env as metadata envelopes. The env file
must be ignored by version control.
Before using an existing .env, llm-auth checks that the file is not readable
by group or other users. In Git, Sapling, and Mercurial repositories, it also
checks that the file is ignored, not currently tracked, and not present in
commit history.
# Create or refresh the ChatGPT subscription OAuth surface.
llm-auth login chatgpt
# Show redacted status for the ChatGPT surface.
llm-auth status chatgpt
# Validate the ChatGPT auth state without printing secrets.
llm-auth test chatgptlogin chatgpt uses LiteLLM's ChatGPT authenticator. LiteLLM owns the
device-code flow and token exchange; llm-auth stores the resulting auth
record in .env as CHATGPT_AUTH_JSON.
If the stored access token is still valid, llm-auth login chatgpt exits with
ok. If a refresh token exists, it attempts refresh before opening a new
device-code login.
When you already have refreshable OAuth state, renew without opening a browser:
# Refresh only the ChatGPT subscription OAuth surface.
llm-auth renew chatgptDevice codes are sensitive while active. Never share them.
Create an API-key surface:
# Create a research surface for OpenAI. The default env var template is
# <PROVIDER>_<SURFACE>_API_KEY, so this uses OPENAI_RESEARCH_API_KEY.
llm-auth add-api-key research openai --model gpt-4.1-miniThis appends an empty env assignment to .env:
# BEGIN LLM AUTH SURFACE research api-key
# surface=research
# provider=openai
# auth=api-key
# env=OPENAI_RESEARCH_API_KEY
# model=gpt-4.1-mini
OPENAI_RESEARCH_API_KEY=
# END LLM AUTH SURFACE research api-keyFill in the key manually, then check it:
# Show redacted status for every configured surface.
llm-auth status
# Test every configured surface.
llm-auth test
# Test only the research surface.
llm-auth test researchUse a specific env var when a provider already has a conventional name:
# Create an OpenRouter surface backed by OPENROUTER_API_KEY.
llm-auth add-api-key evals openrouter \
--env OPENROUTER_API_KEY \
--api-base https://openrouter.ai/api/v1 \
--model openrouter/openai/gpt-5.4-miniUse --key only when you intentionally want the command to write the secret:
llm-auth add-api-key research openai \
--model gpt-4.1-mini \
--key "$OPENAI_RESEARCH_API_KEY"API-key surfaces do not use login or renew. Rotate them by changing the
configured env var value.
Apps that use LiteLLM can treat .env as the local auth bootstrap file.
For API-key surfaces, load .env before creating LiteLLM requests and read the
configured env var, such as OPENAI_RESEARCH_API_KEY or OPENROUTER_API_KEY.
The metadata envelope tells your app which provider, model, API base, and env
var belong to a surface.
For ChatGPT subscription OAuth, load .env so CHATGPT_AUTH_JSON is present,
then install the llm-auth ChatGPT/LiteLLM integration before making
chatgpt/... calls. That integration makes LiteLLM read and refresh the auth
record from .env instead of its default token file.
Show redacted status for all discovered surfaces:
# Show every API-key and OAuth surface discovered in .env.
llm-auth statusShow one surface:
# Show only ChatGPT subscription OAuth status.
llm-auth status chatgpt
# Show only an API-key surface named research.
llm-auth status researchExample ChatGPT status:
{
"chatgpt": {
"access_token": true,
"account_id": true,
"auth": "subscription-oauth",
"auth_json": true,
"env": "CHATGPT_AUTH_JSON",
"expired": false,
"refresh_token": true
}
}Example API-key status:
{
"research": {
"api_key": true,
"auth": "api-key",
"env": "OPENAI_RESEARCH_API_KEY",
"provider": "openai"
}
}Status output reports whether credentials are present, but does not print secret values.
Run lightweight checks for all discovered surfaces:
# Test every configured auth surface discovered in .env.
llm-auth testRun detailed checks for one surface:
# Test only ChatGPT subscription OAuth state.
llm-auth test chatgpt
# Test only an API-key surface named research.
llm-auth test researchFor ChatGPT subscription OAuth, the default test checks that CHATGPT_AUTH_JSON
exists and contains usable OAuth state. The ChatGPT backend completion path is
Cloudflare-gated and is not part of the documented OpenAI API, so the live
completion check is opt-in. Add live_backend=true to the chatgpt surface
envelope only when you explicitly want llm-auth test chatgpt to send a live
backend prompt.
For OpenAI API-key surfaces that declare a model, llm-auth test also checks
model access and sends a small prompt through the official OpenAI Responses API:
llm-auth test research --model gpt-4.1-mini
llm-auth test --surface-model research=gpt-4.1-miniCreate or refresh a managed login surface:
# Run the ChatGPT subscription OAuth login flow.
llm-auth login chatgptToday, login is for ChatGPT subscription OAuth. API-key surfaces are created
with add-api-key.
Refresh renewable OAuth surfaces without a new browser/device-code flow:
# Refresh every renewable auth surface discovered in .env.
llm-auth renew
# Refresh only ChatGPT subscription OAuth.
llm-auth renew chatgptUse login when you need to create a new auth surface or complete a new
browser/device-code flow. Use renew when a surface already has refreshable
OAuth state.
Create an API-key metadata envelope:
llm-auth add-api-key <surface> <provider>Useful options:
--env: choose the env var name.--model: record default model metadata for tests and consumers.--api-base: record provider API base metadata.--key: write the secret value directly.--allow-unignored: allow writing to an env file that is not ignored.
By default, llm-auth refuses to write credentials unless the target is an
ignored repo-root .env. Use --allow-unignored only for deliberate temporary
setups; it does not bypass checks for unsafe file permissions, tracked files,
or committed history.
llm-auth treats .env as a bootstrapping auth store. Each auth surface is
wrapped in a metadata envelope:
# BEGIN LLM AUTH SURFACE research api-key
# surface=research
# provider=openai
# auth=api-key
# env=OPENAI_RESEARCH_API_KEY
# model=gpt-5.4-mini
# renew=false
OPENAI_RESEARCH_API_KEY=...
# END LLM AUTH SURFACE research api-keyThe .env file should be ignored by version control. llm-auth sets file mode
0600 when it writes the file, where supported by the operating system. If an
existing .env has broader permissions, fix it before running commands:
chmod 600 .env- Do not commit
.envor copied credential values. - Keep
.envignored and untracked.llm-authrefuses to use it when Git, Sapling, or Mercurial reports that it is tracked or appears in commit history. - Prefer leaving
--keyout and filling the secret manually when possible. - Treat device codes as temporary secrets.
llm-auth statusredacts credential presence and does not print secret values.llm-authis local tooling, not a hosted secret manager.
- Python 3.11 or newer.
- LiteLLM, installed as a package dependency.
MIT. See LICENSE.