Tencent/WeKnora · error

duckduckgo API returned status %d: %s

Error message

duckduckgo API returned status %d: %s

What it means

Thrown when the DuckDuckGo Instant Answer API responds with a non-200 status. The response body is read and included in the message so the server's error detail is visible. This means the request succeeded but the API rejected it or is degraded.

Source

Thrown at internal/infrastructure/web_search/duckduckgo.go:161

	params.Set("format", "json")
	params.Set("no_html", "1")
	params.Set("skip_disambig", "1")

	reqURL := baseURL + "?" + params.Encode()
	req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}
	req.Header.Set("User-Agent", "WeKnora/1.0")

	resp, err := p.client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to perform request: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("duckduckgo API returned status %d: %s", resp.StatusCode, string(body))
	}

	var apiResponse struct {
		AbstractText  string `json:"AbstractText"`
		AbstractURL   string `json:"AbstractURL"`
		Heading       string `json:"Heading"`
		RelatedTopics []struct {
			FirstURL string `json:"FirstURL"`
			Text     string `json:"Text"`
		} `json:"RelatedTopics"`
		Results []struct {
			FirstURL string `json:"FirstURL"`
			Text     string `json:"Text"`
		} `json:"Results"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {
		return nil, fmt.Errorf("failed to decode API response: %w", err)
	}

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Read the status and body in the error message to identify the cause
  2. Back off and retry on 429/5xx with exponential backoff
  3. Rotate IPs or route through a proxy if the IP is blocked
  4. Set a more realistic browser-like User-Agent if blocked
  5. Check DuckDuckGo API status; fall back to another search provider

Example fix

// before
return nil, fmt.Errorf("duckduckgo API returned status %d: %s", resp.StatusCode, string(body))
// after
if resp.StatusCode == http.StatusTooManyRequests {
    return nil, retryableError{fmt.Errorf("duckduckgo rate limited: %d", resp.StatusCode)}
}
return nil, fmt.Errorf("duckduckgo API returned status %d: %s", resp.StatusCode, string(body))
Defensive patterns

Strategy: retry

Validate before calling

if !isRetryableStatus(resp.StatusCode) {
    return fmt.Errorf("non-retryable duckduckgo status %d", resp.StatusCode)
}
func isRetryableStatus(code int) bool {
    return code == 429 || code >= 500
}

Try / catch

results, err := provider.Search(ctx, query, 10, false)
var apiErr *APIStatusError
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusTooManyRequests {
    time.Sleep(backoff) // then retry or fall back to another provider
}

Prevention

When it happens

Trigger: Calling Search -> searchAPI when DuckDuckGo returns 4xx/5xx — rate limiting (429), blocked user-agent or IP, API outage (5xx), or upstream redirects the client does not follow.

Common situations: High search volume triggering rate limits, datacenter IPs blocked by DuckDuckGo, User-Agent 'WeKnora/1.0' being filtered, temporary DuckDuckGo API incidents.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/e3f9d7c67c4e5c20. Report an issue: GitHub.