Auth

Store and manage LLM provider credentials securely — API keys or browser-based OAuth

praisonai auth stores provider credentials locally so praisonai run works without exporting env vars every session. Two methods are supported: a long-lived API key, or a short-lived OAuth token signed in via your browser.

Diagram

praisonai auth login

🔑 API Key

~/.praisonai/credentials.json

🌐 OAuth Token

⚡ praisonai run

Quick Start

Picker

bash
# Omit the provider to pick from the model catalogue (~30+ LLMs)
praisonai auth login
# → Choose your LLM provider: openai | anthropic | google | ollama | ... | custom

praisonai run "What is 2+2?"

The picker is built from ModelCatalogue.list_providers(): openai, anthropic, google, ollama first, every other catalogue provider (Groq, OpenRouter, Mistral, DeepSeek, xAI, …) next, and custom last.

API Key

bash
# Interactive — key is hidden at the prompt
praisonai auth login openai

# Then run immediately
praisonai run "What is 2+2?"

OAuth (Browser)

bash
# Device-code flow; sign in; tokens stored + auto-refreshed
praisonai auth login github --method oauth --client-id <your-github-oauth-app-client-id>

praisonai run "What is 2+2?"

Headless / SSH

bash
# Prints a URL + code instead of opening a browser
praisonai auth login github --method oauth --client-id <your-github-oauth-app-client-id> --no-browser
# To sign in, visit https://github.com/login/device and enter code: ABCD-EFGH

praisonai run "What is 2+2?"

Commands

CommandDescription
auth login [<provider>]Store credentials. Omit <provider> to open the catalogue-driven picker. --method auto (default) picks OAuth if available, else API key
auth listShow stored and active env-var providers with method, source, and expiry
auth status [provider]Format check; --validate does a live call (uses a freshly-refreshed OAuth token)
auth logout <provider>Remove one provider
auth logout --allRemove all stored credentials

Login flags

FlagDefaultPurpose
`–method auto\apikey\oauth`autoForce a flow, or let the CLI pick
--key sk-…(prompt)API-key only; skip prompt
--key-stdinoffRead API key from stdin
--no-browseroffOAuth only; print URL + code instead of opening a browser
--base-url URL(default)Custom provider endpoint
--model NAME(none)Default model recorded with the credential
--skip-validationoffAPI-key only; skip the live check
--client-id ID(from registry)OAuth client id. Overrides the built-in registry entry. Required for registry providers until a first-party PraisonAI app is provisioned
--token-url URL(from registry)OAuth token endpoint. Set to enable OAuth for a provider outside the registry (e.g. self-hosted gateway)
--device-authorization-url URL(from registry)RFC 8628 device-code endpoint
--authorization-url URL(from registry)RFC 7636 PKCE authorization endpoint
--scope SCOPE(from registry)Scope(s) to request; overrides the registry default

Which --method should I use?

Diagram

Yes

No

No

Yes

Yes

No

Yes

No

Yes

No

Which method?

Have a long-lived API key?

–method apikey

Provider in registry?
github / google / gemini / azure

Have OAuth endpoints for a
self-hosted gateway?

Registered your own
OAuth app?

–method oauth
–token-url …
–device-authorization-url …
–client-id …

On a headless server / SSH?

Register an OAuth app first,
then re-run with –client-id

–method oauth –client-id …
–no-browser

–method oauth –client-id …

Provider Picker

Omit the provider argument and auth login opens a catalogue-driven picker so you never have to memorise a provider id.

bash
praisonai auth login
Choose your LLM provider:
  1) openai
  2) anthropic
  3) google
  4) ollama
  5) groq
  6) openrouter
  7) mistral
  ...
  N) Other/custom
Select provider [1]:

The list comes from ModelCatalogue.list_providers(): openai, anthropic, google, ollama first, every other catalogue provider appended, custom last. Picking custom prompts for a free-form provider id. The positional <provider> argument still works unchanged — keep passing it in scripts and CI.

Diagram

Yes

No

apikey

oauth

praisonai auth login

Provider passed
as argument?

Which method?

Show catalogue picker
curated → others → custom

Print ‘Get your key:’ URL

Browser / device-code flow

Prompt for hidden key

OAuth Login (browser-based)

Two flows are negotiated automatically per provider:

  • Device code (RFC 8628) — prints a short code + URL, polls until you approve. Default on headless servers.
  • Authorization code + PKCE (RFC 7636) — opens your default browser and listens on 127.0.0.1 for the redirect. Default on desktops.
