{"record":{"id":"56f2b013f739da59","repo":"paperclipai/paperclip","slug":"chatgpt-wham-api-returned-resp-status","errorCode":null,"errorMessage":"chatgpt wham api returned ${resp.status}","messagePattern":"chatgpt wham api returned (.+?)","errorType":"exception","errorClass":"CodexQuotaAuthError","httpStatus":null,"severity":"error","filePath":"packages/adapters/codex-local/src/server/quota.ts","lineNumber":295,"sourceCode":"}\n\nexport async function fetchCodexQuota(\n  token: string,\n  accountId: string | null,\n): Promise<QuotaWindow[]> {\n  const headers: Record<string, string> = {\n    Authorization: `Bearer ${token}`,\n  };\n  if (accountId) headers[\"ChatGPT-Account-Id\"] = accountId;\n\n  const resp = await fetchWithTimeout(\"https://chatgpt.com/backend-api/wham/usage\", { headers });\n  if (!resp.ok) {\n    const message = `chatgpt wham api returned ${resp.status}`;\n    const responseText = await readResponseTextPrefix(resp);\n    const authFailure = classifyCodexAuthRefreshFailure({\n      errorMessage: [message, responseText].filter(Boolean).join(\"\\n\"),\n    });\n    if (authFailure) throw new CodexQuotaAuthError(message, authFailure);\n    throw new Error(message);\n  }\n  const body = (await resp.json()) as WhamUsageResponse;\n  const windows: QuotaWindow[] = [];\n\n  const rateLimit = body.rate_limit;\n  if (rateLimit?.primary_window != null) {\n    const w = rateLimit.primary_window;\n    windows.push({\n      label: \"5h limit\",\n      usedPercent: normalizeCodexUsedPercent(w.used_percent),\n      resetsAt:\n        typeof w.reset_at === \"number\"\n          ? unixSecondsToIso(w.reset_at)\n          : (w.reset_at ?? null),\n      valueLabel: null,\n      detail: null,\n    });","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/adapters/codex-local/src/server/quota.ts#L277-L313","documentation":"Thrown by fetchCodexQuota (quota.ts) when the ChatGPT WHAM usage endpoint (https://chatgpt.com/backend-api/wham/usage) returns a non-2xx status. Before throwing the generic Error it classifies the response text; if the body looks like a Codex auth-refresh failure it throws a CodexQuotaAuthError instead, so callers can distinguish auth problems from transient API errors.","triggerScenarios":"fetchWithTimeout resolves but resp.ok is false. Common statuses: 401/403 (token expired or revoked — may surface as CodexQuotaAuthError), 429 (rate limited), 5xx (ChatGPT backend outage), or 404 (account/endpoint changed).","commonSituations":"Codex subscription access token expired and refresh failed; ChatGPT backend incident; the account_id header is wrong/missing for a multi-account token; rate-limited by frequent quota polling.","solutions":["If caught as CodexQuotaAuthError, re-authenticate (re-run codex login) and retry.","For 429, back off quota polling and retry after the rate-limit window.","For 5xx, retry with exponential backoff; the WHAM API is eventually consistent.","Verify the token and accountId passed to fetchCodexQuota come from a current auth.json (decodeJwtPayload for account_id)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isCodexQuotaAuthError(e: unknown): e is CodexQuotaAuthError {\n  return e instanceof Error && /chatgpt wham api returned/.test(e.message) && Object.prototype.hasOwnProperty.call(e, \"authFailure\");\n}","tryCatchPattern":"try {\n  const windows = await fetchCodexQuota(token, accountId);\n} catch (e) {\n  if (e instanceof CodexQuotaAuthError) {\n    // token expired/revoked: re-authenticate then retry once\n    await refreshCodexAuth();\n    return fetchCodexQuota(newToken, accountId);\n  }\n  if (e instanceof Error && /chatgpt wham api returned 429|5\\d\\d/.test(e.message)) {\n    // transient: back off and retry\n    await sleep(backoffMs); backoffMs *= 2;\n    continue;\n  }\n  throw e;\n}","preventionTips":["Decode the token's exp and refresh proactively before calling fetchCodexQuota.","Rate-limit quota polling to avoid 429.","Pass the correct accountId header for multi-account tokens."],"tags":["codex","quota","network","auth","chatgpt-api"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}