{"record":{"id":"51581df2b8f0958f","repo":"paperclipai/paperclip","slug":"anthropic-usage-api-returned-resp-status","errorCode":null,"errorMessage":"anthropic usage api returned ${resp.status}","messagePattern":"anthropic usage api returned (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/adapters/claude-local/src/server/quota.ts","lineNumber":219,"sourceCode":"/** fetch with an abort-based timeout so a hanging provider api doesn't block the response indefinitely */\nexport async function fetchWithTimeout(url: string, init: RequestInit, ms = 8000): Promise<Response> {\n  const controller = new AbortController();\n  const timer = setTimeout(() => controller.abort(), ms);\n  try {\n    return await fetch(url, { ...init, signal: controller.signal });\n  } finally {\n    clearTimeout(timer);\n  }\n}\n\nexport async function fetchClaudeQuota(token: string): Promise<QuotaWindow[]> {\n  const resp = await fetchWithTimeout(\"https://api.anthropic.com/api/oauth/usage\", {\n    headers: {\n      Authorization: `Bearer ${token}`,\n      \"anthropic-beta\": \"oauth-2025-04-20\",\n    },\n  });\n  if (!resp.ok) throw new Error(`anthropic usage api returned ${resp.status}`);\n  const body = (await resp.json()) as AnthropicUsageResponse;\n  const windows: QuotaWindow[] = [];\n\n  if (body.five_hour != null) {\n    windows.push({\n      label: \"Current session\",\n      usedPercent: toPercent(body.five_hour.utilization),\n      resetsAt: body.five_hour.resets_at ?? null,\n      valueLabel: null,\n      detail: null,\n    });\n  }\n  if (body.seven_day != null) {\n    windows.push({\n      label: \"Current week (all models)\",\n      usedPercent: toPercent(body.seven_day.utilization),\n      resetsAt: body.seven_day.resets_at ?? null,\n      valueLabel: null,","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/adapters/claude-local/src/server/quota.ts#L201-L237","documentation":"Thrown by fetchClaudeQuota when the Anthropic OAuth usage endpoint (https://api.anthropic.com/api/oauth/usage) returns a non-OK HTTP status. The bearer token was sent; the server responded, but not with 200, so the body is not parsed as a quota payload and the raw status code is surfaced for diagnosis.","triggerScenarios":"Calling fetchClaudeQuota(token) where the fetch resolves but resp.ok is false. Common status codes: 401 (token expired/revoked), 403 (scope/permission), 429 (rate limited), 5xx (upstream outage), or 404 if the endpoint path changed.","commonSituations":"Claude OAuth token past its refresh window; an organization whose plan disables the usage endpoint; transient Anthropic API outage; running in a region that gets rate-limited; token from a different Anthropic account tier.","solutions":["Refresh the Claude OAuth credentials: run `claude login` so readClaudeToken returns a fresh access token.","Map the status code: 401/403 -> re-auth; 429 -> retry with backoff; 5xx -> transient, retry later.","Confirm the token has the expected OAuth scopes for the usage endpoint.","If using the quota-probe CLI, pass --cli-only to bypass the OAuth path and use the CLI-based quota source instead."],"exampleFix":"// before\nconst windows = await fetchClaudeQuota(staleToken);\n// after\n// run `claude login` to refresh, then:\nconst token = await readClaudeToken();\nconst windows = await fetchClaudeQuota(token);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const windows = await fetchClaudeQuota(token);\n} catch (err) {\n  const m = err.message.match(/returned (\\d+)/);\n  if (m) {\n    const status = Number(m[1]);\n    if (status === 401 || status === 403) { /* re-auth */ }\n    if (status === 429) { /* back off and retry */ }\n  }\n  throw err;\n}","preventionTips":["Refresh Claude OAuth tokens proactively before expiry.","Treat 401/403 as re-auth signal and 429/5xx as retry-with-backoff.","Provide a fallback quota source (CLI probe) when the OAuth endpoint is unavailable."],"tags":["claude","oauth","quota","network","anthropic-api"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}