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

  1. 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.
  2. 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.
  3. Confirm you pass a user_access_token (not app/tenant token) in the Authorization header.
  4. 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

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


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/2efd3af5d8136c09. Report an issue: GitHub.