zylon-ai/private-gpt · error · QuotaConsumed
Brave Search API quota exhausted (x-ratelimit-remaining=0)
Error message
Brave Search API quota exhausted (x-ratelimit-remaining=0)
What it means
QuotaConsumed raised in BraveSearchProvider._check_response when the response header x-ratelimit-remaining equals '0'. Brave's API attaches per-query rate limit headers; remaining=0 means the allowance (1 query/sec on the free plan, or the monthly quota) is exhausted. It is raised before any status-code handling, so even a 200 response with remaining=0 triggers it.
Source
Thrown at private_gpt/components/web/web_search/providers/brave.py:228
status = error.get("status")
parts = []
if code:
parts.append(f"code={code}")
if status:
parts.append(f"status={status}")
if detail:
parts.append(f"detail={detail}")
if parts:
return "; ".join(parts)
return str(error)
return str(error)
def _check_response(
self, response: ClientResponse, response_data: dict[str, Any]
) -> None:
quota_header = response.headers.get("x-ratelimit-remaining")
if quota_header is not None and quota_header.strip() == "0":
raise QuotaConsumed(
"Brave Search API quota exhausted (x-ratelimit-remaining=0)"
)
status_code = response.status
if status_code < 400:
return
error = response_data.get("error", "Unknown error")
error_message = self._extract_error_message(error)
if status_code == 429:
logger.debug(f"Brave Search API rate limit exceeded: {error_message}")
raise RateLimitExceeded(
f"Brave Search API rate limit exceeded: {error_message}"
)
elif status_code == 400:
raise ValueError(f"Brave Search API invalid token ({error_message})")
elif status_code >= 500:
raise Exception(f"Brave Search API server error: {error_message}")View on GitHub (pinned to 4a030776a3)
Solutions
- Back off and retry after the reset window (1s for rate-limit headers; monthly quota resets on billing cycle).
- Enable web_search.cached to dedupe repeated queries.
- Respect/raise brave.rate_limit spacing and cap num_concurrent_consumers to serialize Brave calls.
- Upgrade the Brave plan or rotate to a key with remaining quota.
Example fix
# before
results = await provider.make_query(q, num_links)
# after
try:
results = await provider.make_query(q, num_links)
except QuotaConsumed:
await asyncio.sleep(1.1) # free tier: 1 req/sec
results = await provider.make_query(q, num_links) Defensive patterns
Strategy: retry
Type guard
def is_quota_consumed(exc: BaseException) -> bool:
return type(exc).__name__ == 'QuotaConsumed' Try / catch
from private_gpt.components.web.web_search.providers.brave import QuotaConsumed
try:
results = await provider.make_query(q, num_links)
except QuotaConsumed:
await asyncio.sleep(1.1) # free tier resets per second
results = await provider.make_query(q, num_links) Prevention
- Serialize Brave calls with the configured brave.rate_limit spacing.
- Enable web_search.cached to avoid burning quota on repeated queries.
- Monitor the x-ratelimit-remaining header and shed search traffic near zero.
When it happens
Trigger: Free-tier key limited to 1 request/second firing two searches back to back; exhausting the monthly free-query quota; concurrency > 1 hitting Brave despite the rate_limit setting (minimum seconds between requests).
Common situations: Bursty traffic (multiple chat sessions searching simultaneously); cached=false so every query hits the API; keys on the free plan used in shared/staging environments burning the monthly allowance.
Related errors
- Brave Search API rate limit exceeded: {error_message}
- Brave Search API error ({status_code}): {error_message}
- Invalid page token
- Brave Search API key is not configured
- Search query cannot be empty
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/61d395bfa15d1f9e.
Report an issue: GitHub.