chenhg5/cc-connect · error

api returned status %d

Error message

api returned status %d

What it means

getDownloadURL calls DingTalk's media download API to obtain a temporary download URL for an incoming image/file/audio message. When the API responds with any HTTP status other than 200, the function aborts and wraps the status code in this error. It indicates the download-ticket request itself failed before a URL could be parsed.

Source

Thrown at platform/dingtalk/dingtalk.go:708

	defer cancel()
	req, err := http.NewRequestWithContext(ctx, http.MethodPost,
		"https://api.dingtalk.com/v1.0/robot/messageFiles/download",
		bytes.NewReader(bodyBytes))
	if err != nil {
		return "", fmt.Errorf("create 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("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()

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check the status code in the error message: 401/403 means the access token is invalid — verify appKey/appSecret in config.toml and restart.
  2. If the code is 4xx referencing the downloadCode, the media code likely expired; ask the user to resend the image/file.
  3. For 5xx, retry getDownloadURL with backoff after confirming the DingTalk status page shows no outage.
  4. Confirm the robot/app has media download permissions in the DingTalk developer console.
  5. Enable debug logging to capture the full request/response and compare against DingTalk API docs.

Example fix

// before: single attempt, no retry on transient status
url, err := p.getDownloadURL(ctx, code)
if err != nil { return fmt.Errorf("dingtalk: get download url: %w", err) }

// after: retry transient statuses
url, err := p.getDownloadURL(ctx, code)
if err != nil {
    if strings.Contains(err.Error(), "status 5") {
        time.Sleep(2 * time.Second)
        url, err = p.getDownloadURL(ctx, code)
    }
    if err != nil { return fmt.Errorf("dingtalk: get download url: %w", err) }
}
Defensive patterns

Strategy: retry

Validate before calling

if !strings.HasPrefix(mediaCode, "") && time.Since(mediaCodeReceivedAt) < 5*time.Minute {
    // code likely still valid, safe to call
}

Try / catch

url, err := p.getDownloadURL(ctx, code)
if err != nil {
    var status int
    if _, serr := fmt.Sscanf(err.Error(), "api returned status %d", &status); serr == nil && status >= 500 {
        time.Sleep(2 * time.Second)
        url, err = p.getDownloadURL(ctx, code)
    }
    if err != nil { return fmt.Errorf("download url: %w", err) }
}

Prevention

When it happens

Trigger: Called from handleImageMessage, handleFileMessage, or downloadAudio when the POST to the DingTalk media/download endpoint returns a non-200 status, e.g. 400 for a malformed downloadCode, 401/403 for an expired or invalid access token, 404 for an expired code, or 5xx from DingTalk.

Common situations: Expired downloadCode (DingTalk media codes are short-lived and single-use), an access token that expired or was revoked after an app-secret rotation, wrong appKey/appSecret config, or transient DingTalk server errors (500/502/503).

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/1ea846923e99946f. Report an issue: GitHub.