{"record":{"id":"252cb04d707cb22c","repo":"Wei-Shaw/sub2api","slug":"xai-sso-unauthorized","errorCode":null,"errorMessage":"xai sso unauthorized","messagePattern":"xai sso unauthorized","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/internal/pkg/xai/sso_device.go","lineNumber":35,"sourceCode":")\n\nconst (\n\tSSOBuildScope        = \"openid profile email offline_access grok-cli:access api:access conversations:read conversations:write\"\n\tSSOAccountsURL       = \"https://accounts.x.ai/\"\n\tSSODeviceURL         = OAuthIssuer + \"/oauth2/device/code\"\n\tSSOVerifyURL         = OAuthIssuer + \"/oauth2/device/verify\"\n\tSSOApproveURL        = OAuthIssuer + \"/oauth2/device/approve\"\n\tSSOTokenURL          = OAuthIssuer + \"/oauth2/token\"\n\tSSOConversionTimeout = 90 * time.Second\n\n\tssoMaxAuthBody     = 2 << 20\n\tssoMaxTokenLength  = 16 << 10\n\tssoDefaultUA       = \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\"\n\tssoDefaultTokenTTL = 6 * time.Hour\n)\n\nvar (\n\tErrSSOUnauthorized        = errors.New(\"xai sso unauthorized\")\n\tErrSSOAuthorizationDenied = errors.New(\"xai device authorization denied\")\n)\n\ntype SSOHTTPError struct{ Status int }\n\nfunc (e SSOHTTPError) Error() string { return fmt.Sprintf(\"xAI OAuth HTTP %d\", e.Status) }\n\ntype SSODeviceHTTPClient interface {\n\tDo(*http.Request) (*http.Response, error)\n}\n\ntype SSODeviceOptions struct {\n\tHTTPClient SSODeviceHTTPClient\n\tUserAgent  string\n\tSleep      func(context.Context, time.Duration) error\n}\n\ntype ssoDeviceFlow struct {","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/Wei-Shaw/sub2api/blob/073e92d17178a1ccdb0a27017f572f10c9c7ab62/backend/internal/pkg/xai/sso_device.go#L17-L53","documentation":"ErrSSOUnauthorized is a sentinel error in the xAI SSO device flow indicating the session token used to drive the browser-side device authorization is not authorized (the headless HTTP flow got a 401-class rejection or the token was rejected downstream). It is matched with errors.Is by callers to distinguish 'bad/expired session' from network or flow-shape failures.","triggerScenarios":"Starting an xAI SSO device login with a missing, expired, or revoked session token; the accounts.x.ai endpoints respond 401 during Start, and callers translate that to this sentinel. Also returned by token polling paths when the session becomes invalid mid-flow.","commonSituations":"User pasted an SSO token that has expired or was rotated; the token was truncated when copied; the account requires re-login on x.ai; tokens invalidated by a password change or security reset.","solutions":["Obtain a fresh session token from x.ai and retry the device flow.","Check the token was copied completely (no leading/trailing whitespace or truncation).","If it persists, log in via browser to confirm the account is in good standing, then extract a new token.","Match with errors.Is(err, xai.ErrSSOUnauthorized) in the caller to surface a 'please re-authenticate' UX instead of a generic error."],"exampleFix":"// before\nif err != nil {\n    return fmt.Errorf(\"sso failed: %w\", err) // user sees opaque error\n}\n\n// after\nif errors.Is(err, xai.ErrSSOUnauthorized) {\n    return errors.New(\"session token is invalid or expired; please provide a new one\")\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := flow.Start(ctx); err != nil {\n    if errors.Is(err, xai.ErrSSOUnauthorized) {\n        // session token dead: prompt user for a new token, do not retry with the same one\n        return ErrReauthRequired\n    }\n    return err\n}","preventionTips":["Check token freshness before starting the flow","Store tokens with their acquisition time and refresh proactively","Map the sentinel to a 're-authenticate' UX so users self-serve"],"tags":["auth","xai","sso","sentinel-error"],"backgroundTag":null,"analyzedSha":"073e92d17178a1ccdb0a27017f572f10c9c7ab62","analyzedAt":"2026-08-15T14:33:00.750Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}