Limits adapter

ChatGPT (Codex) Subscription Quota

Opt-in ChatGPT quota gauges stored in the OS keychain and shown with local Codex usage.

ChatGPT (Codex) Subscription Quota

tokenuse can optionally show ChatGPT (Codex) subscription-quota gauges (5-hour / 7-day / credits balance) next to your local Codex CLI spend. It is opt-in, user-triggered, and gated behind the quota-sync Cargo feature (on by default).

Status: implemented (limits-only adapter, no session ingestion).

The flow mirrors the Claude.ai subscription adapter — see claude-subscription.md for the full design rationale. Codex differs in three ways:

  1. Auth is two-step. ChatGPT expects a session cookie (__Secure-next-auth.session-token), which you exchange for a short-lived bearer token before calling the usage endpoint.
  2. Endpoint shape. Quotas come from /backend-api/wham/usage with Authorization: Bearer <accessToken>, not from a per-organization usage path.
  3. LimitSnapshot.tool is tagged "codex" so the gauges appear inside the existing Codex section.

How it works

flowchart TD A["session-token cookie in OS keychain"] --> B["Config page Sync action"] B --> C["GET chatgpt.com/api/auth/session<br/>Cookie: __Secure-next-auth.session-token"] C --> D["accessToken"] D --> E["GET chatgpt.com/backend-api/wham/usage<br/>Authorization: Bearer accessToken"] E --> F["limits/codex_subscription.json"] F --> G["LimitSnapshot rows tagged tool='codex'"]

Service dev.tokenuse, account codex_subscription.session.

  1. Open https://chatgpt.com in your browser and log in.
  2. Open dev tools → Application/Storage → Cookies → https://chatgpt.com.
  3. Locate the session cookies. NextAuth splits session JWTs larger than ~4 KB across .0 / .1 shards; both must be sent under their original names — concatenation does not work.

TUI (recommended). Press c from the dashboard, scroll to ChatGPT (Codex) subscription quota and press Enter. The cookie modal exposes three fields — Tab/Shift+Tab move between them, Cmd/Ctrl+V pastes via bracketed paste:

  • __Secure-next-auth.session-token.0 — paste the value of the first shard (~3–4 KB).
  • __Secure-next-auth.session-token.1 — paste the value of the second shard (~200 B).
  • Additional cookies (optional, multi-line) — paste extra cookies (e.g. cf_clearance=…; __Host-next-auth.csrf-token=…) if Cloudflare or the two shards alone aren’t enough.

When you confirm Save & sync, the TUI composes the Cookie: header (__Secure-next-auth.session-token.0=<a>; __Secure-next-auth.session-token.1=<b>[; <extras>]), writes it to the OS keychain, and runs the sync on a worker thread. The two other modal actions are Sync with stored cookie (reuses the stored value) and Clear stored cookie.

Desktop app. Same Subscription cookie modal — see docs/guides/desktop-usage.md. Underneath: set_codex_session_cookie / clear_codex_session_cookie Tauri commands.

CLI (scripted setup). The flag accepts either form: a bare unsharded token value, or a raw Cookie: request header (any string containing =, optionally prefixed with Cookie:).

# Unsharded (rare — when chatgpt.com sets a single __Secure-next-auth.session-token cookie):
tokenuse --set-codex-cookie '<bare-token-value>'

# Sharded — copy from dev tools → Network → any /api/auth/session request → Headers → Cookie:
tokenuse --set-codex-cookie '__Secure-next-auth.session-token.0=<a>; __Secure-next-auth.session-token.1=<b>'

# Clear:
tokenuse --clear-codex-cookie

Sidecar format

{
  "observed_at": "2026-05-11T12:00:00Z",
  "source": "https://chatgpt.com/backend-api/wham/usage",
  "usage": {
    "plan_type": "plus",
    "rate_limit": {
      "limit_reached": false,
      "primary_window":   { "used_percent": 33.0, "limit_window_seconds": 18000,  "reset_after_seconds": 7200 },
      "secondary_window": { "used_percent": 12.0, "limit_window_seconds": 604800, "reset_after_seconds": 432000 }
    },
    "credits": {
      "has_credits": true,
      "unlimited": false,
      "balance": "45.25"
    },
    "spend_control": { "reached": false }
  }
}

reset_at (epoch seconds) is used when present; otherwise reset_after_seconds is added to “now” at parse time. balance is accepted as either a string or a number.

LimitSnapshot rowSource field
primary (5-Hour Limit)rate_limit.primary_windowwindow_minutes defaults to 300 when limit_window_seconds is missing
secondary (7-Day Limit)rate_limit.secondary_windowwindow_minutes defaults to 10 080
extra_usagecredits + spend_control

Errors

The error mapping matches Claude (401 → re-auth prompt, 403/HTML → Cloudflare, 429 → rate limited). A missing accessToken in the auth response also triggers the re-auth status.

Privacy posture

Same as the Claude adapter — cookie in keychain only, no telemetry, no background polling, disabled when quota-sync is off. OpenAI’s Terms of Service govern your use of the underlying endpoints; this feature accesses your own ChatGPT account using your own credentials.