{"record":{"id":"38170c09489ed2c7","repo":"Tencent/WeKnora","slug":"exa-api-returned-status-d-s","errorCode":null,"errorMessage":"exa API returned status %d: %s","messagePattern":"exa API returned status (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/infrastructure/web_search/exa.go","lineNumber":106,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create Exa request: %w\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"x-api-key\", p.apiKey)\n\n\tlogger.Infof(ctx, \"[WebSearch][Exa] query=%q maxResults=%d url=%s\", query, maxResults, p.baseURL)\n\tresp, err := p.client.Do(req)\n\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 == \"\" {","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/infrastructure/web_search/exa.go#L88-L124","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Verify the Exa API key configured for the provider is valid and active.","Add retry with exponential backoff for transient 429/5xx statuses.","Check https://status.exa.ai or Exa changelogs if 4xx/5xx persists with a valid key."],"exampleFix":"// before\nresults, err := exaProvider.Search(ctx, query, 10, false)\nif err != nil { return err }\n// after\nresults, err := exaProvider.Search(ctx, query, 10, false)\nif err != nil {\n    var respErr *fmt.Errorf\n    if strings.Contains(err.Error(), \"status 429\") {\n        time.Sleep(2 * time.Second)\n        results, err = exaProvider.Search(ctx, query, 10, false)\n    }\n    if err != nil { return fmt.Errorf(\"web search failed: %w\", err) }\n}","handlingStrategy":"retry","validationCode":"if exaAPIKey == \"\" { return errors.New(\"exa API key missing — request will 401\") }","typeGuard":null,"tryCatchPattern":"results, err := provider.Search(ctx, q, n, false)\nif err != nil {\n    if strings.Contains(err.Error(), \"status 429\") || strings.Contains(err.Error(), \"status 5\") {\n        // retry with exponential backoff\n    }\n    return fmt.Errorf(\"exa search failed: %w\", err)\n}","preventionTips":["Rotate and verify API keys before deployment","Wrap search calls with retry+backoff for 429/5xx","Monitor Exa quota usage and alert before exhaustion","Log full status and body on upstream failure for fast triage"],"tags":["http","api","web-search","exa","upstream-error"],"backgroundTag":"upstream-api-non-2xx","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}