chenhg5/cc-connect · error

upload API error %d: %s

Error message

upload API error %d: %s

What it means

DingTalk's media upload API returned HTTP 200, but the JSON envelope carries a business-level errcode != 0 with an errmsg describing the failure. This is the standard DingTalk convention: transport succeeded, but the API rejected the operation. The error surfaces errcode and errmsg verbatim.

Source

Thrown at platform/dingtalk/dingtalk.go:1398

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("upload returned status %d: %s", resp.StatusCode, respBody)
	}

	slog.Debug("dingtalk: media upload response", "status", resp.StatusCode, "body", string(respBody))

	var uploadResp struct {
		ErrCode int    `json:"errcode"`
		ErrMsg  string `json:"errmsg"`
		MediaID string `json:"media_id"`
		Type    string `json:"type"`
	}
	if err := json.Unmarshal(respBody, &uploadResp); err != nil {
		return "", fmt.Errorf("decode upload response: %w, body: %s", err, respBody)
	}

	if uploadResp.ErrCode != 0 {
		return "", fmt.Errorf("upload API error %d: %s", uploadResp.ErrCode, uploadResp.ErrMsg)
	}

	if uploadResp.MediaID == "" {
		return "", fmt.Errorf("empty media_id in upload response: %s", respBody)
	}

	slog.Debug("dingtalk: media uploaded successfully", "media_id", uploadResp.MediaID, "type", mediaType, "size", len(data))
	return uploadResp.MediaID, nil
}

func (p *Platform) Stop() error {
	if p.streamCtxCancel != nil {
		p.streamCtxCancel()
	}
	if p.streamClient != nil {
		p.streamClient.Close()
	}
	return nil

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Read errcode/errmsg in the message and look it up in DingTalk's global error code table (e.g. 40001 = invalid credential, 40014 = invalid access_token).
  2. If the code indicates a token problem, clear the cached token and call getAccessToken again before retrying.
  3. Check media size/format against DingTalk limits: image ≤2MB, voice ≤2MB (amr), video ≤20MB, file ≤20MB.
  4. Verify the mediaType parameter is exactly one of image/voice/video/file.
  5. Confirm the app's permissions in the DingTalk developer console include media upload for the target bot.

Example fix

// before
if uploadResp.ErrCode != 0 {
    return "", fmt.Errorf("upload API error %d: %s", uploadResp.ErrCode, uploadResp.ErrMsg)
}
// after
if uploadResp.ErrCode == 40001 || uploadResp.ErrCode == 40014 {
    p.invalidateToken()
    return "", fmt.Errorf("upload API error %d (token invalid, refresh needed): %s", uploadResp.ErrCode, uploadResp.ErrMsg)
}
if uploadResp.ErrCode != 0 {
    return "", fmt.Errorf("upload API error %d: %s", uploadResp.ErrCode, uploadResp.ErrMsg)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-validate what errcode commonly rejects
if int64(len(data)) > 20<<20 {
    return fmt.Errorf("media exceeds DingTalk 20MB limit")
}
if mediaType == "voice" && !strings.HasSuffix(filename, ".amr") {
    return fmt.Errorf("voice media must be amr format")
}

Type guard

func isTokenErrCode(code int) bool {
    return code == 40001 || code == 40014 || code == 42001 // invalid/expired credential codes
}

Try / catch

mediaID, err := uploadMedia(ctx, data, mediaType)
if err != nil {
    if isTokenErrCode(extractErrCode(err)) {
        p.invalidateToken()
        mediaID, err = uploadMedia(ctx, data, mediaType) // retry with fresh token
    }
    if err != nil {
        return err
    }
}

Prevention

When it happens

Trigger: Uploading media with an invalid or unsupported 'type' value; exceeding DingTalk's media size limits (e.g. image > 2MB, file > 20MB, voice > 2MB or wrong format); access_token invalid at the business layer; app lacking upload permission.

Common situations: Sending files larger than DingTalk's per-type limits; wrong Content-Type for voice (must be amr MP3-era formats per docs); expired/revoked access_token returning errcode 40001/40014; using credentials from the wrong app in the developer console.

Related errors


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