chenhg5/cc-connect · error

upload returned status %d: %s

Error message

upload returned status %d: %s

What it means

The DingTalk media upload HTTP endpoint responded with a status code other than 200. The error includes both the numeric status and the raw response body so the caller can see DingTalk's own error payload. This is thrown whenever resp.StatusCode != http.StatusOK after a media upload request.

Source

Thrown at platform/dingtalk/dingtalk.go:1382

	if err != nil {
		return "", fmt.Errorf("create upload request: %w", err)
	}

	req.Header.Set("Content-Type", writer.FormDataContentType())

	resp, err := p.httpClient.Do(req)
	if err != nil {
		return "", fmt.Errorf("upload request: %w", err)
	}
	defer func() { _ = resp.Body.Close() }()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("read upload response: %w", err)
	}

	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)
	}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect the status code and body in the error message; for 401/403 refresh the access token (delete cached token and let getAccessToken re-fetch).
  2. Verify the mediaType matches a supported value (image, voice, video, file) and that the correct upload endpoint URL for that type is used.
  3. Check the request body — multipart form field name ('media'), filename, and Content-Type must be correct for the DingTalk upload API.
  4. If status is 5xx, retry after a short delay; if persistent, check the DingTalk Open Platform status/announcements.
  5. Ensure the app (AgentId/AppKey) has media upload permission enabled in the DingTalk developer console.

Example fix

// before
if resp.StatusCode != http.StatusOK {
    return "", fmt.Errorf("upload returned status %d: %s", resp.StatusCode, respBody)
}
// after
if resp.StatusCode == http.StatusUnauthorized {
    p.invalidateToken() // force token refresh on next call
    return "", fmt.Errorf("upload returned status %d (token expired?): %s", resp.StatusCode, respBody)
}
if resp.StatusCode != http.StatusOK {
    return "", fmt.Errorf("upload returned status %d: %s", resp.StatusCode, respBody)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before uploading, ensure the media type and size are valid
validTypes := map[string]bool{"image": true, "voice": true, "video": true, "file": true}
if !validTypes[mediaType] {
    return fmt.Errorf("unsupported media type %q", mediaType)
}
limits := map[string]int64{"image": 2 << 20, "voice": 2 << 20, "video": 20 << 20, "file": 20 << 20}
if int64(len(data)) > limits[mediaType] {
    return fmt.Errorf("media too large for type %s", mediaType)
}

Type guard

func isAuthStatus(code int) bool {
    return code == http.StatusUnauthorized || code == http.StatusForbidden
}

Try / catch

mediaID, err := uploadMedia(ctx, data, mediaType)
if err != nil {
    var statusErr interface{ error }
    if strings.Contains(err.Error(), "status 401") || strings.Contains(err.Error(), "status 403") {
        // refresh token and retry once
        p.invalidateToken()
        mediaID, err = uploadMedia(ctx, data, mediaType)
    }
    if err != nil {
        return err
    }
}

Prevention

When it happens

Trigger: Any media upload where the server returns e.g. 400 (malformed multipart), 401/403 (invalid or expired access_token in the upload URL), 404 (wrong upload endpoint or media type path), or 5xx (DingTalk server-side failure).

Common situations: Expired or wrong access token embedded in the upload URL; uploading a media type whose endpoint path is wrong; file rejected server-side; DingTalk API outage or rate limiting returning 5xx/429; corporate proxy returning its own non-200 HTML error page.

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/99b41f20cf1ccb82. Report an issue: GitHub.