Mintplex-Labs/anything-llm · error · Error
${res.status} - ${res.statusText}
Error message
${res.status} - ${res.statusText} What it means
After POSTing the search query to the Keenable endpoint, the plugin checks res.ok; any non-2xx HTTP response causes it to throw an Error formatted as '<status> - <statusText>' (e.g. '401 - Unauthorized'). The thrown message is caught locally and returned to the agent as 'There was an error searching for content. <status> - <statusText>'. It is a thin wrapper that surfaces the upstream Keenable API's rejection of the request.
Solutions
- Check the numeric status in the message: 401/403 means fix AGENT_KEENABLE_API_KEY (correct, non-expired key for the /v1/search endpoint); 404 means AGENT_KEENABLE_API_URL points to a host that doesn't serve the Keenable search routes; 429 means back off/retry later; 5xx means the upstream service is down.
- If you don't need the keyed endpoint, unset AGENT_KEENABLE_API_KEY so the plugin falls back to the keyless /v1/search/public route.
- Curl the endpoint directly to reproduce: POST {baseUrl}/v1/search (or /v1/search/public) with the same headers and body {"query":"test","mode":"pro"} and inspect the response body for details the wrapper discards.
- If running a self-hosted proxy, verify it proxies POST /v1/search and /v1/search/public to Keenable unchanged, including the X-API-Key header.
- Retry the search after addressing the cause — the plugin already catches the throw and degrades to an error string, so no crash occurs.
Example fix
// before (diagnose the raw status)
throw new Error(`${res.status} - ${res.statusText}`);
// after (surface upstream detail)
const body = await res.text();
throw new Error(`${res.status} - ${res.statusText}: ${body.slice(0, 200)}`); Defensive patterns
Strategy: retry
Validate before calling
async function keenableReachable(baseUrl, apiKey) {
const path = apiKey ? "/v1/search" : "/v1/search/public";
const headers = { "Content-Type": "application/json" };
if (apiKey) headers["X-API-Key"] = apiKey;
const res = await fetch(`${baseUrl}${path}`, {
method: "POST",
headers,
body: JSON.stringify({ query: "ping", mode: "pro" }),
});
if (!res.ok) throw new Error(`Keenable endpoint unhealthy: ${res.status} - ${res.statusText}`);
return true;
} Type guard
function isOkResponse(res) {
return res instanceof Response && res.ok && res.status >= 200 && res.status < 300;
} Try / catch
const MAX_RETRIES = 3;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
return await agent._keenableSearch(query);
} catch (e) {
const status = parseInt(e.message, 10);
const retryable = status === 429 || status >= 500;
if (!retryable || attempt === MAX_RETRIES) {
console.error(`Keenable search failed: ${e.message}`);
return null;
}
await new Promise((r) => setTimeout(r, 2 ** attempt * 500));
}
} Prevention
- Verify AGENT_KEENABLE_API_KEY is valid and present whenever you expect the keyed /v1/search endpoint to be used.
- Smoke-test the configured AGENT_KEENABLE_API_URL host with curl before deploying; confirm it implements the search route.
- Log and alert on the status code (401/403 = credentials, 404 = wrong base URL, 429 = throttle, 5xx = outage) instead of treating all failures alike.
- Back off and retry on 429/5xx; fail fast on 4xx client errors.
- Watch Keenable status/uptime to anticipate 5xx outages.
When it happens
Trigger: The fetch to `${baseUrl}${path}` (either /v1/search with X-API-Key when AGENT_KEENABLE_API_KEY is set, or /v1/search/public) returns a non-2xx status: 401/403 for an invalid or missing API key on the keyed endpoint, 404 when AGENT_KEENABLE_API_URL points to a host without the search route, 429 on rate limiting, or 5xx from an outage/misconfigured proxy.
Common situations: Expired or wrong AGENT_KEENABLE_API_KEY, self-hosted proxy at AGENT_KEENABLE_API_URL that doesn't implement /v1/search or /v1/search/public, rate-limit throttling under heavy agent traffic, or Keenable service downtime returning 502/503.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- An error occurred while deleting the model
- AnthropicLLM::getChatCompletion failed to communicate with…
- API Call failed
- Bad Request
- Cannot create: maximum of
AI-assisted analysis of Mintplex-Labs/anything-llm@a145d4d87d (2026-09-15).
Data as JSON: /api/errors/c7e9c704901d9372.
Report an issue: GitHub.
Appendix: source
Thrown at server/utils/agents/aibitat/plugins/web-browsing.js:1402
const headers = {
"Content-Type": "application/json",
"User-Agent": "keenable-anythingllm",
"X-Keenable-Title": getAnythingLLMUserAgent(),
};
// Keyless public endpoint by default; keyed endpoint + X-API-Key
// when a key is configured.
const path = apiKey ? "/v1/search" : "/v1/search/public";
if (apiKey) headers["X-API-Key"] = apiKey;
const { response, error } = await fetch(`${baseUrl}${path}`, {
method: "POST",
headers,
body: JSON.stringify({ query: String(query), mode: "pro" }),
})
.then((res) => {
if (res.ok) return res.json();
throw new Error(`${res.status} - ${res.statusText}`);
})
.then((data) => {
return { response: data, error: null };
})
.catch((e) => {
this.super.handlerProps.log(
`Keenable Search Error: ${e.message}`
);
return { response: null, error: e.message };
});
if (error)
return `There was an error searching for content. ${error}`;
const data = [];
response.results?.forEach((searchResult) => {
const { title, url, description, snippet } = searchResult;
// Keenable returns both fields: `snippet` carries the page text and
// `description` is the page's meta description, which is empty forView on GitHub (pinned to a145d4d87d)