zylon-ai/private-gpt · error · ValueError

Brave Search API invalid token ({error_message})

Error message

Brave Search API invalid token ({error_message})

What it means

ValueError raised when Brave Search returns HTTP 400 and the provider maps it to an invalid-token condition. The Brave API rejects malformed or unauthorized requests with 400 plus an error body; the provider surfaces the body's detail in the message so the exact API complaint is visible.

Source

Thrown at private_gpt/components/web/web_search/providers/brave.py:244

        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}")
        else:
            raise Exception(f"Brave Search API error ({status_code}): {error_message}")

    def _build_request_params(
        self,
        query: str,
        num_links: int,
        offset: int,
        result_filter: str,
        safesearch: bool,
        freshness: str | None,
        spellcheck: bool,
        language: str | None,
    ) -> dict[str, Any]:
        params = {
            "q": query.strip(),

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Verify the key at account.brave.com and re-paste it exactly (no quotes/spaces) into web_search.brave.api_key.
  2. Confirm the key has the Search API product enabled and is not suspended.
  3. Reproduce with curl -H 'X-Subscription-Token: <key>' to see Brave's raw response.
  4. If the body indicates a parameter problem rather than auth, check the query params being sent (country, language, freshness formatting).

Example fix

# verify key before wiring it in
curl -s -o /dev/null -w '%{http_code}' \
  -H 'X-Subscription-Token: $BRAVE_API_KEY' \
  'https://api.search.brave.com/res/v1/web/search?q=test'
Defensive patterns

Strategy: validation

Validate before calling

import aiohttp

async def brave_key_ok(api_key: str) -> bool:
    async with aiohttp.ClientSession() as s:
        async with s.get(
            'https://api.search.brave.com/res/v1/web/search?q=ping',
            headers={'X-Subscription-Token': api_key},
        ) as r:
            return r.status < 400

Type guard

def is_brave_invalid_token(exc: BaseException) -> bool:
    return isinstance(exc, ValueError) and 'Brave Search API invalid token' in str(exc)

Try / catch

try:
    results = await provider.make_query(q, num_links)
except ValueError as e:
    if 'invalid token' in str(e):
        raise RuntimeError('Brave API key rejected — regenerate and update settings') from e
    raise

Prevention

When it happens

Trigger: API key that is wrong, revoked, or contains whitespace/quotes from config templating; a malformed request (bad parameter encoding) that Brave reports as 400; key from a different Brave product (e.g. images/news plan) used on the web search endpoint.

Common situations: Typo'd or expired key pasted into settings.yaml; CI using a placeholder key; key truncated by env-var length limits or shell quoting.

Understand the failure class

Related errors


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/b291d01d4fd7761c. Report an issue: GitHub.