chenhg5/cc-connect · error

empty downloadUrl in response

Error message

empty downloadUrl in response

What it means

getDownloadURL got a valid, decodable 200 response from the DingTalk media download API but the downloadUrl field is empty. The API accepted the request yet produced no usable URL, so there is nothing to fetch.

Source

Thrown at platform/dingtalk/dingtalk.go:717

	req.Header.Set("x-acs-dingtalk-access-token", token)

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

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

	var result downloadResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return "", fmt.Errorf("decode response: %w", err)
	}

	if result.DownloadUrl == "" {
		return "", fmt.Errorf("empty downloadUrl in response")
	}

	return result.DownloadUrl, nil
}

func (p *Platform) getAccessToken() (string, error) {
	p.tokenMu.Lock()
	defer p.tokenMu.Unlock()

	// Return cached token if still valid
	if p.accessToken != "" && time.Now().Before(p.tokenExpiry) {
		return p.accessToken, nil
	}

	// Request new access token using DingTalk's new API (api.dingtalk.com/v1.0/oauth2/accessToken)
	// This requires POST request with JSON body
	url := "https://api.dingtalk.com/v1.0/oauth2/accessToken"

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Ask the user to resend the image/file — the downloadCode is most likely expired or already consumed.
  2. Log the full response JSON to confirm the exact field names returned by your API version.
  3. Check the app's media download scope/permission in the DingTalk developer console.
  4. Upgrade cc-connect / verify against current DingTalk API docs in case the field was renamed.
  5. Treat this as retryable once after refreshing the access token, then fail the message.
Defensive patterns

Strategy: fallback

Validate before calling

if time.Since(mediaCodeReceivedAt) > 10*time.Minute {
    // skip calling the API; ask user to resend
}

Try / catch

url, err := p.getDownloadURL(ctx, code)
if err != nil {
    if strings.Contains(err.Error(), "empty downloadUrl") {
        return p.replyText(ctx, "The media has expired, please resend it.")
    }
    return err
}

Prevention

When it happens

Trigger: handleImageMessage / handleFileMessage / downloadAudio receive a 200 response whose JSON body lacks a non-empty downloadUrl — e.g. the downloadCode is semantically invalid (expired/consumed) while the API still returns 200, or the media was deleted from DingTalk servers.

Common situations: Re-processing an old queued message whose media code expired, a DingTalk API change in field naming/casing, or the bot app lacking permission so the API returns a success envelope without a URL.

Understand the failure class

Background: "empty response", "returned no data", "empty embeddings": what HTTP 200-with-empty-body errors mean across libraries — this error's family across 36 libraries.

Related errors


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