Diagram
ProviderBrowserpraisonaiUserProviderBrowserpraisonaiUserpraisonai auth login github –method oauth –client-id …Open authorize URL (PKCE) / print device codeUser signs in & approvescode (via 127.0.0.1 callback) / poll → tokenStore access_token + refresh_token in ~/.praisonai/credentials.json✅ Signed in to github via OAuth

The OAuth callback handler and PKCE helpers are shared with the MCP OAuth integration.

Token storage & refresh

ItemValue
File~/.praisonai/credentials.json (unchanged from API-key flow)
Legacy fallback~/.praison/credentials.json is still read if the canonical file doesn’t exist yet; migrated onto the canonical file on the next write. No auth logout/re-login needed.
Permissions0o600, atomic writes
RefreshTransparent on next use, ~60 s before expires_at
Refresh failureCLI returns no token rather than a stale one — re-run auth login

Self-hosted / custom OAuth providers

praisonai auth login ships built-in endpoints for github, google, gemini, and azure. For a provider outside the registry (e.g. a self-hosted gateway), supply the endpoints directly with CLI flags:

bash
praisonai auth login my-gateway --method oauth \
  --client-id cli \
  --device-authorization-url https://gateway.example.com/oauth/device \
  --token-url https://gateway.example.com/oauth/token \
  --scope "read write"

The CLI infers the device flow when a --device-authorization-url is present, or falls back to authcode (PKCE) when you pass --authorization-url instead.

As an escape hatch, you can also register a provider config from Python before invoking the flow:

python
from praisonai_code.cli.configuration.oauth import OAUTH_PROVIDERS, OAuthProviderConfig

OAUTH_PROVIDERS["my-gateway"] = OAuthProviderConfig(
    flow="device",
    client_id="my-client-id",
    token_url="https://gateway.example.com/oauth/token",
    device_authorization_url="https://gateway.example.com/oauth/device",
    scope="read write",
)

Headless / SSH

bash
praisonai auth login github --method oauth --client-id <your-github-oauth-app-client-id> --no-browser
# To sign in, visit https://github.com/login/device and enter code: ABCD-EFGH

API Key Login

bash
praisonai auth login openai
praisonai auth login openai --key sk-...
echo "sk-..." | praisonai auth login openai --key-stdin

# Optional extras
praisonai auth login openai --key sk-... --base-url https://api.openai.com/v1 --model gpt-4o
praisonai auth login openai --skip-validation

Replacing an existing key

The store holds one credential per provider, so auth login never overwrites a different stored key silently.

Interactive

bash
praisonai auth login openai
# Enter API key for openai: ****************
# API key validated: live call ok
# Replace existing openai key sk-o***1111? [Y/n]: n
# → exits with status 1; the old key is kept

Press Enter (default yes) to replace the key, or type n to abort.

Scripted (–key / –key-stdin)

bash
praisonai auth login openai --key sk-new-...2222
# API key validated: live call ok
# Replacing existing openai key sk-o***1111
# Stored credentials for openai
# Key stored: sk-n***2222

No prompt — scripts behave as before. The Replacing existing … line records which redacted key was overwritten.

JSON

bash
praisonai --json auth login openai --key sk-new-...2222
json
{
  "provider": "openai",
  "status": "stored",
  "key_redacted": "sk-n***2222",
  "replaced_key_redacted": "sk-o***1111"
}

replaced_key_redacted appears only when a different stored key was replaced.

Diagram

No

Yes

typed at prompt

–key / –key-stdin

Y

n

praisonai auth login provider

Different key already
stored for provider?

Store key
no prompt

How was the
key supplied?

Replace existing key?
Y / n

Print ‘Replacing existing …’
then store

Exit 1
old key kept

List & status

bash
praisonai auth list
praisonai auth status
praisonai auth status openai --validate

Logout

bash
praisonai auth logout openai
praisonai auth logout --all

List & Status — new columns

bash
praisonai auth list
# Provider   Method   Source                   Secret          Expires   Base URL      Model
# openai     apikey   env (OPENAI_API_KEY)      sk-1***efgh     (n/a)     (default)     (none)
# anthropic  apikey   stored                   sk-a***wxyz     (n/a)     (default)     (none)
# groq       apikey   env (GROQ_API_KEY)        gsk_***abcd     (n/a)     (default)     (none)
# github     oauth    stored                   at-9***xyz1     59m       (default)     (none)

Source shows where the live credential comes from: stored (from ~/.praisonai/credentials.json) or env (VARNAME) when an active environment variable is present. An env key that shadows a stored one is flagged env (VARNAME) [live, overrides stored].

