chenhg5/cc-connect · error
dingtalk: send image failed: status=%d, body=%s
Error message
dingtalk: send image failed: status=%d, body=%s
What it means
Returned by SendImage when DingTalk's oToMessages batchSend API replies with a non-200 status. The response body (DingTalk's JSON error payload) is embedded in the error, so read it to identify the API-level cause such as an expired access token or invalid robot code/user id.
Source
Thrown at platform/dingtalk/dingtalk.go:1069
"https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend",
bytes.NewReader(body))
if err != nil {
return fmt.Errorf("dingtalk: create image request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-acs-dingtalk-access-token", token)
resp, err := p.httpClient.Do(req)
if err != nil {
return fmt.Errorf("dingtalk: send image request: %w", err)
}
defer func() { _ = resp.Body.Close() }()
respBody, _ := io.ReadAll(resp.Body)
slog.Debug("dingtalk: oToMessages image response", "status", resp.StatusCode, "body", string(respBody))
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("dingtalk: send image failed: status=%d, body=%s", resp.StatusCode, string(respBody))
}
slog.Info("dingtalk: image message sent", "media_id", mediaID, "user", rc.senderStaffId)
return nil
}
var _ core.ImageSender = (*Platform)(nil)
var _ core.StreamingCardPlatform = (*Platform)(nil)
var _ core.ReplyContextReconstructor = (*Platform)(nil)
var _ core.TypingIndicator = (*Platform)(nil)
var _ core.TypingIndicatorDone = (*Platform)(nil)
// CreateStreamingCard creates a new streaming card for the given reply context.
// Implements core.StreamingCardPlatform.
func (p *Platform) CreateStreamingCard(ctx context.Context, replyCtx any) (core.StreamingCard, error) {
if p.cardTemplateID == "" {
return nil, fmt.Errorf("dingtalk: card_template_id not configured")
}View on GitHub (pinned to 4000b2338a)
Solutions
- Read the body=%s portion of the error — it contains DingTalk's code/message
- If 401/invalidAuthentication, force token refresh (clear cached access token) and retry
- Verify the robot app has the 'Enterprise robot message' permission and the target userId is correct
- Ensure mediaID came from a successful uploadMedia with mediaType 'image'
- Retry with backoff if status is 429/5xx
Example fix
// before
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("dingtalk: send image failed: status=%d, body=%s", resp.StatusCode, string(respBody))
}
// after (caller-side)
if err := p.SendImage(ctx, rc, img); err != nil && strings.Contains(err.Error(), "invalidAuthentication") {
p.invalidateToken()
err = p.SendImage(ctx, rc, img)
} Defensive patterns
Strategy: try-catch
Try / catch
if err := p.SendImage(ctx, rc, img); err != nil {
var apiErr struct{ Status int; Body string }
if strings.Contains(err.Error(), "invalidAuthentication") {
p.invalidateToken(); /* retry once */
}
slog.Warn("dingtalk image send rejected", "err", err)
} Prevention
- Parse the body= payload for DingTalk's error code before deciding to retry
- Refresh access tokens proactively before expiry
- Confirm oToMessages permissions and recipient contact requirements in the DingTalk console
When it happens
Trigger: Expired or invalid access token (401/400); senderStaffId not reachable by this robot; robot app lacking permission for oToMessages; mediaID invalid or expired; DingTalk rate limiting (429).
Common situations: Access token cache expired; image uploaded to wrong media type; target user never messaged the robot first (oToMessages requires existing contact); app permissions not granted in DingTalk developer console.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- create AI card: status=%d, body=%s
- stream AI card: status=%d, body=%s
- create request: %w
- do request: %w
- api returned status %d
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/eff078094aeb7bfe.
Report an issue: GitHub.