Tencent/WeKnora · error

exa API returned status %d: %s

Error message

exa API returned status %d: %s

What it means

The Exa web-search provider wraps its Search HTTP call and returns this error whenever the Exa API responds with a non-2xx HTTP status. The provider reads the response body (bounded by maxExaResponseBytes), logs it at warn level, and surfaces both the status code and body so the caller can see why Exa rejected the request. It is a deliberate fail-fast for upstream API failures rather than a client-side bug.

Source

Thrown at internal/infrastructure/web_search/exa.go:106

	if err != nil {
		return nil, fmt.Errorf("failed to create Exa request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("x-api-key", p.apiKey)

	logger.Infof(ctx, "[WebSearch][Exa] query=%q maxResults=%d url=%s", query, maxResults, p.baseURL)
	resp, err := p.client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to execute Exa request: %w", err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(io.LimitReader(resp.Body, maxExaResponseBytes))
	if err != nil {
		return nil, fmt.Errorf("failed to read Exa response: %w", err)
	}
	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
		logger.Warnf(ctx, "[WebSearch][Exa] API returned status %d: %s", resp.StatusCode, string(body))
		return nil, fmt.Errorf("exa API returned status %d: %s", resp.StatusCode, string(body))
	}

	var data exaSearchResponse
	if err := json.Unmarshal(body, &data); err != nil {
		return nil, fmt.Errorf("failed to unmarshal Exa response: %w", err)
	}
	if data.Error != "" {
		return nil, fmt.Errorf("exa API error: %s", data.Error)
	}

	results := make([]*types.WebSearchResult, 0, len(data.Results))
	for _, item := range data.Results {
		if len(results) >= maxResults {
			break
		}
		snippet := strings.TrimSpace(strings.Join(item.Highlights, "\n"))
		content := truncateExaText(strings.TrimSpace(item.Text), maxExaContentRunes)
		if snippet == "" {

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Read the status code and body in the error message: 401/403 means fix the API key, 429 means back off and retry with delay, 5xx means retry later or check Exa status.
  2. Verify the Exa API key configured for the provider is valid and active.
  3. Add retry with exponential backoff for transient 429/5xx statuses.
  4. Check https://status.exa.ai or Exa changelogs if 4xx/5xx persists with a valid key.

Example fix

// before
results, err := exaProvider.Search(ctx, query, 10, false)
if err != nil { return err }
// after
results, err := exaProvider.Search(ctx, query, 10, false)
if err != nil {
    var respErr *fmt.Errorf
    if strings.Contains(err.Error(), "status 429") {
        time.Sleep(2 * time.Second)
        results, err = exaProvider.Search(ctx, query, 10, false)
    }
    if err != nil { return fmt.Errorf("web search failed: %w", err) }
}
Defensive patterns

Strategy: retry

Validate before calling

if exaAPIKey == "" { return errors.New("exa API key missing — request will 401") }

Try / catch

results, err := provider.Search(ctx, q, n, false)
if err != nil {
    if strings.Contains(err.Error(), "status 429") || strings.Contains(err.Error(), "status 5") {
        // retry with exponential backoff
    }
    return fmt.Errorf("exa search failed: %w", err)
}

Prevention

When it happens

Trigger: Calling Search on the Exa provider when the HTTP response status is outside [200, 300) — e.g. 400 bad request body, 401/403 invalid or missing API key, 429 rate limit, 5xx Exa outage.

Common situations: Expired or wrong EXA API key; exceeding Exa's rate quota; malformed query parameters; Exa having a temporary outage or deploying a breaking API change; corporate proxy interfering with TLS.

Related errors


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