{"record":{"id":"32072d4a08a1b36b","repo":"sipeed/picoclaw","slug":"feishu-image-upload-api-error-code-d-msg-s","errorCode":null,"errorMessage":"feishu image upload api error (code=%d msg=%s)","messagePattern":"feishu image upload api error \\(code=(.+?) msg=(.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/feishu/feishu_64.go","lineNumber":1153,"sourceCode":"}\n\n// sendImage uploads an image and sends it as a message.\nfunc (c *FeishuChannel) sendImage(ctx context.Context, chatID string, file *os.File) error {\n\t// Upload image to get image_key\n\tuploadReq := larkim.NewCreateImageReqBuilder().\n\t\tBody(larkim.NewCreateImageReqBodyBuilder().\n\t\t\tImageType(\"message\").\n\t\t\tImage(file).\n\t\t\tBuild()).\n\t\tBuild()\n\n\tuploadResp, err := c.client.Im.V1.Image.Create(ctx, uploadReq)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"feishu image upload: %w\", err)\n\t}\n\tif !uploadResp.Success() {\n\t\tc.invalidateTokenOnAuthError(uploadResp.Code)\n\t\treturn fmt.Errorf(\"feishu image upload api error (code=%d msg=%s)\", uploadResp.Code, uploadResp.Msg)\n\t}\n\tif uploadResp.Data == nil || uploadResp.Data.ImageKey == nil {\n\t\treturn fmt.Errorf(\"feishu image upload: no image_key returned\")\n\t}\n\n\timageKey := *uploadResp.Data.ImageKey\n\n\t// Send image message\n\tcontent, _ := json.Marshal(map[string]string{\"image_key\": imageKey})\n\treq := larkim.NewCreateMessageReqBuilder().\n\t\tReceiveIdType(larkim.CreateMessageV1ReceiveIDTypeChatId).\n\t\tBody(larkim.NewCreateMessageReqBodyBuilder().\n\t\t\tReceiveId(chatID).\n\t\t\tMsgType(larkim.MsgTypeImage).\n\t\t\tContent(string(content)).\n\t\t\tBuild()).\n\t\tBuild()\n","sourceCodeStart":1135,"sourceCodeEnd":1171,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/feishu/feishu_64.go#L1135-L1171","documentation":"Feishu's image upload endpoint answered HTTP-level success semantics but with a non-zero business code (uploadResp.Success() false). The channel clears the cached tenant token when the code is 99991663. Typical codes: 230002 invalid image (unsupported format or >10MB), 99991663 invalid token, 99991400 rate limited.","triggerScenarios":"Im.V1.Image.Create with ImageType('message') returns code 230002: file is not JPG/PNG, exceeds 10MB, or is corrupted; 99991663 after credential rotation; 99991400 from burst uploads; app missing im:image scope.","commonSituations":"Sending WebP/GIF/HEIC which message-type images reject; phone-sourced photos over 10MB; bot token regenerated in the developer console but still cached.","solutions":["Match the code: 230002 -> convert to JPG/PNG and compress under 10MB; 99991663 -> fix credentials (cache is auto-cleared); 99991400 -> throttle uploads.","Pre-validate size and MIME type before sending (see exampleFix).","Grant im:image permission to the app in the Feishu console.","If the file came from user upload, re-encode server-side instead of forwarding raw bytes."],"exampleFix":"// before — any file goes straight to the API, 230002 surfaces after the upload\nerr := ch.SendMedia(ctx, msg)\n\n// after — validate before uploading\nif fi, serr := f.Stat(); serr == nil && fi.Size() > 10<<20 {\n    return fmt.Errorf(\"image %s is %d bytes; Feishu limit is 10MB\", name, fi.Size())\n}\nif mt := mime.TypeByExtension(filepath.Ext(name)); mt != \"image/jpeg\" && mt != \"image/png\" {\n    return fmt.Errorf(\"image %s is %s; convert to JPEG/PNG first\", name, mt)\n}","handlingStrategy":"validation","validationCode":"const feishuMaxImage = 10 << 20\nif fi, serr := f.Stat(); serr != nil || fi.Size() > feishuMaxImage {\n    return fmt.Errorf(\"image exceeds Feishu 10MB limit or is unreadable\")\n}\nswitch mime.TypeByExtension(filepath.Ext(name)) {\ncase \"image/jpeg\", \"image/png\":\ndefault:\n    return fmt.Errorf(\"image %s must be JPEG or PNG\", name)\n}","typeGuard":"func isFeishuUploadAPIErr(err error) bool {\n    return strings.Contains(err.Error(), \"feishu image upload api error (code=\")\n}","tryCatchPattern":"if err := ch.SendMedia(ctx, msg); err != nil {\n    if strings.Contains(err.Error(), \"code=230002\") {\n        // invalid image: convert/compress and retry — do not blind-retry the same bytes\n    }\n    if strings.Contains(err.Error(), \"code=99991663\") {\n        // token: credentials problem, cache already cleared by the channel\n    }\n}","preventionTips":["Convert WebP/GIF/HEIC to JPEG/PNG before sending","Compress images under 10MB server-side","Grant im:image scope up front","Throttle uploads to avoid 99991400"],"tags":["feishu","upload","go","validation","lark-api"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}