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

  1. Read the body=%s portion of the error — it contains DingTalk's code/message
  2. If 401/invalidAuthentication, force token refresh (clear cached access token) and retry
  3. Verify the robot app has the 'Enterprise robot message' permission and the target userId is correct
  4. Ensure mediaID came from a successful uploadMedia with mediaType 'image'
  5. 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

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


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/eff078094aeb7bfe. Report an issue: GitHub.