Expires shows (n/a) for API keys, 59m, 1h 23m, <1m, or expired for OAuth tokens, and (no expiry) when the provider didn’t include one.

Stored vs environment credentials

auth list and auth status surface both stored credentials (from ~/.praisonai/credentials.json) and active environment-variable keys for catalogue-known providers. The Source column disambiguates them:

SourceMeaning
storedFrom ~/.praisonai/credentials.json (unchanged behaviour)
env (VAR_NAME)Live credential coming from an environment variable; the var name is shown for unambiguity
env (in-memory)The whole store was loaded from PRAISONAI_AUTH_CONTENT — see Zero-disk mode

When PRAISONAI_AUTH_CONTENT is set, every stored-side entry reports env (in-memory) instead of stored:

bash
praisonai auth list
# Provider   Method   Source            Secret          Expires   Base URL   Model
# openai     apikey   env (in-memory)   sk-1***efgh     (n/a)     (default)  (none)
# github     oauth    env (in-memory)   gho_***xyz1     59m       (default)  (none)

If both a stored and an env credential exist for one provider, the env value wins at run time (this is unchanged) — the Source line appends [live, overrides stored] so it’s visible:

bash
praisonai auth list
# Provider   Method   Source                                       Secret          Expires   Base URL   Model
# openai     apikey   env (OPENAI_API_KEY) [live, overrides stored] sk-1***efgh     (n/a)     (default)  (none)

Both stored and env secrets are redacted the same way (sk-1***efgh).

Credential env-var aliases

Some providers declare more than one credential env-var spelling — Together’s TOGETHER_API_KEY / TOGETHERAI_API_KEY, Fireworks’ FIREWORKS_API_KEY / FIREWORKS_AI_API_KEY, Google’s GOOGLE_API_KEY / GEMINI_API_KEY. auth list and auth status recognise every alias — a key set under a secondary spelling is surfaced exactly like one set under the canonical first.

bash
export TOGETHERAI_API_KEY=sk-1234abcdefgh
praisonai auth list
# Provider   Method   Source                     Secret          Expires   Base URL   Model
# together   apikey   env (TOGETHERAI_API_KEY)   sk-1***efgh     (n/a)     (default)  (none)

The Source column names whichever alias is live in the environment, so it always tells you exactly which variable the runtime is reading — even when a secondary spelling is the one you set. If no alias is set but the provider is otherwise referenced, the canonical first var name is shown for display only.

Diagram

No

Yes

Yes

No

Yes

No

praisonai auth list / status

Provider in
PROVIDER_ENV_CATALOGUE?

Not surfaced

Iterate declared
env-var aliases

Any alias set
in os.environ?

Source = env ALIAS_NAME
whichever is live

Stored credential?

Source = stored

Report missing
show canonical var name

Credential Fields (ProviderCredential)

Each stored provider credential contains the following fields:

FieldTypeDescription
api_keystrStatic API key. For OAuth flows, mirrors access_token for backward compatibility
access_tokenstrOAuth access token (empty for API-key credentials)
refresh_tokenstrOAuth refresh token used for silent renewal
expires_atintUnix epoch seconds when the access token expires. 0 for API keys (no expiry)
auth_methodstr"apikey" or "oauth" — indicates which flow was used to store this credential

auth list and auth status show Method and Expires columns reflecting these fields.


Storage & Security

ItemValue
File~/.praisonai/credentials.json
Legacy fallbackRead-only fallback: ~/.praison/credentials.json. Any write transparently migrates its entries onto the canonical file.
Permissions0o600 (auto-corrected on read if loose)
WritesAtomic via temp file + os.replace
DisplayKeys/tokens redacted as at-9***xyz1 everywhere except the file
OAuth internalsrefresh_token, token_url, client_id are scrubbed before crossing into the LLM resolver — never sent to the provider
Zero-disk modeSet PRAISONAI_AUTH_CONTENT to a JSON store — nothing is read from or written to credentials.json for the process lifetime. See below
Replaceauth login confirms (interactive) or reports (scripted) before overwriting a different stored key. See Replacing an existing key

Zero-disk mode (PRAISONAI_AUTH_CONTENT)

Set PRAISONAI_AUTH_CONTENT to a JSON credential store and PraisonAI runs entirely from memory — nothing is ever read from or written to ~/.praisonai/credentials.json.

Use it for ephemeral containers (Docker/Kubernetes), CI runners, and secret-hygiene-sensitive deployments where OAuth and refresh tokens must not touch disk. A single secret injects the whole store — including OAuth entries — with zero on-disk footprint.

