{"record":{"id":"1815ad53a71fa158","repo":"sipeed/picoclaw","slug":"insufficient-scope-usage-endpoint-requires-oauth","errorCode":null,"errorMessage":"insufficient scope: usage endpoint requires oauth scope","messagePattern":"insufficient scope: usage endpoint requires oauth scope","errorType":"exception","errorClass":null,"httpStatus":403,"severity":"error","filePath":"pkg/auth/anthropic_usage.go","lineNumber":50,"sourceCode":"\treq.Header.Set(\"Authorization\", \"Bearer \"+token)\n\treq.Header.Set(\"Anthropic-Version\", anthropicAPIVersion)\n\treq.Header.Set(\"Anthropic-Beta\", anthropicBetaHeader)\n\n\tclient := &http.Client{Timeout: 10 * time.Second}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"reading usage response: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tif resp.StatusCode == http.StatusForbidden {\n\t\t\treturn nil, fmt.Errorf(\"insufficient scope: usage endpoint requires oauth scope\")\n\t\t}\n\t\treturn nil, fmt.Errorf(\"usage request failed (%d): %s\", resp.StatusCode, string(body))\n\t}\n\n\tvar result struct {\n\t\tFiveHour struct {\n\t\t\tUtilization float64 `json:\"utilization\"`\n\t\t} `json:\"five_hour\"`\n\t\tSevenDay struct {\n\t\t\tUtilization float64 `json:\"utilization\"`\n\t\t} `json:\"seven_day\"`\n\t}\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\treturn nil, fmt.Errorf(\"parsing usage response: %w\", err)\n\t}\n\n\treturn &AnthropicUsage{\n\t\tFiveHourUtilization: result.FiveHour.Utilization,","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/auth/anthropic_usage.go#L32-L68","documentation":"The Anthropic usage endpoint answered HTTP 403 (anthropic_usage.go:50): the request authenticated, but the credential's OAuth token lacks the scope required by the usage endpoint. This is a deliberate authorization failure, distinct from a network or parse error, and is non-retryable — retrying the same token always yields 403.","triggerScenarios":"Calling the usage fetch with an OAuth credential minted by a login flow that did not request the usage scope; using a token from an OAuth client/app not allow-listed for usage reporting; org policy stripping the scope from issued tokens.","commonSituations":"Token from an older login (pre-usage-scope); custom OAuth client_id missing the usage scope; Claude Max/Pro subscription vs API-key confusion where usage endpoint is OAuth-only.","solutions":["Re-authenticate through the current login flow so a fresh token with the usage scope is issued","Verify in the provider console that your OAuth client requests/is granted the usage scope","If your plan/credential type does not include usage scope, disable usage polling rather than retrying","Do not retry in a loop — classify 403 as permanent and surface it to the user"],"exampleFix":"// before: poll usage forever with an unscoped token\nfor { usage, err := fetchUsage(token); if err != nil { continue } }\n\n// after: stop permanently on scope errors\nif strings.Contains(err.Error(), \"insufficient scope\") {\n    log.Print(\"token lacks usage scope; re-login required\")\n    return err\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"usage, err := fetchUsage(ctx, token)\nif err != nil {\n    if strings.Contains(err.Error(), \"insufficient scope\") {\n        // permanent: stop polling, prompt re-login\n        disableUsagePolling()\n        promptRelogin()\n        return err\n    }\n    return err\n}","preventionTips":["Authenticate through the flow that grants the usage scope before enabling usage features","Never auto-retry 403-class authorization errors","Distinguish 'insufficient scope' (403) from 'request failed (401)' (expired token) in handlers"],"tags":["auth","oauth","http","permissions","anthropic"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}