sipeed/picoclaw · error
feishu image send api error (code=%d msg=%s)
Error message
feishu image send api error (code=%d msg=%s)
What it means
Feishu rejected the image message send with a non-zero business code after the upload succeeded. invalidateTokenOnAuthError runs first, so a 99991663 clears the token cache. The dominant code is 230002 invalid image_key — keys are app-scoped and short-lived.
Source
Thrown at pkg/channels/feishu/feishu_64.go:1178
// Send image message
content, _ := json.Marshal(map[string]string{"image_key": imageKey})
req := larkim.NewCreateMessageReqBuilder().
ReceiveIdType(larkim.CreateMessageV1ReceiveIDTypeChatId).
Body(larkim.NewCreateMessageReqBodyBuilder().
ReceiveId(chatID).
MsgType(larkim.MsgTypeImage).
Content(string(content)).
Build()).
Build()
resp, err := c.client.Im.V1.Message.Create(ctx, req)
if err != nil {
return fmt.Errorf("feishu image send: %w", err)
}
if !resp.Success() {
c.invalidateTokenOnAuthError(resp.Code)
return fmt.Errorf("feishu image send api error (code=%d msg=%s)", resp.Code, resp.Msg)
}
return nil
}
// sendFile uploads a file and sends it as a message.
func (c *FeishuChannel) sendFile(ctx context.Context, chatID string, file *os.File, filename, fileType string) error {
// Map part type to Feishu file type
feishuFileType := "stream"
switch fileType {
case "audio":
feishuFileType = "opus"
case "video":
feishuFileType = "mp4"
}
// Upload file to get file_key
uploadReq := larkim.NewCreateFileReqBuilder().
Body(larkim.NewCreateFileReqBodyBuilder().View on GitHub (pinned to 49183d7e8d)
Solutions
- Treat upload+send as one unit: on this error, re-upload to get a fresh image_key and immediately send.
- Confirm upload and send use the same app credentials (image_key is bound to the app).
- Verify the bot is still in the target chat and has im:message scope.
- If code is 99991663, the token cache was already cleared — the next attempt fetches a fresh token.
Example fix
// before — upload once, send; a backoff-delayed retry can outlive the image_key
key := upload(ctx, f)
err := sendImageMsg(ctx, chatID, key)
// after — re-upload on invalid-key so retry always uses a fresh key
key := upload(ctx, f)
err := sendImageMsg(ctx, chatID, key)
if err != nil && strings.Contains(err.Error(), "code=230002") {
key = upload(ctx, f)
err = sendImageMsg(ctx, chatID, key)
} Defensive patterns
Strategy: retry
Validate before calling
// minimize key age: upload immediately before send // (there is no API to validate an image_key without spending it)
Type guard
func isInvalidImageKey(err error) bool {
return strings.Contains(err.Error(), "feishu image send api error") && strings.Contains(err.Error(), "code=230002")
} Try / catch
if err := sendImageFlow(ctx, chatID, f); err != nil {
if isInvalidImageKey(err) {
// key expired/app-mismatch: re-upload and resend immediately
err = sendImageFlow(ctx, chatID, f)
}
return err
} Prevention
- Never cache image_keys across requests — mint and use immediately
- Use one app credential for upload and send
- Keep bot membership current in target chats
When it happens
Trigger: Message.Create with msg_type=image fails: image_key expired because too much time passed between upload and send; key was minted by a different app_id; bot lacks permission to the target chat; 99991663 stale token.
Common situations: Retries after backoff that outlive the key's validity; uploading with one credential and sending with another after a config change; bot removed from the chat between upload and send.
Related errors
- feishu file send api error (code=%d msg=%s)
- feishu text api error (code=%d msg=%s): %w
- feishu image upload api error (code=%d msg=%s)
- feishu image send: %w
- feishu file upload api error (code=%d msg=%s)
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/ece42bd82f089a2c.
Report an issue: GitHub.