{"record":{"id":"2e5bc5feef9877a1","repo":"vxcontrol/pentagi","slug":"api-key-is-wrong-2e5bc5","errorCode":null,"errorMessage":"API key is wrong","messagePattern":"API key is wrong","errorType":"http","errorClass":"Fatal","httpStatus":401,"severity":"critical","filePath":"backend/pkg/tools/searchers/tavily.go","lineNumber":152,"sourceCode":"\t\treturn \"\", Retryable(fmt.Errorf(\"failed to do request: %v\", err), 0)\n\t}\n\tdefer resp.Body.Close()\n\n\treturn t.parseHTTPResponse(ctx, resp)\n}\n\nfunc (t *tavily) parseHTTPResponse(ctx context.Context, resp *http.Response) (string, error) {\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar respBody tavilySearchResult\n\t\tif err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil {\n\t\t\treturn \"\", Fatal(fmt.Errorf(\"failed to decode response body: %v\", err))\n\t\t}\n\t\treturn t.buildTavilyResult(ctx, &respBody), nil\n\tcase http.StatusBadRequest:\n\t\treturn \"\", Fatal(fmt.Errorf(\"request is invalid\"))\n\tcase http.StatusUnauthorized:\n\t\treturn \"\", Fatal(fmt.Errorf(\"API key is wrong\"))\n\tcase http.StatusForbidden:\n\t\treturn \"\", Fatal(fmt.Errorf(\"the endpoint requested is hidden for administrators only\"))\n\tcase http.StatusNotFound:\n\t\treturn \"\", Fatal(fmt.Errorf(\"the specified endpoint could not be found\"))\n\tcase http.StatusMethodNotAllowed:\n\t\treturn \"\", Fatal(fmt.Errorf(\"there need to try to access an endpoint with an invalid method\"))\n\tcase http.StatusTooManyRequests:\n\t\treturn \"\", Retryable(fmt.Errorf(\"there are requesting too many results\"), 0)\n\tcase http.StatusInternalServerError:\n\t\treturn \"\", Retryable(fmt.Errorf(\"there had a problem with our server. try again later\"), 0)\n\tcase http.StatusBadGateway:\n\t\treturn \"\", Retryable(fmt.Errorf(\"there was a problem with the server. Please try again later\"), 0)\n\tcase http.StatusServiceUnavailable:\n\t\treturn \"\", Retryable(fmt.Errorf(\"there are temporarily offline for maintenance. please try again later\"), 0)\n\tcase http.StatusGatewayTimeout:\n\t\treturn \"\", Retryable(fmt.Errorf(\"there are temporarily offline for maintenance. please try again later\"), 0)\n\tdefault:\n\t\treturn \"\", Fatal(fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode))","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/tools/searchers/tavily.go#L134-L170","documentation":"Thrown in tavily.parseHTTPResponse when Tavily answers HTTP 401 Unauthorized, mapped to \"API key is wrong\". Tavily authenticates via the api_key field of the JSON body (tavilyRequest.ApiKey), so this means the key sent is missing at the API level, revoked, expired, or malformed. Classified Fatal — retrying with the same credentials always fails.","triggerScenarios":"POST to api.tavily.com/search with tavilyRequest.ApiKey set to an empty (shouldn't happen — IsAvailable guards), stale, mistyped, or revoked key in cfg.TavilyAPIKey, producing a 401 from Tavily.","commonSituations":"TAVILY_API_KEY env var copied with quotes/whitespace/trailing newline from .env; key regenerated in the Tavily dashboard after the free-tier reset; .env not remounted into the running container after a key rotation; extra characters like 'Bearer ' pasted into the key.","solutions":["Verify TAVILY_API_KEY in the container env (docker compose exec backend env | grep TAVILY) matches the current key from the Tavily dashboard — restart the container after .env changes.","Re-copy the key without surrounding quotes, spaces, or newline characters.","Regenerate the key in the Tavily dashboard if it was revoked, then update .env and restart.","Confirm the key with a minimal curl: curl -s -X POST api.tavily.com/search -d '{\"api_key\":\"tvly-...\",\"query\":\"test\"}' and check for 200.","Check Tavily account status — suspended or over-quota accounts can also reject authentication."],"exampleFix":"// before (.env)\nTAVILY_API_KEY=\"tvly-abc123 \"   // quotes + trailing space -> 401\n// after (.env)\nTAVILY_API_KEY=tvly-abc123","handlingStrategy":"validation","validationCode":"key := strings.TrimSpace(cfg.TavilyAPIKey)\nif key == \"\" || !strings.HasPrefix(key, \"tvly-\") {\n    return \"\", ErrNotConfigured // don't even attempt the call\n}","typeGuard":null,"tryCatchPattern":"if err != nil {\n    if strings.Contains(err.Error(), \"API key is wrong\") {\n        // fatal config problem: alert an operator, do not retry\n        logger.Error(\"TAVILY_API_KEY rejected by api.tavily.com\")\n    }\n    return err\n}","preventionTips":["Set TAVILY_API_KEY without quotes/whitespace in .env and restart containers after rotation.","Validate the key once at startup with a cheap curl or status call, not on the first user query.","Track key expiry/rotation in the Tavily dashboard calendar.","Keep the key only in .env/secret manager; grep for stray 'Bearer ' prefixes when pasting."],"tags":["authentication","api-key","http-401","searchers","config"],"backgroundTag":"invalid-api-key","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}