larksuite/cli · error
user_info API error: [%d] %s
Error message
user_info API error: [%d] %s
What it means
The user_info endpoint returned HTTP 200 but the JSON envelope carried a non-zero code — an API-level business error. The message includes the legacy Lark code and msg fields, so the exact failure reason is in the error text.
Source
Thrown at internal/credential/user_info.go:53
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("user_info API returned HTTP %d", resp.StatusCode)
}
var result struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
OpenID string `json:"open_id"`
Name string `json:"name"`
} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
if result.Code != 0 {
return nil, fmt.Errorf("user_info API error: [%d] %s", result.Code, result.Msg)
}
return &userInfo{OpenID: result.Data.OpenID, Name: result.Data.Name}, nil
}
View on GitHub (pinned to 7fd6ef3c07)
Solutions
- Read the [%d] code in the message and look it up in the Lark error-code docs; auth/token codes (9999166x) mean re-obtain the user_access_token.
- Verify the app has the required scopes (e.g. contact:user.base:readonly / user info scope) and the user has re-consented after scope changes.
- Confirm you pass a user_access_token (not app/tenant token) in the Authorization header.
- If permission codes persist, republish the app version with the new scopes and re-install/authorize in the tenant.
Example fix
// before
tok := appTenantToken() // wrong token type
info, err := enrichUserInfo(ctx, tok)
// after
tok := userAccessTokenFromOAuth(ctx) // user_access_token
info, err := enrichUserInfo(ctx, tok)
if err != nil && strings.Contains(err.Error(), "9999166") {
tok = userAccessTokenFromOAuth(ctx) // token expired/invalid: re-run OAuth
info, err = enrichUserInfo(ctx, tok)
} Defensive patterns
Strategy: validation
Validate before calling
func validateUserAccessPrereqs(tok string) error {
if tok == "" { return errors.New("user_access_token required for user_info") }
if strings.HasPrefix(tok, "t-") { return errors.New("looks like a tenant/app token; user_info needs a user_access_token") }
return nil
} Try / catch
info, err := enrichUserInfo(ctx, userToken)
if err != nil {
var code int
if _, scan := fmt.Sscanf(err.Error(), "user_info API error: [%d]", &code); scan == nil {
switch code {
case 99991663, 99991661, 99991668: // token invalid/expired family
userToken = refreshUserAccessToken(ctx)
info, err = enrichUserInfo(ctx, userToken)
default: // permission/scope issue: do not retry, fix app config
return fmt.Errorf("user_info rejected (code %d): grant scopes and re-auth", code)
}
}
if err != nil { return err }
} Prevention
- Always use a user_access_token, never an app/tenant token, for authen/v1/user_info.
- Declare and publish all required user-info scopes before first authorization, so consent covers them.
- Re-run the OAuth consent flow after any scope change to the app.
- Map known Lark error codes (9999166x auth family) to token-refresh handling in your integration.
When it happens
Trigger: result.Code != 0 after decoding a 200 response from authen/v1/user_info: invalid access token (code 99991663/99991661 family), insufficient app scopes/permissions, token belonging to a different app, or user not accessible to the app.
Common situations: Calling user_info with an app_access_token instead of a user_access_token; missing contact/user-info scopes on the app; expired user token that still passes an auth gateway but fails business validation; app installed in a tenant lacking the required permissions.
Related errors
- failed to get user info [%d]: %s
- [%d] %s
- fetch bot info: HTTP %d
- {json.dumps(envelope, ensure_ascii=False)}
- keychain access blocked
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/2efd3af5d8136c09.
Report an issue: GitHub.