{"record":{"id":"3a16f804a80c4a98","repo":"sipeed/picoclaw","slug":"feishu-file-upload-api-error-code-d-msg-s","errorCode":null,"errorMessage":"feishu file upload api error (code=%d msg=%s)","messagePattern":"feishu file upload api error \\(code=(.+?) msg=(.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/feishu/feishu_64.go","lineNumber":1209,"sourceCode":"\t\tfeishuFileType = \"mp4\"\n\t}\n\n\t// Upload file to get file_key\n\tuploadReq := larkim.NewCreateFileReqBuilder().\n\t\tBody(larkim.NewCreateFileReqBodyBuilder().\n\t\t\tFileType(feishuFileType).\n\t\t\tFileName(filename).\n\t\t\tFile(file).\n\t\t\tBuild()).\n\t\tBuild()\n\n\tuploadResp, err := c.client.Im.V1.File.Create(ctx, uploadReq)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"feishu file upload: %w\", err)\n\t}\n\tif !uploadResp.Success() {\n\t\tc.invalidateTokenOnAuthError(uploadResp.Code)\n\t\treturn fmt.Errorf(\"feishu file upload api error (code=%d msg=%s)\", uploadResp.Code, uploadResp.Msg)\n\t}\n\tif uploadResp.Data == nil || uploadResp.Data.FileKey == nil {\n\t\treturn fmt.Errorf(\"feishu file upload: no file_key returned\")\n\t}\n\n\tfileKey := *uploadResp.Data.FileKey\n\n\t// Send file message\n\tcontent, _ := json.Marshal(map[string]string{\"file_key\": fileKey})\n\treq := larkim.NewCreateMessageReqBuilder().\n\t\tReceiveIdType(larkim.CreateMessageV1ReceiveIDTypeChatId).\n\t\tBody(larkim.NewCreateMessageReqBodyBuilder().\n\t\t\tReceiveId(chatID).\n\t\t\tMsgType(larkim.MsgTypeFile).\n\t\t\tContent(string(content)).\n\t\t\tBuild()).\n\t\tBuild()\n","sourceCodeStart":1191,"sourceCodeEnd":1227,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/feishu/feishu_64.go#L1191-L1227","documentation":"Feishu's file upload endpoint returned a non-zero business code (uploadResp.Success() false). invalidateTokenOnAuthError runs first, clearing the token cache on 99991663. Typical codes: 230002 invalid file (size >30MB, unsupported/mismatched file_type), 99991663 token, 99991400 rate limit.","triggerScenarios":"Im.V1.File.Create with FileType('stream'|'opus'|'mp4') fails: file exceeds 30MB; audio/video sent with the wrong feishuFileType mapping; 99991663 stale token; app lacks im:file scope; burst uploads rate-limited.","commonSituations":"Forwarding large media from other channels; sending a .ogg recorded as opus but declared stream; credentials rotated while old token cached.","solutions":["Match the code: 230002 -> check 30MB limit and that file_type matches the actual content (audio=opus, video=mp4, else stream); 99991663 -> fix credentials; 99991400 -> throttle.","Pre-validate size and extension-to-type mapping before upload (see exampleFix).","Grant im:file permission to the app.","Re-encode oversized media instead of forwarding raw."],"exampleFix":"// before\nerr := ch.SendMedia(ctx, msg) // 230002 after full upload of an oversized file\n\n// after — validate before uploading\nif fi, serr := f.Stat(); serr == nil && fi.Size() > 30<<20 {\n    return fmt.Errorf(\"file %s exceeds Feishu 30MB limit (%d bytes)\", name, fi.Size())\n}\nwantType := map[string]string{\"audio\": \"opus\", \"video\": \"mp4\"}[fileType]\nif wantType == \"\" { wantType = \"stream\" } // must match the value used in CreateFileReqBodyBuilder","handlingStrategy":"validation","validationCode":"const feishuMaxFile = 30 << 20\nif fi, serr := f.Stat(); serr != nil || fi.Size() > feishuMaxFile {\n    return fmt.Errorf(\"file exceeds Feishu 30MB limit or is unreadable\")\n}\nwant := map[string]string{\"audio\": \"opus\", \"video\": \"mp4\"}[fileType]\nif want == \"\" { want = \"stream\" }\n// ensure the same 'want' is what the channel passes as feishuFileType","typeGuard":"func isFeishuFileUploadAPIErr(err error) bool {\n    return strings.Contains(err.Error(), \"feishu file upload api error (code=\")\n}","tryCatchPattern":"if err := ch.SendMedia(ctx, msg); err != nil {\n    if strings.Contains(err.Error(), \"code=230002\") {\n        // invalid file/type: fix size, encoding, or type mapping — retrying same bytes fails again\n    }\n    if strings.Contains(err.Error(), \"code=99991663\") {\n        // token issue; cache already invalidated\n    }\n}","preventionTips":["Map part types to opus/mp4/stream exactly like the channel does","Re-encode oversized media under 30MB","Grant im:file scope","Throttle uploads to stay under QPS limits"],"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"}