Switching Codex between login quota and a custom API

I wanted a boring switch: use Codex with the official login quota most of the time, then manually switch to a custom API profile when needed, without re-entering provider settings every time.

The useful constraint is that both modes should share the same local history. I do not want two separate Codex homes, two sets of sessions, or a migration step every time I change providers. The switch should only change how future requests are routed.

The key idea

Codex reads its active configuration from one file under the Codex home:

%USERPROFILE%\.codex\config.toml

Instead of editing that file by hand, keep two sanitized profile copies:

%USERPROFILE%\.codex\profiles\official.config.toml
%USERPROFILE%\.codex\profiles\custom-api.config.toml

Switching profiles is just copying one of those files over the active config.toml. The rest of the Codex home remains untouched:

%USERPROFILE%\.codex\sessions
%USERPROFILE%\.codex\session_index.jsonl
%USERPROFILE%\.codex\history.jsonl

That is why chat history can stay shared. The switch changes provider configuration, not the directory where Codex stores conversations.

Preparing the profiles

First, save the currently working official-login configuration as the official profile:

New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.codex\profiles"
Copy-Item "$env:USERPROFILE\.codex\config.toml" `
  "$env:USERPROFILE\.codex\profiles\official.config.toml"

Then save the custom API configuration as a second profile:

Copy-Item "path\to\custom-api-config.toml" `
  "$env:USERPROFILE\.codex\profiles\custom-api.config.toml"

Do not publish the real custom API profile. It may contain a private base URL, model names, account-specific settings, or environment assumptions that should stay local.

The switching script

The switcher accepts official, custom-api, or status. Before overwriting the active config, it saves a timestamped backup.

param(
    [Parameter(Mandatory = $true, Position = 0)]
    [ValidateSet("official", "custom-api", "status")]
    [string] $Profile
)

$ErrorActionPreference = "Stop"

$codexHome = Join-Path $HOME ".codex"
$configPath = Join-Path $codexHome "config.toml"
$profilesDir = Join-Path $codexHome "profiles"
$backupDir = Join-Path $profilesDir "backups"

$profiles = @{
    official = Join-Path $profilesDir "official.config.toml"
    "custom-api" = Join-Path $profilesDir "custom-api.config.toml"
}

if ($Profile -eq "status") {
    Write-Host "Active config: $configPath"
    exit 0
}

$sourcePath = $profiles[$Profile]
if (!(Test-Path -LiteralPath $sourcePath)) {
    throw "Profile config not found: $sourcePath"
}

New-Item -ItemType Directory -Force -Path $backupDir | Out-Null

if (Test-Path -LiteralPath $configPath) {
    $stamp = Get-Date -Format "yyyyMMdd-HHmmss"
    $backupPath = Join-Path $backupDir "config.$stamp.toml"
    Copy-Item -LiteralPath $configPath -Destination $backupPath -Force
}

Copy-Item -LiteralPath $sourcePath -Destination $configPath -Force
Write-Host "Switched Codex profile to: $Profile"

Save it as:

%USERPROFILE%\.codex\switch-codex-profile.ps1

Then run:

powershell.exe -NoProfile -ExecutionPolicy Bypass `
  -File "$env:USERPROFILE\.codex\switch-codex-profile.ps1" official

powershell.exe -NoProfile -ExecutionPolicy Bypass `
  -File "$env:USERPROFILE\.codex\switch-codex-profile.ps1" custom-api

Optional double-click wrappers

For a manual workflow, small .cmd wrappers are enough. They avoid typing the PowerShell command each time.

@echo off
powershell.exe -NoProfile -ExecutionPolicy Bypass ^
  -File "%USERPROFILE%\.codex\switch-codex-profile.ps1" official
pause

@echo off
powershell.exe -NoProfile -ExecutionPolicy Bypass ^
  -File "%USERPROFILE%\.codex\switch-codex-profile.ps1" custom-api
pause

After switching, restart Codex or open a new session so the app reads the new config.toml.

What this does not do

This is not a multi-account sync system. It does not merge remote server history, transfer billing state, or change authentication tokens. It only swaps the local provider configuration file used by Codex.

The important safety rule is to keep one shared CODEX_HOME. If each profile launches Codex with a different home directory, then sessions will naturally split across those directories.

Troubleshooting missing history

If the sidebar appears to lose old conversations, first check whether the real session files still exist:

Get-ChildItem -Recurse -File "$env:USERPROFILE\.codex\sessions" |
  Measure-Object

If there are many session files but only a few entries in session_index.jsonl, the problem is likely the local sidebar index rather than deleted conversations. Rebuilding the index from the session files restores the visible list while keeping the original conversations intact.

The lesson is simple: profile switching should be scoped to config.toml. Conversation storage should remain shared, boring, and backed up.




Enjoy Reading This Article?

Here are some more articles you might like to read next: