Tencent/WeKnora · error
exa API error
Error message
exa API error: %s
What it means
Exa responded successfully (2xx, valid JSON) but the payload contains an application-level error in the error field of exaSearchResponse. The provider surfaces it verbatim so the developer can act on Exa's own error message (e.g. insufficient credits, invalid parameters).
Solutions
- Read the embedded Exa error message — it names the concrete problem (credits, key, policy).
- Check the Exa dashboard for credit balance and billing status.
- Confirm the API key's scopes/permissions cover the search endpoint.
- Adjust the query/parameters if the message indicates a policy or parameter violation.
Example fix
null
Defensive patterns
Strategy: try-catch
Validate before calling
if exaCreditsRemaining <= 0 { return errors.New("exa credits exhausted — skipping search") } Try / catch
if err != nil && strings.HasPrefix(err.Error(), "exa API error:") {
msg := strings.TrimPrefix(err.Error(), "exa API error: ")
log.Warnf("exa rejected the search: %s", msg)
return fallbackProvider.Search(ctx, q, n, false)
} Prevention
- Monitor Exa credit balance programmatically
- Set up billing alerts before quota exhaustion
- Route to a fallback provider when Exa reports app-level errors
- Surface the embedded Exa message to operators instead of swallowing it
When it happens
Trigger: A successful HTTP call where data.Error != "" — Exa reports a logical failure such as exhausted credits, an invalid API key accepted at transport level, or a disallowed query.
Common situations: Exa account out of credits or over quota; key valid but lacking permissions; query violating usage policies; billing lapsed.
Related errors
- exa API returned status
- failed to unmarshal Exa response
- API key is required for Exa provider
- bing API returned status
- duckduckgo API search failed
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/38c88e84ceb104da.
Report an issue: GitHub.
Appendix: source
Thrown at internal/infrastructure/web_search/exa.go:114
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 == "" {
snippet = truncateExaText(content, 500)
}
result := &types.WebSearchResult{Title: item.Title, URL: item.URL, Snippet: snippet, Content: content, Source: "exa"}
if includeDate && item.PublishedDate != "" {
if publishedAt, err := time.Parse(time.RFC3339, item.PublishedDate); err == nil {
result.PublishedAt = &publishedAt
}
}View on GitHub (pinned to 988cbb0330)