Location: ~/.cligate/accounts.json
{
"accounts": [
{
"email": "user@gmail.com",
"accountId": "d41e9636-16d8-42be-91da-7ea8773bfb7e",
"planType": "plus",
"accessToken": "eyJhbGciOiJSUzI1NiIs...",
"refreshToken": "rt_WpTMn1...",
"idToken": "eyJhbGciOiJSUzI1NiIs...",
"expiresAt": 1770886178000,
"addedAt": "2026-02-13T04:00:00.000Z",
"lastUsed": "2026-02-13T04:30:00.000Z",
"quota": {
"usage": {...},
"account": {...},
"lastChecked": "2026-02-14T10:00:00.000Z"
}
}
],
"activeAccount": "user@gmail.com",
"version": 1
}Location: ~/.cligate/accounts/<email>/auth.json
{
"auth_mode": "chatgpt",
"OPENAI_API_KEY": null,
"tokens": {
"id_token": "...",
"access_token": "...",
"refresh_token": "...",
"account_id": "..."
},
"last_refresh": "2026-02-14T10:00:00.000Z"
}curl -X POST http://localhost:8081/accounts/add
# Returns OAuth URL to open in browsercurl -X POST http://localhost:8081/accounts/import
# Imports from ~/.codex/auth.jsoncurl http://localhost:8081/accounts
# Response
{
"accounts": [
{
"email": "user@gmail.com",
"accountId": "...",
"planType": "plus",
"addedAt": "...",
"lastUsed": "...",
"isActive": true,
"tokenExpired": false,
"quota": {...}
}
],
"activeAccount": "user@gmail.com",
"total": 1
}curl -X POST http://localhost:8081/accounts/switch \
-H "Content-Type: application/json" \
-d '{"email":"other@gmail.com"}'Switching:
- Updates
activeAccountinaccounts.json - Updates auth file for the account
- Next API calls use new account's credentials
curl -X DELETE http://localhost:8081/accounts/user@gmail.comRemoves:
- Account from registry
- Per-account token directory
# Active account
curl -X POST http://localhost:8081/accounts/refresh
# Specific account
curl -X POST http://localhost:8081/accounts/user@gmail.com/refresh
# All accounts
curl -X POST http://localhost:8081/accounts/refresh/all- Access tokens expire in ~1 hour (3600 seconds)
- Refresh tokens are long-lived (weeks/months)
- Background refresh every 55 minutes
- Startup refresh 2 seconds after server start
- Proactive refresh 5 minutes before expiry
Before each API call:
- Check if token is expired or expiring within 5 minutes
- If yes, refresh using refresh token
- Use new access token for the call
curl http://localhost:8081/accounts/quota
# Response
{
"success": true,
"email": "user@gmail.com",
"quota": {
"usage": {
"totalTokenUsage": 15,
"limit": 100,
"remaining": 85,
"percentage": 15,
"resetAt": "..."
},
"account": {...}
},
"cached": false
}- The Accounts table displays remaining quota as a percentage.
- Remaining percentage is normalized to
0-100to avoid broken UI values. - If
limitReached=trueorallowed=false, UI shows quota as exhausted even when percentage data is missing. - If usage data is unavailable, UI shows
-instead of rendering a broken bar. - Reset window is shown using
usage.resetAt(with fallback tousage.raw.rate_limit.primary_window.reset_at). - UI also shows a relative countdown (e.g.
Resets in 6d 13h) when reset data is available.
curl http://localhost:8081/accounts/quota/allOn server startup:
ensureAccountsPersist()loads accounts- Restores active account's auth
- Starts auto-refresh timer
- Tokens stored locally in
~/.cligate/ - Directory permissions: user read/write only
- Never logged or exposed in API responses
- Per-account isolation via separate directories