{"record":{"id":"661f3dcd2bad8e44","repo":"decolua/9router","slug":"claude-usage-oauth-endpoint-returned-oauthresp","errorCode":null,"errorMessage":"[Claude Usage] OAuth endpoint returned ${oauthResponse.status}, falling back to legacy","messagePattern":"\\[Claude Usage\\] OAuth endpoint returned (.+?), falling back to legacy","errorType":"console","errorClass":null,"httpStatus":null,"severity":"info","filePath":"open-sse/services/usage/claude.js","lineNumber":126,"sourceCode":"          const modelName = key.replace(\"seven_day_\", \"\");\n          quotas[`weekly ${modelName} (7d)`] = createQuotaObject(value);\n        }\n      }\n\n      return {\n        plan: \"Claude Code\",\n        extraUsage: data.extra_usage ?? null,\n        quotas,\n      };\n    }\n\n    // Cool down OAuth usage polling after a 429 (quota endpoint only)\n    if (oauthResponse.status === 429) {\n      oauthCooldown.set(accessToken, Date.now() + OAUTH_429_COOLDOWN_MS);\n    }\n\n    // Fallback: legacy settings + org usage endpoint\n    console.warn(`[Claude Usage] OAuth endpoint returned ${oauthResponse.status}, falling back to legacy`);\n    return await getClaudeUsageLegacy(accessToken, proxyOptions);\n  } catch (error) {\n    return { message: `Claude connected. Unable to fetch usage: ${error.message}` };\n  }\n}\n\n/**\n * Legacy Claude usage for API key / org admin users\n */\nasync function getClaudeUsageLegacy(accessToken, proxyOptions = null) {\n  try {\n    const settingsResponse = await proxyAwareFetch(CLAUDE_CONFIG.settingsUrl, {\n      method: \"GET\",\n      headers: {\n        \"Authorization\": `Bearer ${accessToken}`,\n        \"anthropic-version\": CLAUDE_CONFIG.apiVersion,\n      },\n    }, proxyOptions);","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/services/usage/claude.js#L108-L144","documentation":"fetchClaudeUsageRaw first calls Anthropic's OAuth usage endpoint; on any non-ok status (except the handled 429 cooldown path) it logs this warning and transparently falls back to the legacy settings + org usage endpoint. The value returned to the caller comes from the legacy path, so usage data is usually still available.","triggerScenarios":"The OAuth usage endpoint returned 400/401/403/404/5xx — expired OAuth token, account not eligible for the new usage API, region/plan without the endpoint, or transient upstream 5xx.","commonSituations":"OAuth access token expired or revoked (401); account on a plan that lacks the OAuth usage endpoint (404); Anthropic API change moved/renamed the endpoint; transient 500/503 during Anthropic incidents.","solutions":["No action needed if the legacy fallback returns data — this is a downgrade warning only","If fallback also fails, re-authenticate the Claude OAuth connection to get a fresh token","Confirm the connected account's plan supports the OAuth usage endpoint","Check Anthropic status/incidents if the status is 5xx","Verify proxy settings aren't altering the usage request"],"exampleFix":"// before: stale token keeps hitting the new endpoint with 401\n// after: re-run OAuth to refresh credentials, or shorten refresh interval\nawait refreshToken(connectionId); // before next fetchClaudeUsageRaw call","handlingStrategy":"fallback","validationCode":"const conn = await api.get(`/api/connections/${id}`);\nif (!conn || conn.tokenExpiresAt < Date.now()) await reauth(id); // refresh before usage fetch","typeGuard":"const hasUsageData = (u) => u && !u.message && typeof u === 'object';\nif (!hasUsageData(usage)) console.warn('usage unavailable:', usage?.message);","tryCatchPattern":"try {\n  const usage = await fetchClaudeUsageRaw(token);\n  // legacy fallback already applied internally; validate shape\n  if (usage?.message) console.warn(usage.message);\n  return usage;\n} catch (e) {\n  console.warn(`claude usage unavailable: ${e.message}`);\n  return null;\n}","preventionTips":["Keep OAuth tokens refreshed before polling usage","Verify the account plan supports the OAuth usage endpoint","Handle 429 cooldowns — back off instead of hammering the quota endpoint","Fall back to legacy usage data display when the new endpoint is unavailable"],"tags":["claude","usage","oauth","fallback","http-status"],"backgroundTag":"oauth-endpoint-unavailable","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}