paperclipai/paperclip · error · CodexQuotaAuthError
chatgpt wham api returned ${resp.status}
Error message
chatgpt wham api returned ${resp.status} What it means
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.
Source
Thrown at packages/adapters/codex-local/src/server/quota.ts:295
}
export async function fetchCodexQuota(
token: string,
accountId: string | null,
): Promise<QuotaWindow[]> {
const headers: Record<string, string> = {
Authorization: `Bearer ${token}`,
};
if (accountId) headers["ChatGPT-Account-Id"] = accountId;
const resp = await fetchWithTimeout("https://chatgpt.com/backend-api/wham/usage", { headers });
if (!resp.ok) {
const message = `chatgpt wham api returned ${resp.status}`;
const responseText = await readResponseTextPrefix(resp);
const authFailure = classifyCodexAuthRefreshFailure({
errorMessage: [message, responseText].filter(Boolean).join("\n"),
});
if (authFailure) throw new CodexQuotaAuthError(message, authFailure);
throw new Error(message);
}
const body = (await resp.json()) as WhamUsageResponse;
const windows: QuotaWindow[] = [];
const rateLimit = body.rate_limit;
if (rateLimit?.primary_window != null) {
const w = rateLimit.primary_window;
windows.push({
label: "5h limit",
usedPercent: normalizeCodexUsedPercent(w.used_percent),
resetsAt:
typeof w.reset_at === "number"
? unixSecondsToIso(w.reset_at)
: (w.reset_at ?? null),
valueLabel: null,
detail: null,
});View on GitHub (pinned to 67001ec6eb)
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).
Defensive patterns
Strategy: try-catch
Type guard
function isCodexQuotaAuthError(e: unknown): e is CodexQuotaAuthError {
return e instanceof Error && /chatgpt wham api returned/.test(e.message) && Object.prototype.hasOwnProperty.call(e, "authFailure");
} Try / catch
try {
const windows = await fetchCodexQuota(token, accountId);
} catch (e) {
if (e instanceof CodexQuotaAuthError) {
// token expired/revoked: re-authenticate then retry once
await refreshCodexAuth();
return fetchCodexQuota(newToken, accountId);
}
if (e instanceof Error && /chatgpt wham api returned 429|5\d\d/.test(e.message)) {
// transient: back off and retry
await sleep(backoffMs); backoffMs *= 2;
continue;
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Request failed: ${response.status}
- anthropic usage api returned ${resp.status}
- Choose either --rpc-only or --wham-only, not both.
- no Codex credentials provisioned for managed home "${input.e
- no Codex credentials provisioned for managed home "${input.e
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/56f2b013f739da59.
Report an issue: GitHub.