chatboxai/chatbox · error · Error

Web search failed with status ${response.status}

Error message

Web search failed with status ${response.status}

What it means

Thrown by searchNativeTavily (src/shared/services/native-web-search.ts:263) when POST ${apiHost||https://api.tavily.com}/search returns a non-2xx status. The body is { query, search_depth:'basic', include_domains:[], exclude_domains:[], max_results? } with Authorization: Bearer <apiKey>. The throw happens before body parsing, so only the raw HTTP status is reported. Unlike Querit, Tavily has no separate business error_code path, so any non-ok response is a hard failure.

Source

Thrown at src/shared/services/native-web-search.ts:263

    search_depth: 'basic',
    include_domains: [],
    exclude_domains: [],
  }
  if (maxResults !== undefined) {
    body.max_results = maxResults
  }

  const response = await fetchFn(`${host}/search`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${options.apiKey ?? ''}`,
    },
    body: JSON.stringify(body),
    signal: options.signal,
  })
  if (!response.ok) {
    throw new Error(`Web search failed with status ${response.status}`)
  }

  const payload = (await response.json()) as { results?: TavilyResponseItem[] }
  const results = Array.isArray(payload.results) ? payload.results : []
  // No link filtering: matches the old renderer Tavily provider, which returned every
  // result Tavily sent (link-less items included) for the model to use.
  const items = results.map((item) => ({
    title: item.title ?? '',
    link: item.url ?? '',
    snippet: item.content ?? '',
  }))
  return maxResults !== undefined ? items.slice(0, maxResults) : items
}

/**
 * Chatbox build-in search — single implementation behind the renderer's `build-in`
 * provider (which injects an afetch `fetchFn` for retry + Chatbox error parsing, plus the
 * Chatbox platform `headers`) and the native shell. `POST /api/tool/web-search`, license

View on GitHub (pinned to 81571269ad)

Solutions

  1. Confirm options.apiKey is set, trimmed, and matches a valid Tavily key with remaining quota.
  2. Leave options.apiHost empty to use the default https://api.tavily.com, or ensure a custom host implements POST /search with Tavily's contract.
  3. Catch the error and fall back to another provider or return no results so the chat continues.
  4. On 429, surface 'quota exceeded' to the user rather than silently retrying.
  5. Strip whitespace/newlines from the key before storing it in settings.

Example fix

// before
const host = settings.apiHost
const items = await searchNativeTavily(query, { apiKey: settings.apiKey, apiHost: host })

// after
const apiKey = settings.apiKey.trim()
if (!apiKey) return []
try {
  return await searchNativeTavily(query, { apiKey, apiHost: settings.apiHost })
} catch (e) {
  console.warn('tavily search failed', e)
  return []
}
Defensive patterns

Strategy: validation

Validate before calling

// Block the call before it reaches Tavily when there is no usable key
const apiKey = (settings.apiKey ?? '').trim()
if (settings.provider === 'tavily' && !apiKey) return []

Try / catch

// Fall back to another provider on failure
try {
  return await searchNativeTavily(query, { apiKey, apiHost, signal })
} catch (e) {
  console.warn('tavily failed', e)
  return []
}

Prevention

When it happens

Trigger: Tavily POST returns response.ok === false: 401 for missing/invalid apiKey (an empty key sends `Bearer `), 429 for quota/rate limit (Tavily free tier is small), 5xx outage, or a wrong apiHost override returning a non-2xx. Aborting options.signal mid-flight can also surface here.

Common situations: User picked Tavily without entering a key; free-tier quota exhausted after a few searches; options.apiHost customized to a wrong/mocked host that does not implement /search; Tavily regional block; key pasted with stray whitespace/newline.

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/a65cc0d920822d08. Report an issue: GitHub.