crewAIInc/crewAI · error · RuntimeError

Brave Search API error (HTTP {status}): {body}

Error message

Brave Search API error (HTTP {status}): {body}

What it means

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).

Source

Thrown at lib/crewai-tools/src/crewai_tools/tools/brave_search_tool/base.py:48

def _parse_error_body(resp: requests.Response) -> dict[str, Any] | None:
    """Extract the structured "error" object from a Brave API error response."""
    try:
        body = resp.json()
        error = body.get("error")
        return error if isinstance(error, dict) else None
    except (ValueError, KeyError):
        return None


def _raise_for_error(resp: requests.Response) -> None:
    """Brave Search API error responses contain helpful JSON payloads"""
    status = resp.status_code
    try:
        body = json.dumps(resp.json())
    except (ValueError, KeyError):
        body = resp.text[:500]

    raise RuntimeError(f"Brave Search API error (HTTP {status}): {body}")


def _is_retryable(resp: requests.Response) -> bool:
    """Return True for transient failures that are worth retrying.

    * 429 + RATE_LIMITED — the per-second sliding window is full.
    * 5xx — transient server-side errors.

    Quota exhaustion (QUOTA_LIMITED, USAGE_LIMIT_EXCEEDED) is
    explicitly excluded: retrying will never succeed until the billing
    period resets.
    """
    if resp.status_code == 429:
        error = _parse_error_body(resp) or {}
        return error.get("code") not in _QUOTA_CODES
    return 500 <= resp.status_code < 600


View on GitHub (pinned to 754d7323be)

Solutions

  1. 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.
  2. For 401/422, regenerate the API key in the Brave dashboard and re-export BRAVE_API_KEY.
  3. If 5xx persists across retries, check https://status.brave.com and retry later with backoff.
  4. Reduce call volume: cache query results and reuse one tool instance so its per-instance rate limiter is effective.

Example fix

# before
results = tool._run("query")

# after
try:
    results = tool._run("query")
except RuntimeError as e:
    if "QUOTA_LIMITED" in str(e) or "USAGE_LIMITED" in str(e):
        results = cached_results  # quota errors never succeed on retry
    else:
        raise
Defensive patterns

Strategy: try-catch

Type guard

def is_fatal_brave_error(err: RuntimeError) -> bool:
    msg = str(err)
    return "QUOTA_LIMITED" in msg or "USAGE_LIMITED" in msg or "HTTP 401" in msg or "HTTP 422" in msg

Try / catch

try:
    result = tool._run(query)
except RuntimeError as e:
    if is_fatal_brave_error(e):
        return fallback_results(query)  # quota/auth errors never succeed on retry
    if "HTTP 5" in str(e):
        time.sleep(5)  # tool already retried; one outer backoff for server errors
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15). Data as JSON: /api/errors/ceb26eedf4c9c2e1. Report an issue: GitHub.