# 02 — Codex usage API (https://jackin.tailrocks.com/research/product/desktop/usage-provider-apis/02-codex-api/)



## Summary [#summary]

Codex exposes quota windows and reset metadata through authenticated usage surfaces; presentation labels and projections remain client-derived.

## Question and scope [#question-and-scope]

(1) Which official OpenAI/ChatGPT endpoints expose Codex rate-limit/usage windows (5h/weekly, percent used, resets) for ChatGPT plans (Plus/Pro, "Pro 20x"-style)? (2) Does an official endpoint expose "Codex Spark" windows and with what fields? (3) Does an official endpoint expose "Limit Reset Credits" (count + per-credit reset/expiry data)? (4) What auth do these endpoints need (ChatGPT OAuth token, scopes, on-disk location — location/type only)? (5) Which CodexBar-style UI elements have NO direct API source and must be computed client-side (deficit %, "Runs out in X")?

This chapter informs jackin-desktop.

## Method [#method]

**Method:** web + reference read of github.com/openai/codex (shallow clone, commit `74e9d7efc416b1cb9f3ad10c70a91afbcb6d6a29`, 2026-07-23).
&#x2A;*Verification cutoff:** 2026-07-24 Clean-room honored: CodexBar/OpenUsage source NOT read; CodexBar README prose cited as concept evidence only. No embedded instructions were encountered in any fetched web or repo content.

## Findings [#findings]

### Q1 — Endpoints exposing Codex rate-limit/usage windows [#q1--endpoints-exposing-codex-rate-limitusage-windows]

