moeru-ai/airi · error · Error
web search failed: tavily
Error message
web search failed: tavily ${response.status}${detail ? `: ${detail}` : ''} What it means
The web-search tool called Tavily's REST endpoint with the configured bearer key and got a non-2xx; the message embeds the status plus up to 200 chars of body so the model and logs see the upstream reason without payload dumps.
Solutions
- Check the Tavily key in provider settings and re-save it cleanly (no whitespace).
- Verify quota/billing on the Tavily dashboard.
- Classify by embedded status: 401/403 = key, 429/432 = quota, 400 = arguments.
- Retry with backoff only for 429/5xx.
Defensive patterns
Strategy: try-catch
Validate before calling
const apiKey = providerConfig.apiKey?.trim()
if (!apiKey) {
return { results: [], error: 'Tavily API key not configured' }
} Try / catch
try {
const results = await webSearchTavily(query, { apiKey, signal })
}
catch (e) {
const msg = errorMessageFrom(e) ?? ''
if (msg.includes('tavily 401'))
configureProvider()
else if (msg.includes('tavily 429') || msg.includes('tavily 432'))
await delay(60_000) // then retry once
else
throw e
} Prevention
- Trim and validate the API key before first use.
- Run a cheap warm-up call at settings-save time to fail fast on bad keys.
- Log the status code separately from the body for alerting.
When it happens
Trigger: 401/403 invalid or revoked Tavily API key; 429 (or 432) quota/rate limit exhausted; 400 malformed query arguments; region-blocked access.
Common situations: Key pasted with trailing whitespace/newline; expired free-tier quota; one key shared across environments hitting rate limits; a proxy in front of api.tavily.com rejecting the request.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Gemini TTS request failed
- MiMo TTS request failed
- web search failed: tavily returned a non-JSON response
- [chat-session] persist task failed:
- Failed to add provider
AI-assisted analysis of moeru-ai/airi@b6d0809ecb (2026-08-18).
Data as JSON: /api/errors/f492f64635d18937.
Report an issue: GitHub.
Appendix: source
Thrown at packages/stage-ui/src/tools/web-search.ts:143
body.include_domains = input.include_domains
if (input.exclude_domains?.length)
body.exclude_domains = input.exclude_domains
const response = await fetch(TAVILY_SEARCH_URL, {
method: 'POST',
headers: {
'content-type': 'application/json',
'authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify(body),
signal,
})
if (!response.ok) {
// Slice the body so a failing endpoint never dumps a full payload into the
// model context or logs.
const detail = (await response.text().catch(() => '')).slice(0, 200)
throw new Error(`web search failed: tavily ${response.status}${detail ? `: ${detail}` : ''}`)
}
// A 2xx with a non-JSON body (an HTML proxy/error page, a truncated response)
// would otherwise throw an opaque SyntaxError; surface it in the same taxonomy.
let json: { results?: Array<{ title?: string, url?: string, content?: string, score?: number, published_date?: string }> }
try {
json = await response.json()
}
catch {
throw new Error('web search failed: tavily returned a non-JSON response')
}
// Guard the shape before mapping: a 2xx whose `results` is missing or not an
// array is treated as "no results" rather than throwing on `.map`.
const results = Array.isArray(json.results) ? json.results : []
return results.map(result => ({
title: result.title ?? '',
url: result.url ?? '',View on GitHub (pinned to b6d0809ecb)