{"record":{"id":"ceb26eedf4c9c2e1","repo":"crewAIInc/crewAI","slug":"brave-search-api-error-http-status-body","errorCode":null,"errorMessage":"Brave Search API error (HTTP {status}): {body}","messagePattern":"Brave Search API error \\(HTTP (.+?)\\): (.+?)","errorType":"http","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"lib/crewai-tools/src/crewai_tools/tools/brave_search_tool/base.py","lineNumber":48,"sourceCode":"def _parse_error_body(resp: requests.Response) -> dict[str, Any] | None:\r\n    \"\"\"Extract the structured \"error\" object from a Brave API error response.\"\"\"\r\n    try:\r\n        body = resp.json()\r\n        error = body.get(\"error\")\r\n        return error if isinstance(error, dict) else None\r\n    except (ValueError, KeyError):\r\n        return None\r\n\r\n\r\ndef _raise_for_error(resp: requests.Response) -> None:\r\n    \"\"\"Brave Search API error responses contain helpful JSON payloads\"\"\"\r\n    status = resp.status_code\r\n    try:\r\n        body = json.dumps(resp.json())\r\n    except (ValueError, KeyError):\r\n        body = resp.text[:500]\r\n\r\n    raise RuntimeError(f\"Brave Search API error (HTTP {status}): {body}\")\r\n\r\n\r\ndef _is_retryable(resp: requests.Response) -> bool:\r\n    \"\"\"Return True for transient failures that are worth retrying.\r\n\r\n    * 429 + RATE_LIMITED — the per-second sliding window is full.\r\n    * 5xx — transient server-side errors.\r\n\r\n    Quota exhaustion (QUOTA_LIMITED, USAGE_LIMIT_EXCEEDED) is\r\n    explicitly excluded: retrying will never succeed until the billing\r\n    period resets.\r\n    \"\"\"\r\n    if resp.status_code == 429:\r\n        error = _parse_error_body(resp) or {}\r\n        return error.get(\"code\") not in _QUOTA_CODES\r\n    return 500 <= resp.status_code < 600\r\n\r\n\r","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/crewai-tools/src/crewai_tools/tools/brave_search_tool/base.py#L30-L66","documentation":"BraveSearchTool's _raise_for_error() builds the final error for non-OK Brave API responses: it embeds the HTTP status and, when possible, the JSON error body (Brave returns helpful error payloads); if the body is not JSON it falls back to the first 500 chars of text. It is raised after retries are exhausted for retryable errors, or immediately for non-retryable ones (auth failures, quota exhaustion).","triggerScenarios":"422/401 from an invalid or revoked BRAVE_API_KEY; 429 RATE_LIMITED persisting beyond the retry window; QUOTA_LIMITED / USAGE_LIMIT_EXCEEDED (explicitly non-retryable); 5xx that keeps failing across all retry attempts.","commonSituations":"Expired or wrong-plan API key; free tier monthly quota used up; sustained traffic above the per-second rate so every retry still lands in the full window; Brave-side incidents.","solutions":["Read the embedded body: RATE_LIMITED means slow down (lower requests_per_second), QUOTA_LIMITED/USAGE_LIMITED means wait for the billing reset or upgrade the plan.","For 401/422, regenerate the API key in the Brave dashboard and re-export BRAVE_API_KEY.","If 5xx persists across retries, check https://status.brave.com and retry later with backoff.","Reduce call volume: cache query results and reuse one tool instance so its per-instance rate limiter is effective."],"exampleFix":"# before\nresults = tool._run(\"query\")\n\n# after\ntry:\n    results = tool._run(\"query\")\nexcept RuntimeError as e:\n    if \"QUOTA_LIMITED\" in str(e) or \"USAGE_LIMITED\" in str(e):\n        results = cached_results  # quota errors never succeed on retry\n    else:\n        raise","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def is_fatal_brave_error(err: RuntimeError) -> bool:\n    msg = str(err)\n    return \"QUOTA_LIMITED\" in msg or \"USAGE_LIMITED\" in msg or \"HTTP 401\" in msg or \"HTTP 422\" in msg","tryCatchPattern":"try:\n    result = tool._run(query)\nexcept RuntimeError as e:\n    if is_fatal_brave_error(e):\n        return fallback_results(query)  # quota/auth errors never succeed on retry\n    if \"HTTP 5\" in str(e):\n        time.sleep(5)  # tool already retried; one outer backoff for server errors\n    raise","preventionTips":["Parse the embedded JSON body string to branch: rate-limit vs quota vs auth vs 5xx each need different responses.","Monitor quota consumption in the Brave dashboard and alert before exhaustion.","Never retry QUOTA_LIMITED/USAGE_LIMITED — the tool deliberately excludes them from retryability."],"tags":["brave-search","http","api-error","rate-limiting","quota"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}