Skip to content

Instantly share code, notes, and snippets.

@monperrus
Last active July 18, 2026 07:02
Show Gist options
  • Select an option

  • Save monperrus/21dc7d85ea518dc2a66606006d356b5b to your computer and use it in GitHub Desktop.

Select an option

Save monperrus/21dc7d85ea518dc2a66606006d356b5b to your computer and use it in GitHub Desktop.
OpenAI Codex wham/usage API — Unofficial Reference

OpenAI Codex wham/usage API — Unofficial Reference

OpenAI Codex wham/usage API — Unofficial Reference

Disclaimer: This is an undocumented internal API used by the Codex CLI and related tools. It is not part of the OpenAI Platform API, carries no stability guarantee, and may change without notice.

Endpoint

GET https://chatgpt.com/backend-api/wham/usage

Authentication

Requires a ChatGPT OAuth access token from ~/.codex/auth.json (written by codex login).

Authorization: Bearer <access_token>
ChatGPT-Account-Id: <account_id>

account_id can be extracted from the https://api.openai.com/auth claim inside the JWT id_token, or read directly from tokens.account_id in auth.json.

Token refresh endpoint:

POST https://auth.openai.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&client_id=app_EMoamEEZ73f0CkXaXp7hrann&refresh_token=<token>

Response shape

{
  "user_id": "user-...",
  "account_id": "...",
  "email": "user@example.com",
  "plan_type": "team",          // "free" | "plus" | "pro" | "team" | "enterprise"

  "rate_limit": {
    "allowed": true,            // false when the account is blocked
    "limit_reached": false,
    "primary_window": {         // see Window shape below
      "used_percent": 9,
      "limit_window_seconds": 604800,
      "reset_after_seconds": 584279,
      "reset_at": 1784530149    // unix timestamp
    },
    "secondary_window": null    // null when plan has only one window
  },

  // Per-model caps (e.g. "Codex Spark 5-hour", "Codex Spark Weekly").
  // null when no model-specific limits apply.
  "additional_rate_limits": [
    {
      "id": "codex-spark",              // stable id
      "title": "Codex Spark 5-hour",
      "primary_window": { /* Window */ },
      "secondary_window": { /* Window */ }
    }
  ] | null,

  // Separate limit for the Codex code-review feature.
  "code_review_rate_limit": {
    "primary_window": { /* Window */ }
  } | null,

  "credits": {
    "has_credits": false,
    "unlimited": false,
    "overage_limit_reached": false,
    "balance": null,                    // string or null, e.g. "$4.20"
    "approx_local_messages": null,
    "approx_cloud_messages": null
  },

  "spend_control": {
    "reached": false,
    "individual_limit": null
  },

  // Banked rate-limit resets (see companion endpoint below).
  "rate_limit_reset_credits": {
    "available_count": 3
  },

  "rate_limit_reached_type": null,      // unknown enum; non-null when limit_reached
  "promo": null,
  "referral_beacon": null
}

Window object

{
  "used_percent": 9,               // 0–100; null when not tracked
  "limit_window_seconds": 604800,  // duration: 18000 = 5 h, 604800 = 7 d
  "reset_after_seconds": 584279,   // seconds until reset (from request time)
  "reset_at": 1784530149           // absolute unix timestamp of reset
}

Identifying the window type

Do not assume primary_window = 5-hour and secondary_window = weekly. Use limit_window_seconds to identify the type:

limit_window_seconds meaning
18000 5-hour rolling window
604800 7-day (weekly) window

Plan shape varies by subscription. For example:

  • Plus / Pro: primary_window = 5-hour (18000 s), secondary_window = weekly (604800 s)
  • Team: primary_window = weekly (604800 s), secondary_window = null

This means secondary_window may be null for plans that only enforce a weekly cap.

Caveat: a 7-day reset_at is not necessarily a fixed weekly schedule

Treat the window duration as the server's declared limit duration, not as proof of a calendar- or fixed-period reset. On one Team-plan account observed in July 2026, the 7-day window behaved as follows:

  • While used_percent was zero, reset_after_seconds stayed close to 604800 and reset_at advanced with every poll, always remaining about seven days ahead.
  • After usage resumed, reset_at became a nearly fixed timestamp.
  • used_percent subsequently dropped from 55% to 0%, and later from 61% to 0%, only hours apart—not one week apart.

The reason for this server-side behavior is unknown. used_percent, allowed, and limit_reached remain useful for the current enforcement state, but clients should not derive a fixed next weekly reset, send rollover alerts, or promise a seven-day usage budget solely from reset_at / reset_after_seconds. Keep local history if a longer-term usage view is needed.

Companion endpoint: banked resets

GET https://chatgpt.com/backend-api/wham/rate-limit-reset-credits
Authorization: Bearer <access_token>
ChatGPT-Account-Id: <account_id>

Returns the inventory of banked rate-limit resets (credits that can be used to unblock an exhausted window early). The same data appears as rate_limit_reset_credits.available_count in the main usage response.

Notes

  • The Codex CLI polls this endpoint roughly every 60 seconds during active sessions.
  • The endpoint is also used by codex app-server (JSON-RPC via account/rateLimits/read).
  • When allowed is false, the account is blocked regardless of window percentages.
  • used_percent can exceed 100 in edge cases; clamp to [0, 100] before display.

Prior art

These open-source projects reverse-engineered this endpoint:

  • steipete/CodexBar — macOS menu bar app; docs/codex.md is the most complete existing documentation
  • fberbert/codex-widget — Linux desktop widget; cleanest Python implementation of window-type detection
  • timm-u/pi-usage — pi coding agent extension; TypeScript interface definitions for the full response shape
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment