{"record":{"id":"a8c3280ad61acf8b","repo":"CherryHQ/cherry-studio","slug":"brave-api-error-response-status-response-sta","errorCode":null,"errorMessage":"Brave API error: ${response.status} ${response.statusText}\\n${await response.text()}","messagePattern":"Brave API error: (.+?) (.+?)\\\\n(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/mcp/servers/braveSearch.ts","lineNumber":173,"sourceCode":"}\n\nasync function performWebSearch(apiKey: string, query: string, count: number = 10, offset: number = 0) {\n  checkRateLimit()\n  const url = new URL('https://api.search.brave.com/res/v1/web/search')\n  url.searchParams.set('q', query)\n  url.searchParams.set('count', Math.min(count, 20).toString()) // API limit\n  url.searchParams.set('offset', offset.toString())\n\n  const response = await net.fetch(url.toString(), {\n    headers: {\n      Accept: 'application/json',\n      'Accept-Encoding': 'gzip',\n      'X-Subscription-Token': apiKey\n    }\n  })\n\n  if (!response.ok) {\n    throw new Error(`Brave API error: ${response.status} ${response.statusText}\\n${await response.text()}`)\n  }\n\n  const data = (await response.json()) as BraveWeb\n\n  // Extract just web results\n  const results = (data.web?.results || []).map((result) => ({\n    title: result.title || '',\n    description: result.description || '',\n    url: result.url || ''\n  }))\n\n  return results.map((r) => `Title: ${r.title}\\nDescription: ${r.description}\\nURL: ${r.url}`).join('\\n\\n')\n}\n\nasync function performLocalSearch(apiKey: string, query: string, count: number = 5) {\n  checkRateLimit()\n  // Initial search to get location IDs\n  const webUrl = new URL('https://api.search.brave.com/res/v1/web/search')","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/mcp/servers/braveSearch.ts#L155-L191","documentation":"Thrown by performWebSearch after net.fetch to Brave's /res/v1/web/search endpoint returns a non-OK HTTP status. The message concatenates status code, status text, and the raw response body so the upstream Brave API error is fully surfaced. Brave returns 401 for a bad/missing X-Subscription-Token, 429 for rate limiting, 5xx for upstream outages, and 422/400 for malformed/oversized queries.","triggerScenarios":"Any MCP CallTool request for brave_web_search where Brave rejects the request: invalid or expired API key (401), exceeded 1 req/s or 15000/month plan quota (429), query longer than 400 chars / 50 words (422), or transient Brave backend failure (5xx).","commonSituations":"API key typo or copied with whitespace; free-tier key hitting the monthly cap mid-session; an LLM constructing a very long query string; Brave API incident; proxy/corporate network blocking api.search.brave.com.","solutions":["Inspect the surfaced status code in the error message — 401 means fix BRAVE_API_KEY, 429 means back off / upgrade plan, 422 means shorten the query.","Verify the key in the MCP server env config (the same envs.BRAVE_API_KEY passed to BraveSearchServer) is a valid active Brave Search API subscription token.","For 429/5xx, retry with exponential backoff; checkRateLimit() already gates per-second/per-month counts locally but not server-enforced 429s.","Trim the query to <=400 chars and <=50 words before calling performWebSearch.","Check https://status.search.brave.com for ongoing Brave API outages."],"exampleFix":"// before\nurl.searchParams.set('q', query)\nconst response = await net.fetch(url.toString(), { headers })\nif (!response.ok) {\n  throw new Error(`Brave API error: ${response.status} ...`)\n}\n\n// after — retry transient failures, fail fast on auth errors\nif (response.status === 401 || response.status === 403) {\n  throw new Error(`Brave auth failed (${response.status}); check BRAVE_API_KEY`)\n}\nif (response.status === 429 || response.status >= 500) {\n  throw new RetryableError(`Brave transient ${response.status}`) // retry upstream\n}\nif (!response.ok) {\n  throw new Error(`Brave API error: ${response.status} ${response.statusText}\\n${await response.text()}`)\n}","handlingStrategy":"try-catch","validationCode":"// Validate inputs before calling brave_web_search\nfunction validateWebSearchArgs(args) {\n  if (typeof args.query !== 'string' || !args.query.trim()) throw new Error('query required')\n  if (args.query.length > 400) throw new Error('query must be <= 400 chars')\n  const c = args.count ?? 10\n  if (typeof c !== 'number' || c < 1 || c > 20) throw new Error('count must be 1-20')\n  return { query: args.query.trim(), count: Math.min(c, 20) }\n}","typeGuard":"function isWebArgs(a): a is { query: string; count?: number } {\n  return typeof a === 'object' && a !== null && typeof a.query === 'string' && a.query.length > 0 && a.query.length <= 400\n}","tryCatchPattern":"// Retry transient HTTP statuses; fail fast on auth/validation\nasync function braveWebSearchWithRetry(apiKey, query, count, tries = 3) {\n  for (let i = 0; i < tries; i++) {\n    try {\n      return await performWebSearch(apiKey, query, count)\n    } catch (e) {\n      const msg = String(e.message || e)\n      const status = parseInt((msg.match(/Brave API error: (\\d+)/) || [])[1] || '0', 10)\n      if (status === 401 || status === 403 || status === 422) throw e // non-retryable\n      if (i === tries - 1) throw e\n      await new Promise((r) => setTimeout(r, 2 ** i * 500))\n    }\n  }\n}","preventionTips":["Confirm BRAVE_API_KEY is valid before enabling the server (one test call).","Cap query length client-side at 400 chars and 50 words.","Retry only on 429/5xx; surface 401/422 immediately.","Monitor monthly quota to avoid surprise 429s."],"tags":["network","api","brave-search","http"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}