Subscription Auth

Use your Claude Pro / Qwen CLI subscription instead of an API key

Use the subscription you already pay for — Claude Pro, Qwen CLI, etc. — to power your Agent without a separate API key.

python
from praisonaiagents import Agent

agent = Agent(
    name="assistant",
    instructions="You are helpful.",
    llm="claude-haiku-4-5",
    auth="claude-code",
)
agent.start("Hello")

The user chats via the agent; subscription auth supplies credentials instead of a raw API key.

Diagram

Subscription Auth Flow

👤 User

🤖 Agent

🔍 Provider Registry

💾 Token Store

🌐 LLM API

Quick Start

  1. Login to CLI

    claude /login (Claude Pro) — or qwen login

  2. Use in Agent
    python
    from praisonaiagents import Agent
    
    agent = Agent(
        name="assistant",
        instructions="You are helpful.",
        llm="claude-haiku-4-5",
        auth="claude-code",
    )
    agent.start("Hello")

How It Works

Subscription Auth enables agents to use your existing subscriptions without requiring separate API keys.

Diagram
Anthropic APIKeychain / ~/.claudeClaudeCodeAuthAgentUserAnthropic APIKeychain / ~/.claudeClaudeCodeAuthAgentUserExpired token → user runs `claude /login`,PraisonAI never rotates the shared session.Request with auth=”claude-code”resolve_credentials()Read (never write)accessToken, expiresAtSubscriptionCredentialsBearer accessToken + anthropic-beta headersResponseFinal response

Cloning & multi-channel

clone_for_channel() forwards auth=, so gateway and multi-channel setups that duplicate the agent per channel keep subscription billing.

python
from praisonaiagents import Agent

agent = Agent(name="t", llm="anthropic/claude-sonnet-4-5", auth="claude-code")
clone = agent.clone_for_channel()
# clone.auth == "claude-code"

Supported Providers

Which subscription auth provider should you use?

Diagram

Choose Your Provider

Yes

No

Yes

No

Yes

No

Yes

No

Have Claude Pro?

auth=’claude-code’

No llm= →
anthropic/claude-sonnet-4-5

Have Qwen?

auth=’qwen-cli’

No llm= →
openai/qwen3-coder-plus

Have ChatGPT Plus?

auth=’codex’
(experimental)

Have Gemini?

auth=’gemini-cli’
(experimental)

Use API keys instead

auth= valueSubscriptionToken sourceStatus
claude-codeClaude Pro~/.claude/.credentials.json or macOS Keychain✅ Working
qwen-cliQwen~/.qwen/oauth_creds.json✅ Working
codexChatGPT Plus~/.codex/auth.json⚠️ Experimental
gemini-cliGemini~/.gemini/oauth_creds.json⚠️ Experimental

Common Patterns

No model needed

Skip llm= and the provider’s vendor model is chosen for you.

python
from praisonaiagents import Agent

# No llm= — defaults to anthropic/claude-sonnet-4-5 for claude-code,
# openai/qwen3-coder-plus for qwen-cli.
agent = Agent(name="t", auth="claude-code")
agent.start("Hello")

Choose your refresh path

When a claude-code token expires, pick the action that matches your setup — PraisonAI never rotates it for you.

Diagram

Token expired — what do I do?

Yes

No

Yes

No

Have Claude CLI installed?

Run ‘claude /login’
OR let CLI refresh in background

Want a long-lived token?

Set ANTHROPIC_TOKEN
or CLAUDE_CODE_OAUTH_TOKEN

Install Claude CLI, then ‘claude /login’

Telegram bot using your Claude subscription

python
from praisonaiagents import Agent
from praisonai.bots import Bot

agent = Agent(
    name="claude-bot",
    instructions="You are a helpful Telegram bot.",
    llm="claude-haiku-4-5",
    auth="claude-code",
)

bot = Bot("telegram", agent=agent)
bot.run()

Switching between API key and subscription

python
from praisonaiagents import Agent

# Use your Claude Pro subscription
agent_subscription = Agent(
    name="claude-subscription",
    llm="claude-haiku-4-5",
    auth="claude-code",  # Uses your subscription
)

# Use API key (traditional method — set ANTHROPIC_API_KEY)
agent_api_key = Agent(
    name="claude-api",
    llm="claude-haiku-4-5",
)

# Both work identically
response1 = agent_subscription.start("Hello")
response2 = agent_api_key.start("Hello")

Custom provider (advanced)

python
from praisonaiagents.auth import register_subscription_provider, SubscriptionCredentials

class MyAuth:
    def resolve_credentials(self) -> SubscriptionCredentials:
        return SubscriptionCredentials(
            api_key="my-token",
            base_url="https://my-provider.com/v1",
            headers={"x-custom": "value"},
        )

    def refresh(self) -> SubscriptionCredentials:
        return self.resolve_credentials()

    def headers_for(self, base_url: str, model: str) -> dict:
        return {"user-agent": "my-provider/1.0.0"}

