{"record":{"id":"38c88e84ceb104da","repo":"Tencent/WeKnora","slug":"exa-api-error-s","errorCode":null,"errorMessage":"exa API error: %s","messagePattern":"exa API error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/infrastructure/web_search/exa.go","lineNumber":114,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to execute Exa request: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, maxExaResponseBytes))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read Exa response: %w\", err)\n\t}\n\tif resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {\n\t\tlogger.Warnf(ctx, \"[WebSearch][Exa] API returned status %d: %s\", resp.StatusCode, string(body))\n\t\treturn nil, fmt.Errorf(\"exa API returned status %d: %s\", resp.StatusCode, string(body))\n\t}\n\n\tvar data exaSearchResponse\n\tif err := json.Unmarshal(body, &data); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal Exa response: %w\", err)\n\t}\n\tif data.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"exa API error: %s\", data.Error)\n\t}\n\n\tresults := make([]*types.WebSearchResult, 0, len(data.Results))\n\tfor _, item := range data.Results {\n\t\tif len(results) >= maxResults {\n\t\t\tbreak\n\t\t}\n\t\tsnippet := strings.TrimSpace(strings.Join(item.Highlights, \"\\n\"))\n\t\tcontent := truncateExaText(strings.TrimSpace(item.Text), maxExaContentRunes)\n\t\tif snippet == \"\" {\n\t\t\tsnippet = truncateExaText(content, 500)\n\t\t}\n\t\tresult := &types.WebSearchResult{Title: item.Title, URL: item.URL, Snippet: snippet, Content: content, Source: \"exa\"}\n\t\tif includeDate && item.PublishedDate != \"\" {\n\t\t\tif publishedAt, err := time.Parse(time.RFC3339, item.PublishedDate); err == nil {\n\t\t\t\tresult.PublishedAt = &publishedAt\n\t\t\t}\n\t\t}","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/infrastructure/web_search/exa.go#L96-L132","documentation":"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).","triggerScenarios":"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.","commonSituations":"Exa account out of credits or over quota; key valid but lacking permissions; query violating usage policies; billing lapsed.","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."],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":"if exaCreditsRemaining <= 0 { return errors.New(\"exa credits exhausted — skipping search\") }","typeGuard":null,"tryCatchPattern":"if err != nil && strings.HasPrefix(err.Error(), \"exa API error:\") {\n    msg := strings.TrimPrefix(err.Error(), \"exa API error: \")\n    log.Warnf(\"exa rejected the search: %s\", msg)\n    return fallbackProvider.Search(ctx, q, n, false)\n}","preventionTips":["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"],"tags":["api","web-search","exa","quota","application-error"],"backgroundTag":"api-quota-exceeded","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}