ProductDesktopUsage provider APIs

02 — Codex usage API

Documents Codex quota endpoints, authentication, response fields, and client-derived presentation semantics.

Summary

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

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: web + reference read of github.com/openai/codex (shallow clone, commit 74e9d7efc416b1cb9f3ad10c70a91afbcb6d6a29, 2026-07-23). 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

Q1 — Endpoints exposing Codex rate-limit/usage 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 @ 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/usagecodex-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_typecodex-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/usagehttps://developers.openai.com/codex/pricing (308-redirects to 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/updatedcodex-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, unknowncodex-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).
  • 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

  • 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 (fetched 2026-07-24); announcement page 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_namecodex-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/23150 (confidence: MED — issue reports, not verified behavior)

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/readrateLimitResetCredits { 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; 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 (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 (confidence: HIGH)

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 (redirects to 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 (confidence: HIGH)

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_atcodex-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 @ 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 README, fetched 2026-07-24 (confidence: MED — README prose only)

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).
  • 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

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

  • 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

On this page