# How to Configure Multiple Provider Accounts in jcode

> Master configuring multiple provider accounts in jcode. Seamlessly switch between OpenAI and Claude with JSON storage and CLI commands. Boost your productivity now.

- Repository: [Jeremy Huang/jcode](https://github.com/1jehuang/jcode)
- Tags: how-to-guide
- Published: 2026-04-30

---

**jcode supports concurrent OpenAI and Claude accounts through JSON-backed storage with canonical labeling, enabling instant switching via CLI commands without restarting the session.**

Managing multiple AI provider accounts in the `jcode` CLI requires understanding its three-layer persistence architecture. The system automatically stores credentials in `~/.jcode/` JSON files, generates canonical labels like "openai-1" or "claude-2", and notifies the runtime when you switch active credentials.

## Understanding the Multi-Account Architecture

jcode implements multi-account support through three distinct layers:

| Layer | Responsibility | Core Implementation |
|-------|----------------|----------------------|
| **Account storage** | Persist JSON files containing account lists and active labels | [`src/auth/codex.rs`](https://github.com/1jehuang/jcode/blob/main/src/auth/codex.rs), [`src/auth/claude.rs`](https://github.com/1jehuang/jcode/blob/main/src/auth/claude.rs) |
| **Generic helpers** | Normalize labels, generate next label index, switch active accounts | [`src/auth/account_store.rs`](https://github.com/1jehuang/jcode/blob/main/src/auth/account_store.rs) |
| **Runtime integration** | Invalidate cached credentials and refresh model catalogs on switch | [`src/server/provider_control.rs`](https://github.com/1jehuang/jcode/blob/main/src/server/provider_control.rs), [`src/tui/app/auth_account_commands.rs`](https://github.com/1jehuang/jcode/blob/main/src/tui/app/auth_account_commands.rs) |

### Account Storage and File Locations

OpenAI accounts persist in **`~/.jcode/openai-auth.json`**, while Claude/Anthropic accounts use **`~/.jcode/auth.json`**. Both files store a `Vec<Account>` where each entry contains OAuth tokens and a **label** field (e.g., `"openai-1"`).

When first loaded, the `relabel_accounts()` helper in [`src/auth/account_store.rs`](https://github.com/1jehuang/jcode/blob/main/src/auth/account_store.rs) rewrites legacy labels into the canonical `prefix-index` format:

```rust
// src/auth/account_store.rs
pub fn relabel_accounts<T, FGet, FSet>(...) -> RelabelOutcome { ... }

```

## Adding New Provider Accounts

### Using the Interactive CLI

Run **`/account openai add`** or **`/account claude add`** to initiate OAuth authentication. The command invokes `upsert_account()` from [`account_store.rs`](https://github.com/1jehuang/jcode/blob/main/account_store.rs), which:

1. Assigns the next canonical label via `next_account_label()` if the incoming account has an empty label
2. Inserts the account into the stored vector
3. Sets the account as active if it is the first entry

```text
/account openai add

```

This opens an inline OAuth prompt. Upon completion, the account appears as `openai-2` (or the next available index).

### Programmatic Account Creation

You can add accounts programmatically using the provider-specific modules:

```rust
// Example: Add a new OpenAI account
let new_account = jcode::auth::codex::OpenAiAccount {
    label: "".to_string(),               // empty → auto-labelled
    access_token: "...".into(),
    refresh_token: "...".into(),
    id_token: None,
    account_id: None,
    expires_at: None,
    email: None,
};
let label = jcode::auth::codex::upsert_account(new_account)?;   // returns "openai-2"

```

For Claude accounts, use the `claude` module with `AnthropicAccount`:

```rust
use jcode::auth::claude::{self, AnthropicAccount};

let acct = AnthropicAccount {
    label: "".to_string(),               // auto-label → "claude-1"
    access: "...".into(),
    refresh: "...".into(),
    expires: 0,
    email: Some("user@example.com".into()),
    subscription_type: None,
};

let label = claude::upsert_account(acct)?; // label == "claude-1"

```

## Switching Between Active Accounts

### Command-Based Account Switching

Use **`/account openai switch <label>`** or the shorthand **`/:openai <label>`** to change the active account. The command handler in [`src/tui/app/auth_account_commands.rs`](https://github.com/1jehuang/jcode/blob/main/src/tui/app/auth_account_commands.rs) forwards the request to the server:

```rust
// src/tui/app/auth_account_commands.rs
AccountCommand::Switch { provider, label } => {
    match provider.as_str() {
        "openai" => remote.switch_openai_account(&label).await?,
        "claude" => remote.switch_anthropic_account(&label).await?,
        ...
    }
}

```

### Server-Side Invalidation Process

When switching, `handle_switch_openai_account()` in [`src/server/provider_control.rs`](https://github.com/1jehuang/jcode/blob/main/src/server/provider_control.rs) performs four critical operations:

1. Updates the JSON file via `crate::auth::codex::set_active_account(&label)`
2. Invalidates cached credentials via `provider.invalidate_credentials().await`
3. Clears provider-wide unavailability flags with `clear_all_provider_unavailability_for_account`
4. Resets the provider session and triggers a background usage-fetch and model-catalog refresh

```rust
// src/server/provider_control.rs
pub(super) async fn handle_switch_openai_account(...) {
    match crate::auth::codex::set_active_account(&label) {
        Ok(()) => {
            crate::auth::AuthStatus::invalidate_cache();
            // ... credential invalidation and cache clearing
        }
        Err(e) => { /* error handling */ }
    }
}

```

The same flow exists for Claude via `handle_switch_anthropic_account`.

## Listing Configured Accounts

Execute **`/account`** (without sub-commands) to open the inline account picker implemented in [`src/tui/app/auth_account_picker.rs`](https://github.com/1jehuang/jcode/blob/main/src/tui/app/auth_account_picker.rs). The UI renders a markdown table built by `render_openai_accounts_markdown()` and `render_claude_accounts_markdown()`, displaying each account's label, email, and active status.

```text
/account

```

This displays sections for **OpenAI Accounts** and **Claude Accounts**, clearly indicating which account is currently active.

## Handling Token Expiration

When OAuth tokens expire, jcode displays:

```text
OAuth token expired - use `/login openai` to re-authenticate

```

Run **`/login openai`** or **`/login claude`** to refresh tokens for the currently active account. Upon successful authentication, the system updates the JSON storage and calls `crate::auth::AuthStatus::invalidate_cache()` to reload the fresh credentials immediately.

## Summary

- **Storage**: Accounts persist in `~/.jcode/openai-auth.json` and `~/.jcode/auth.json` as JSON arrays with canonical labels
- **Adding**: Use `/account <provider> add` CLI commands or programmatic `upsert_account()` calls in [`src/auth/account_store.rs`](https://github.com/1jehuang/jcode/blob/main/src/auth/account_store.rs)
- **Switching**: The `/account <provider> switch <label>` command triggers server-side invalidation via `handle_switch_openai_account()` or `handle_switch_anthropic_account()` in [`src/server/provider_control.rs`](https://github.com/1jehuang/jcode/blob/main/src/server/provider_control.rs)
- **Runtime**: Account changes immediately invalidate cached credentials and refresh model catalogs without requiring a restart
- **Labels**: The system automatically generates canonical labels (e.g., "openai-1", "claude-2") using `next_account_label()` if none is provided

## Frequently Asked Questions

### How does jcode store multiple accounts for the same provider?

jcode stores multiple accounts in JSON files located at `~/.jcode/openai-auth.json` (OpenAI) and `~/.jcode/auth.json` (Claude). Each file contains an array of account objects with unique labels in the format `{provider}-{index}`, managed by the `relabel_accounts()` function in [`src/auth/account_store.rs`](https://github.com/1jehuang/jcode/blob/main/src/auth/account_store.rs).

### Can I switch between OpenAI and Claude accounts without restarting jcode?

Yes. Use `/account openai switch <label>` or `/account claude switch <label>` to switch instantly. The server invalidates cached credentials via `invalidate_credentials().await` and refreshes the model catalog automatically, as implemented in [`src/server/provider_control.rs`](https://github.com/1jehuang/jcode/blob/main/src/server/provider_control.rs).

### What happens if I don't specify a label when adding an account?

If you provide an empty label string, the `upsert_account()` helper in [`src/auth/account_store.rs`](https://github.com/1jehuang/jcode/blob/main/src/auth/account_store.rs) automatically generates the next available canonical label using `next_account_label()`. For example, if "openai-1" exists, the new account becomes "openai-2".

### How do I refresh expired tokens for a specific account?

Run `/login openai` or `/login claude` to initiate the OAuth flow for the currently active account. Upon successful authentication, the system updates the JSON storage and calls `AuthStatus::invalidate_cache()` to reload the fresh credentials immediately.