* Primary usage endpoint: `GET {base}/wham/usage` when the base URL contains `/backend-api` (ChatGPT path style), else `GET {base}/api/codex/usage` (Codex API path style) — `codex-rs/backend-client/src/client/rate_limit_resets.rs` lines 82–83, [https://github.com/openai/codex](https://github.com/openai/codex) @ `74e9d7e` (confidence: HIGH)
* Default ChatGPT base URL is `https://chatgpt.com/backend-api/` (config key `chatgpt_base_url`), so the effective first-party URL is `https://chatgpt.com/backend-api/wham/usage` — `codex-rs/core/src/config/mod.rs` line 4064–4066; path-style switch on `/backend-api` in `codex-rs/backend-client/src/client.rs` lines 118–122 (confidence: HIGH)
* Response payload (`RateLimitStatusPayload`): `plan_type`, `rate_limit { allowed, limit_reached, primary_window, secondary_window }`, `credits`, `spend_control`, `additional_rate_limits[]`, `rate_limit_reached_type` — `codex-rs/codex-backend-openapi-models/src/models/rate_limit_status_payload.rs` (OpenAPI-generated client models for "codex-backend") (confidence: HIGH)
* Window fields (`RateLimitWindowSnapshot`): `used_percent`, `limit_window_seconds`, `reset_after_seconds`, `reset_at` (unix seconds) — `codex-rs/codex-backend-openapi-models/src/models/rate_limit_window_snapshot.rs` (confidence: HIGH)
* 5h/weekly semantics are NOT flagged in the payload; the client derives labels from window duration: \~300 min → "5h", \~10 080 min → "weekly" (also daily/monthly/annual buckets) — `codex-rs/tui/src/chatwidget/rate_limits.rs` lines 77–102 (confidence: HIGH)
* Official docs confirm the model: "The usage limits for local messages and cloud chats share a five-hour window. Additional weekly limits may apply"; tiers listed include Plus, Pro 5x, Pro 20x; in-session check via `/status`; web dashboard at `https://chatgpt.com/codex/settings/usage` — [https://developers.openai.com/codex/pricing](https://developers.openai.com/codex/pricing) (308-redirects to [https://learn.chatgpt.com/docs/pricing.md](https://learn.chatgpt.com/docs/pricing.md)), fetched 2026-07-24; dashboard URL also hardcoded in `codex-rs/tui/src/status/card.rs` line 56 (confidence: HIGH)
* Second delivery channel (per-turn, not poll): rate-limit snapshots ride on turn responses from `https://chatgpt.com/backend-api/codex/responses` as response headers: `x-codex-primary-used-percent`, `x-codex-primary-window-minutes`, `x-codex-primary-reset-at`, `x-codex-secondary-*` (same trio), per-limit families `x-<limit-id>-*` plus `x-<limit-id>-limit-name`, `x-codex-credits-{has-credits,unlimited,balance}`, `x-codex-rate-limit-reached-type`, `x-codex-promo-message`; over websocket, event type `codex.rate_limits` carries `{plan_type, rate_limits{primary,secondary}, credits, metered_limit_name}` — `codex-rs/codex-api/src/rate_limits.rs` (whole file), `codex-rs/codex-api/src/endpoint/responses_websocket.rs` lines 748–749, responses URL in `codex-rs/http-client/src/chatgpt_cloudflare_cookies.rs` line 130 (confidence: HIGH)
* First-party client plumbing (not HTTP, JSON-RPC to the local app-server): `account/rateLimits/read`, `account/usage/read`, notification `account/rateLimits/updated` — `codex-rs/app-server-protocol/src/protocol/common.rs` lines 1053–1070, 1706 (confidence: HIGH)
* `plan_type` enum values: `guest, free, go, plus, pro, prolite, free_workspace, team, self_serve_business_usage_based, business, enterprise_cbp_usage_based, education, quorum, k12, enterprise, edu, unknown` — `codex-rs/codex-backend-openapi-models/src/models/rate_limit_status_payload.rs` `PlanType` enum (confidence: HIGH). The enum distinguishes `pro` and `prolite` but carries no multiplier label; first-party Codex renders "Pro" / "Pro Lite", so `pro`↔"Pro 20x" and `prolite`↔"Pro 5x" are marketing-level inferences ([chapter 08](/research/product/desktop/usage-provider-apis/08-codex-followups/)).
* No public REST usage API is documented on developers.openai.com; docs point only to `/status` and the web dashboard. The `/backend-api/wham/*` endpoints are known solely from the first-party client source (confidence: HIGH for the absence in docs)

### Q2 — Codex Spark windows [#q2--codex-spark-windows]

* Spark = GPT-5.3-Codex-Spark, research preview, ChatGPT Pro only: "Because it runs on specialized low-latency hardware, usage is governed by a separate usage limit that may adjust based on demand" — [https://developers.openai.com/codex/pricing](https://developers.openai.com/codex/pricing) (fetched 2026-07-24); announcement page [https://openai.com/index/introducing-gpt-5-3-codex-spark/](https://openai.com/index/introducing-gpt-5-3-codex-spark/) exists but returned HTTP 403 to non-browser fetch (confidence: HIGH for the pricing-doc quote)
* No Spark-specific endpoint exists in the first-party client. Spark limits arrive inside the same `/wham/usage` payload via `additional_rate_limits[]`, each entry `{ limit_name, metered_feature, rate_limit { allowed, limit_reached, primary_window, secondary_window } }` — i.e. the identical window shape (`used_percent`, `limit_window_seconds`, `reset_after_seconds`, `reset_at`) per named limit — `codex-rs/codex-backend-openapi-models/src/models/additional_rate_limit_details.rs`; mapping in `codex-rs/backend-client/src/client.rs` lines 526–557 (default limit keyed `limit_id="codex"`, additional limits keyed by `metered_feature` with `limit_name` carried for display) (confidence: HIGH)
* The TUI renders a named additional limit as `"<name> limit"` with its own 5h (300 min) primary and weekly (10 080 min) secondary windows; the repo's own fixture uses `limit_name: "spark"` with exactly those two windows and asserts "spark limit" / "5h limit" / "Weekly limit" output — `codex-rs/tui/src/status/tests.rs` lines 1725–1775 (confidence: HIGH for the mechanism; MED for `"spark"` being the exact live server string, since it appears only in a test fixture)
* On the streaming channel, Spark-family limits would surface as an `x-<limit-id>-*` header family and/or `codex.rate_limits` websocket events keyed by `metered_limit_name` — `codex-rs/codex-api/src/rate_limits.rs` `parse_all_rate_limits` / `parse_rate_limit_event` (confidence: HIGH for mechanism)
* Contradiction in the wild: users report Spark usage draining or being blocked by the general Codex quota despite the separate meter — [https://github.com/openai/codex/issues/20122](https://github.com/openai/codex/issues/20122), [https://github.com/openai/codex/issues/23150](https://github.com/openai/codex/issues/23150) (confidence: MED — issue reports, not verified behavior)

### Q3 — Limit Reset Credits [#q3--limit-reset-credits]

* Yes — three first-party endpoints (same path-style switch as usage):
  * `GET {base}/wham/usage` also returns top-level `rate_limit_reset_credits { available_count }` alongside the rate-limit payload — `codex-rs/backend-client/src/types.rs` lines 22–25, 50–55 (confidence: HIGH)
  * `GET {base}/wham/rate-limit-reset-credits` (Codex style: `{base}/api/codex/rate-limit-reset-credits`) returns `{ credits: [ { id, reset_type, status, granted_at, expires_at, title, description } ], available_count }` — `codex-rs/backend-client/src/client/rate_limit_resets.rs` lines 38–43, 88–97; `codex-rs/backend-client/src/types.rs` lines 27–42 (confidence: HIGH)
  * `POST {base}/wham/rate-limit-reset-credits/consume` with JSON body `{ redeem_request_id, credit_id? }` (redeem\_request\_id = caller-generated idempotency key, UUID recommended) returns `{ code: reset | nothing_to_reset | no_credit | already_redeemed, windows_reset }` — `codex-rs/backend-client/src/client/rate_limit_resets.rs` lines 15–20, 45–113; `codex-rs/backend-client/src/types.rs` lines 84–98 (confidence: HIGH)
* Per-credit fields carry timestamps, not countdowns: `granted_at` and nullable `expires_at` (unix seconds in the app-server projection; "null if it does not expire"); credit `status` enum `available | redeeming | redeemed | unknown`; any "expires in X" countdown is client-computed — `codex-rs/app-server-protocol/src/protocol/v2/account.rs` lines 302–353 (confidence: HIGH)
* App-server surface at HEAD: `account/rateLimits/read` → `rateLimitResetCredits { availableCount, credits[]? }` (detail rows fetched from the list endpoint with a 5 s timeout and merged; `null` credits = summary only; "The backend may cap this list"), and `account/rateLimitResetCredit/consume { idempotencyKey, creditId? }` — `codex-rs/app-server/src/request_processors/account_processor/rate_limit_resets.rs` lines 1–30, `codex-rs/app-server-protocol/src/protocol/common.rs` lines 1059–1063 (confidence: HIGH)
* Lineage: PR "feat(app-server): expose rate-limit reset credits" added the summary+consume surface — [https://github.com/openai/codex/pull/28143](https://github.com/openai/codex/pull/28143); issue asking for per-credit detail exposure states that as of `rust-v0.142.0` only `availableCount` + consume existed and calls `GET /backend-api/wham/rate-limit-reset-credits` a "private backend endpoint" that third-party tools were already hitting — [https://github.com/openai/codex/issues/29618](https://github.com/openai/codex/issues/29618) (confidence: HIGH that these are the first-party statements)
* Distinct concept, do not conflate: `credits` in the usage payload (`CreditStatusDetails { has_credits, unlimited, balance, approx_local_messages, approx_cloud_messages }`) is the pay-per-use credit balance, not reset credits — `codex-rs/codex-backend-openapi-models/src/models/credit_status_details.rs`; the pricing doc separately describes "banked rate-limit reset" grants (e.g. referral rewards) — [https://developers.openai.com/codex/pricing](https://developers.openai.com/codex/pricing) (confidence: HIGH)

### Q4 — Authentication [#q4--authentication]

* All `/wham/*` calls use the ChatGPT OAuth access token as `Authorization: Bearer <JWT>`, plus a `ChatGPT-Account-Id` header when a workspace/account id is set — `codex-rs/backend-client/src/client.rs` lines 226–231 (header injection) and line 1139–1142 (test asserting Bearer + account-id header) (confidence: HIGH)
* Token location on disk (location/type only): `$CODEX_HOME/auth.json` (default `~/.codex/auth.json`), or the OS credential store depending on `cli_auth_credentials_store = file | keyring | auto`; structure `AuthDotJson { auth_mode, OPENAI_API_KEY, tokens { id_token, access_token (JWT), refresh_token, account_id }, last_refresh, agent_identity, personal_access_token, bedrock_api_key }` — `codex-rs/login/src/auth/storage.rs` lines 38–61, 150–151; `codex-rs/login/src/token_data.rs` lines 11–25; official doc: "Codex caches login details locally in a plaintext file at `~/.codex/auth.json` or in your OS-specific credential store" — [https://developers.openai.com/codex/auth](https://developers.openai.com/codex/auth) (redirects to [https://learn.chatgpt.com/docs/auth](https://learn.chatgpt.com/docs/auth)), fetched 2026-07-24 (confidence: HIGH)
* OAuth flow: `codex login` browser PKCE flow (S256) against `auth.openai.com`; client id `app_EMoamEEZ73f0CkXaXp7hrann` (env override `CODEX_APP_SERVER_LOGIN_CLIENT_ID`); scopes requested: `openid profile email offline_access api.connectors.read api.connectors.invoke`; refresh at `https://auth.openai.com/oauth/token`, revoke at `https://auth.openai.com/oauth/revoke`; tokens auto-refresh during use — `codex-rs/login/src/auth/manager.rs` lines 191–195, 1448–1454; `codex-rs/login/src/server.rs` lines 555–580 (confidence: HIGH)
* API-key login exists as an alternative (`codex login --with-api-key`), but the ChatGPT-plan quota endpoints above are the ChatGPT-token path — [https://learn.chatgpt.com/docs/auth](https://learn.chatgpt.com/docs/auth) (confidence: HIGH)

### Q5 — UI elements with no direct API source (client-side computation required) [#q5--ui-elements-with-no-direct-api-source-client-side-computation-required]

* Neither `/wham/usage` payload nor the `x-codex-*` header family carries burn rate, pace, deficit, projection, or any historical series: the complete per-window surface is `used_percent`, `limit_window_seconds`, `reset_after_seconds`, `reset_at` — `codex-rs/codex-backend-openapi-models/src/models/rate_limit_window_snapshot.rs`, `codex-rs/codex-api/src/rate_limits.rs` (confidence: HIGH — exhaustive absence in first-party models)
* Deficit % (used % ahead of elapsed-window %): must be computed client-side; elapsed fraction is derivable from `(limit_window_seconds − reset_after_seconds) / limit_window_seconds`, so a single snapshot suffices arithmetically, but no API field provides it (confidence: HIGH for the absence; derivation is arithmetic on HIGH-confidence fields)
* "Runs out in X" projection: no API field; requires client-side extrapolation from successive `used_percent` samples (the API is a point-in-time snapshot with no rate) (confidence: HIGH for the absence)
* No usage-history/time-series endpoint exists anywhere in the first-party client (`backend-client` has no such route) — [https://github.com/openai/codex](https://github.com/openai/codex) @ `74e9d7e` (confidence: HIGH for absence)
* Per-credit "expires in" countdown: API gives `expires_at` timestamps only; countdown rendering is client-side (confidence: HIGH)
* CodexBar README prose corroborates provider windows and reset countdowns, but its additional enrichments are not sourced from the token-authenticated usage API — [https://github.com/steipete/codexbar](https://github.com/steipete/codexbar) README, fetched 2026-07-24 (confidence: MED — README prose only)

### Dead ends and contradictions [#dead-ends-and-contradictions]

* `help.openai.com` article 11369540 ("Using Codex with your ChatGPT plan") and `openai.com/index/introducing-gpt-5-3-codex-spark/` both return HTTP 403 to non-browser fetch — content not directly verified; the developers.openai.com pricing doc (which 308-redirects to `learn.chatgpt.com/docs/pricing.md`) was used as the primary doc instead.
* `developers.openai.com/codex/*` pages permanently redirect to `learn.chatgpt.com/docs/*` — the canonical docs host has moved; cite either, they are the same content.
* No officially documented public REST API for Codex ChatGPT-plan usage exists; the docs only offer `/status` and the web dashboard. Every endpoint above is established from first-party client source, and openai/codex issue #29618 explicitly characterizes the reset-credits list endpoint as "private backend" — official support status for third-party callers is not granted anywhere found.
* Spark contradiction: docs state a separate Spark usage limit, but issues #20122 and #23150 report Spark being blocked by / draining the general Codex quota. Both cannot be simultaneously true as steady-state behavior; unresolved upstream.
* `"spark"` as `limit_name` appears only in a TUI test fixture, not in any request/response constant — ruled out as a confirmed server string.
* `plan_type` carries no multiplier labels ("5x"/"20x" absent from the whole openai/codex repo) — a multiplier display requires an out-of-band `pro`/`prolite`→marketing-name mapping ([chapter 08](/research/product/desktop/usage-provider-apis/08-codex-followups/)).
* The `x-codex-*` header / `codex.rate_limits` websocket channel only fires on actual turn requests to `/backend-api/codex/responses` — ruled out as a passive polling source for a menu-bar app (polling `/wham/usage` is the passive path).
* openai/codex `docs/` directory is now stub files pointing at developers.openai.com — no in-repo usage-limits documentation to cite.

## Implications for jackin❯ [#implications-for-jackin]

Use first-party Codex client models for bounded windows and reset credits, while treating marketing labels, projections, and private backend stability as derived or uncertain.

## Limitations and unknowns [#limitations-and-unknowns]

* Exact live `limit_name` / `metered_feature` strings the server emits for Spark (and any other additional limits) in `/wham/usage` responses — needs an operator-authenticated session (real ChatGPT Pro token or browser network inspection on chatgpt.com/codex/settings/usage) to observe.
* Whether `/wham/usage` and the reset-credits endpoints require the `ChatGPT-Account-Id` header for personal (non-workspace) accounts, or only when a workspace is selected — needs live traffic inspection.
* Whether/at what size the backend caps the `credits[]` list from `GET /wham/rate-limit-reset-credits` (protocol doc says "may cap") — needs live observation.
* Official 30-day expiry for banked resets: third-party posts claim 30 days; the authoritative per-credit source is the `expires_at` field; the help-center article that would confirm the policy number returned 403 — an operator browser session could verify.
* Whether OpenAI intends to bless `/backend-api/wham/*` for third-party clients (issue #29618 asks; no answer found as of 2026-07-24).

## Sources [#sources]

* [OpenAI Codex source](https://github.com/openai/codex)
* [Codex pricing](https://developers.openai.com/codex/pricing)

## Related work [#related-work]

* [Provider API coverage](/research/product/desktop/usage-provider-apis/)
* [Codex follow-ups](/research/product/desktop/usage-provider-apis/08-codex-followups/)
