Codex / ChatGPT auth
Setup
codex login
That writes ~/.codex/auth.json. The same Codex credentials power chat plus the bearer-protected account usage and reset-credit redemption endpoints.
Configuration
| Setting | Env | Flag | Default |
|---|---|---|---|
| Codex home | CODEX_HOME | --codex-home | ~/.codex |
| Auth file | CODEX_AUTH_PATH | --auth-path | <codex-home>/auth.json |
On each request we load auth.json through a token source that:
- Uses the access token while it is still valid (with a short expiry slack).
- Silently refreshes via
https://auth.openai.com/oauth/tokenwhen near expiry (or after a ChatGPT websocket401/403). - Persists the rotated
access_token/refresh_token/expires_at/last_refreshback toauth.json.
If refresh fails with invalid_grant / revoked refresh token, run codex login again and re-seed the gateway secret.
Which login is which
label is the stable pool slot (primary, secondary). It is not the ChatGPT account. Set account_name on that CODEX_CLIENTS entry, or write account.json next to the auth file:
{"name":"ada@example.com"}
An explicit account_name wins over the file. GET /v1/accounts returns both fields and does not return tokens or auth paths. The same name is included on GET /v1/accounts/usage.
Docker
Compose mounts ${HOME}/.codex → /home/codex/.codex read-only and sets CODEX_HOME=/home/codex/.codex. Prefer a writable mount in long-lived deployments so refresh can persist.
Kubernetes
Secret key CODEX_AUTH_JSON is seeded by an init container into /home/codex/.codex/auth.json on a writable HOME volume (PVC in prod so rotated refresh tokens survive pod restarts). The init copies the secret when the live file is missing or the secret's last_refresh is newer.