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 nilView on GitHub (pinned to 4000b2338a)
Solutions
- 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).
- If the code indicates a token problem, clear the cached token and call getAccessToken again before retrying.
- Check media size/format against DingTalk limits: image ≤2MB, voice ≤2MB (amr), video ≤20MB, file ≤20MB.
- Verify the mediaType parameter is exactly one of image/voice/video/file.
- 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
- Keep a mapping of common DingTalk errcodes and their fixes next to your runbook.
- Pre-check media size/format against DingTalk per-type limits.
- Proactively refresh tokens before the 2-hour expiry window closes.
- Confirm app permissions in the developer console after any app changes.
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
- upload returned status %d: %s
- decode upload response: %w, body: %s
- empty media_id in upload response: %s
- get access token: %w
- create AI card: status=%d, body=%s
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/6d9df7891b12804f.
Report an issue: GitHub.