register_subscription_provider("mine", lambda: MyAuth())

Configuration Options

SubscriptionCredentials API Reference

Configuration options and types for subscription credentials

Best Practices

Fail-fast at construction

Agent(auth=...) resolves credentials eagerly at construction. An unknown provider id, missing credentials, or an experimental provider raises AuthError from the Agent(...) call itself rather than silently falling back to your API key at request time. This is deliberate — a silent downgrade means money leaves the wrong account.

Token Security

Tokens are read locally from your CLI tools and never persisted by PraisonAI. They stay in memory only during agent execution. PraisonAI does not refresh or write them back — see “Read-only for claude-code” below.

Backward Compatibility

Existing agents using API keys continue to work unchanged. Setting auth= is completely optional and doesn’t affect agents that don’t use it.

Read-only for claude-code

PraisonAI reads Claude Code OAuth credentials from macOS Keychain, ~/.claude/.credentials.json, or the ANTHROPIC_TOKEN / CLAUDE_CODE_OAUTH_TOKEN env vars, and uses them as-is. It never refreshes or persists tokens for auth="claude-code".

This is deliberate. Anthropic rotates OAuth refresh tokens on every use. If PraisonAI refreshed in-memory without writing back to the Keychain, it would silently invalidate the shared session used by the Claude CLI and any other tool on the same machine.

When your Keychain token expires, refresh it once via one of these paths — Claude CLI will keep it fresh from then on: – Run claude /login (interactive), OR – Let the Claude CLI run in the background — its own refresh keeps the Keychain entry valid, OR – Set ANTHROPIC_TOKEN (or CLAUDE_CODE_OAUTH_TOKEN) to a long-lived token — env vars take precedence over Keychain/file.

Programmatic provider.refresh() for claude-code raises AuthError with a message telling you exactly what to do next. This is not a bug — it’s the guard that keeps shared sessions safe.

Don't commit token files

Never commit ~/.claude/.credentials.json, ~/.qwen/oauth_creds.json, or similar files to version control. These contain sensitive authentication data.

Troubleshooting

“No Claude Code credentials found”

Solution: Install Claude Code CLI and run claude /login to authenticate with your Claude Pro account.

bash
# Install Claude Code (if not already installed)
# Then login with your Claude Pro account
claude /login

invalid_request_error on a Claude Code request

What it means: This is a genuine client-side error (a malformed request), not a retryable auth failure. PraisonAI no longer misclassifies it as an expired-token error, so it surfaces directly instead of being silently retried.

Solution: Check the request itself — model name, message shape, or parameters. If you also see auth failures, re-authenticate the shared session:

bash
claude /login

macOS Keychain prompt every run

Solution: Grant security command access to avoid repeated prompts. This is handled automatically by the system after first access.

Anthropic 500 errors

Issue: OAuth tokens require specific headers that are automatically included.

What PraisonAI does: Automatically sends required headers:

  • user-agent: claude-cli/<version> (external, cli)
  • x-app: cli
  • anthropic-beta: interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14,claude-code-20250219

Codex / Gemini raise AuthError

Expected behavior: These providers are experimental and not yet usable. They raise AuthError at construction time — Agent(auth="codex") raises immediately, not at first request.

python
from praisonaiagents import Agent

Agent(name="t", auth="codex")
# AuthError: Codex provider is experimental — needs Responses API transport wiring

AuthError: Unknown subscription provider

Cause: A typo or unregistered id in auth=. Valid ids are claude-code, codex, gemini-cli, and qwen-cli. The typo now surfaces at construction rather than silently billing your API key.

python
from praisonaiagents import Agent
from praisonaiagents.auth.subscription.protocols import AuthError

try:
    Agent(name="t", llm="gpt-4o-mini", auth="nonexistent-provider")
except AuthError as e:
    print(e)
# Unknown subscription provider 'nonexistent-provider'.
# Registered: ['claude-code', 'codex', 'gemini-cli', 'qwen-cli']

I passed a bare model name and it silently billed my API key

Cause: Before PR #4147, auth= was dropped when llm= was a bare model name (or omitted), so requests went out on OPENAI_API_KEY. This is fixed — upgrade praisonai-agents to a version that includes the fix and every llm= shape honours auth=.

Mixing an OpenAI model with subscription auth

Behavior: Responses-API-eligible models (gpt-4o, o1, o3, openai/) with subscription auth now route to the subscription’s vendor. The simpler path is to omit llm= and let the auto-default pick the right vendor for your seat.

Token Detection Priority (Claude Pro)

Tokens are resolved in this exact order:

  1. ANTHROPIC_TOKEN environment variable
  2. CLAUDE_CODE_OAUTH_TOKEN environment variable
  3. macOS Keychain (Claude Code-credentials)
  4. ~/.claude/.credentials.json file

Environment Variable Override

You can disable subscription auth entirely (planned):

bash
export PRAISONAI_DISABLE_SUBSCRIPTION_AUTH=1

Related