{"record":{"id":"61d395bfa15d1f9e","repo":"zylon-ai/private-gpt","slug":"brave-search-api-quota-exhausted-x-ratelimit-rema","errorCode":null,"errorMessage":"Brave Search API quota exhausted (x-ratelimit-remaining=0)","messagePattern":"Brave Search API quota exhausted \\(x-ratelimit-remaining=0\\)","errorType":"exception","errorClass":"QuotaConsumed","httpStatus":null,"severity":"error","filePath":"private_gpt/components/web/web_search/providers/brave.py","lineNumber":228,"sourceCode":"            status = error.get(\"status\")\n            parts = []\n            if code:\n                parts.append(f\"code={code}\")\n            if status:\n                parts.append(f\"status={status}\")\n            if detail:\n                parts.append(f\"detail={detail}\")\n            if parts:\n                return \"; \".join(parts)\n            return str(error)\n        return str(error)\n\n    def _check_response(\n        self, response: ClientResponse, response_data: dict[str, Any]\n    ) -> None:\n        quota_header = response.headers.get(\"x-ratelimit-remaining\")\n        if quota_header is not None and quota_header.strip() == \"0\":\n            raise QuotaConsumed(\n                \"Brave Search API quota exhausted (x-ratelimit-remaining=0)\"\n            )\n\n        status_code = response.status\n        if status_code < 400:\n            return\n\n        error = response_data.get(\"error\", \"Unknown error\")\n        error_message = self._extract_error_message(error)\n        if status_code == 429:\n            logger.debug(f\"Brave Search API rate limit exceeded: {error_message}\")\n            raise RateLimitExceeded(\n                f\"Brave Search API rate limit exceeded: {error_message}\"\n            )\n        elif status_code == 400:\n            raise ValueError(f\"Brave Search API invalid token ({error_message})\")\n        elif status_code >= 500:\n            raise Exception(f\"Brave Search API server error: {error_message}\")","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/web/web_search/providers/brave.py#L210-L246","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"# before\nresults = await provider.make_query(q, num_links)\n\n# after\ntry:\n    results = await provider.make_query(q, num_links)\nexcept QuotaConsumed:\n    await asyncio.sleep(1.1)  # free tier: 1 req/sec\n    results = await provider.make_query(q, num_links)","handlingStrategy":"retry","validationCode":null,"typeGuard":"def is_quota_consumed(exc: BaseException) -> bool:\n    return type(exc).__name__ == 'QuotaConsumed'","tryCatchPattern":"from private_gpt.components.web.web_search.providers.brave import QuotaConsumed\n\ntry:\n    results = await provider.make_query(q, num_links)\nexcept QuotaConsumed:\n    await asyncio.sleep(1.1)  # free tier resets per second\n    results = await provider.make_query(q, num_links)","preventionTips":["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."],"tags":["brave","rate-limit","quota","api"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}