Diagram

Yes

No

No

Yes

Where should credentials live?

Ephemeral container /
CI runner / no disk?

PRAISONAI_AUTH_CONTENT
zero-disk, in-memory
whole store as JSON

Multiple providers
or OAuth tokens?

Per-provider env var
e.g. OPENAI_API_KEY

praisonai auth login
stored in ~/.praisonai/credentials.json

The value is a JSON object mapping provider name → credential object. Each credential object mirrors the on-disk ProviderCredential fields — api_key and auth_method for API keys, plus access_token, refresh_token, and expires_at for OAuth:

json
{
  "openai": {
    "api_key": "sk-...",
    "auth_method": "apikey"
  },
  "github": {
    "api_key": "gho_...",
    "auth_method": "oauth",
    "access_token": "gho_...",
    "refresh_token": "ghr_...",
    "expires_at": 1723545600
  }
}

Bash / CI

bash
# Inject the store from a CI secret; nothing is written to disk
export PRAISONAI_AUTH_CONTENT='{"openai":{"api_key":"sk-...","auth_method":"apikey"}}'
praisonai run "What is 2+2?"

Python

python
import json, os
from praisonaiagents import Agent

os.environ["PRAISONAI_AUTH_CONTENT"] = json.dumps({
    "openai": {"api_key": "sk-...", "auth_method": "apikey"},
})

agent = Agent(name="CI Agent", instructions="Answer briefly")
agent.start("What is 2+2?")
# No ~/.praisonai/credentials.json is created — everything stays in memory.

Invalid values fail fast rather than silently falling back to disk, protecting the zero-disk contract:

ValueBehaviour
UnsetFalls back to disk (unchanged behaviour)
Empty / whitespace onlyRaises ValueError — misconfiguration, no silent disk fallback
Not valid JSONRaises ValueError with the underlying decode error
JSON scalar or array (not an object)Raises ValueError — must map provider → credential
A provider value that isn’t a JSON objectRaises ValueError naming the offending provider(s)
Valid object with OAuth tokensLoaded in memory; OAuth refresh works and stays in memory

auth list and auth status report the Source column as env (in-memory) in this mode via the CredentialStore.is_in_memory property, so zero-disk runs are never silently invisible.

Supported Providers

praisonai auth list / praisonai auth status recognise every provider in PROVIDER_ENV_CATALOGUE — the same catalogue that drives first-run credential detection (full 14-row table) and praisonai setup.

ProviderAPI key env var(s)Default modelOAuth supported today?
openaiOPENAI_API_KEY (+ OPENAI_BASE_URL if set)gpt-4o-miniNot yet — use API key
anthropicANTHROPIC_API_KEYanthropic/claude-3-5-sonnet-latestNot yet — use API key
google / geminiGOOGLE_API_KEY / GEMINI_API_KEYgemini/gemini-1.5-flash✅ — device flow, requires --client-id
groqGROQ_API_KEYgroq/llama-3.3-70b-versatileNot yet — use API key
cohereCOHERE_API_KEYcohere/command-rNot yet — use API key
openrouterOPENROUTER_API_KEYopenrouter/openai/gpt-4o-miniNot yet — use API key
ollamaOLLAMA_API_BASE / OLLAMA_HOST (endpoint hints, not credentials)ollama/llama3.2n/a — local
mistralMISTRAL_API_KEYmistral/mistral-large-latestNot yet — use API key
deepseekDEEPSEEK_API_KEYdeepseek/deepseek-chatNot yet — use API key
xaiXAI_API_KEYxai/grok-2-latestNot yet — use API key
togetherTOGETHER_API_KEY or TOGETHERAI_API_KEYtogether_ai/meta-llama/Llama-3.3-70B-Instruct-TurboNot yet — use API key
perplexityPERPLEXITYAI_API_KEYperplexity/sonarNot yet — use API key
fireworksFIREWORKS_API_KEY or FIREWORKS_AI_API_KEYfireworks_ai/accounts/fireworks/models/llama-v3p3-70b-instructNot yet — use API key
githubn/a(your model)✅ — device flow, requires --client-id
azuren/a(your model)✅ — device flow, requires --client-id
Self-hosted / customn/a(your model)✅ — supply endpoints via --token-url / --device-authorization-url
Plugin providers (via praisonai.providers entry point)`

_API_KEY` (derived) | (your model) | Not yet — use API key |

Unknown providers accept API keys with length ≥ 10. OAuth is opt-in per provider; registry providers ship endpoints but no client_id — pass yours with --client-id.