Web Providers Reference
This document describes web_search's currently supported search providers, provider selection behavior, configuration, and error boundaries. For Web tools parameters and return structures, see web-tools.md; for complete configuration, see configuration.md.
Provider overview
Providers are the search backend adapters for web_search. The current provider registry is at src/modules/web/providers/registry.ts, and provider selection is at src/modules/web/providers/select-provider.ts.
Current providers:
ddgs, brave, tavily, serper, openserp, searxngProviders are only used for web_search. fetch_content's Jina Reader fallback is not a regular search provider; see Jina fallback below.
Provider implementations follow the architecture consistency policy: each search provider should use the shared provider adapter interface, provider registry, selection flow, configuration naming pattern, and provider-focused tests unless a documented exception exists.
Provider network safety
All provider endpoint requests use the shared pinned DNS fetch path. The request URL must be HTTP(S), is validated against web.allowPrivateNetwork, and is pinned at connection time after DNS resolution. With the default web.allowPrivateNetwork=false, provider baseUrl values pointing to localhost, loopback, link-local, private IP ranges, private hostnames, or DNS results resolving to private addresses are rejected before fetch is called.
Set web.allowPrivateNetwork=true only when intentionally querying a trusted local or self-hosted provider, such as a local SearXNG instance.
Provider matrix
| Provider | API key | Config key | Environment variable | Selection availability | Primary use | Limitations |
|---|---|---|---|---|---|---|
ddgs | Not needed | No provider sub-config | None | Always enabled; adapter isAvailable() always returns true | Zero-config DuckDuckGo Lite fallback | Max 5 results per request; depends on DuckDuckGo Lite HTML structure |
brave | Required | web.brave.* | Default BRAVE_SEARCH_API_KEY, customizable via web.brave.apiKeyEnv | Requires web.brave.enabled=true, valid baseUrl, and key | Brave Search API | Third-party rate limit/API behavior may change |
tavily | Required | web.tavily.* | Default TAVILY_API_KEY, customizable via web.tavily.apiKeyEnv | Requires web.tavily.enabled=true, valid baseUrl, and key | Tavily Search API | Third-party rate limit/API behavior may change |
serper | Required | web.serper.* | Default SERPER_API_KEY, customizable via web.serper.apiKeyEnv | Requires web.serper.enabled=true, valid baseUrl, and key | Serper Google Search API | Third-party rate limit/API behavior may change |
openserp | Required | web.openserp.* | Default OPENSERP_API_KEY, customizable via web.openserp.apiKeyEnv | Requires web.openserp.enabled=true, valid baseUrl, and key | OpenSERP-compatible API | Response fields support organic_results or results |
searxng | Not needed | web.searxng.* | None | Requires web.searxng.enabled=true and valid HTTP/HTTPS baseUrl | Self-hosted SearXNG JSON search | Requires accessible SearXNG instance; requests use fixed format=json |
Provider configuration summary
ddgs
Zero-config provider. Default web.provider is ddgs.
{
"web": {
"provider": "ddgs"
}
}Implementation limit: src/modules/web/providers/ddgs.ts limits results to a maximum of 5.
brave
{
"web": {
"provider": "brave",
"brave": {
"enabled": true,
"baseUrl": "https://api.search.brave.com/res/v1/web/search",
"apiKeyEnv": "BRAVE_SEARCH_API_KEY"
}
}
}When explicitly selecting brave, web.brave.enabled must be true; missing key will be classified as PROVIDER_AUTH_FAILED during the provider request phase.
tavily
{
"web": {
"provider": "tavily",
"tavily": {
"enabled": true,
"baseUrl": "https://api.tavily.com/search",
"apiKeyEnv": "TAVILY_API_KEY"
}
}
}When explicitly selecting tavily, web.tavily.enabled must be true; missing key will be classified as PROVIDER_AUTH_FAILED during the provider request phase.
serper
{
"web": {
"provider": "serper",
"serper": {
"enabled": true,
"baseUrl": "https://google.serper.dev/search",
"apiKeyEnv": "SERPER_API_KEY"
}
}
}When explicitly selecting serper, web.serper.enabled must be true; missing key will be classified as PROVIDER_AUTH_FAILED during the provider request phase.
openserp
{
"web": {
"provider": "openserp",
"openserp": {
"enabled": true,
"baseUrl": "https://api.openserp.com/search",
"apiKeyEnv": "OPENSERP_API_KEY"
}
}
}When explicitly selecting openserp, web.openserp.enabled must be true. The provider reads the key from the environment variable specified by web.openserp.apiKeyEnv.
searxng
{
"web": {
"provider": "searxng",
"searxng": {
"enabled": true,
"baseUrl": "https://searx.example.com",
"defaultEngine": "google"
}
}
}When explicitly selecting searxng, web.searxng.enabled must be true, and web.searxng.baseUrl must be a valid HTTP/HTTPS URL. If the base URL pathname is /, source code will request /search; otherwise the original pathname is preserved.
Provider selection
Explicit mode
When web.provider is a specific provider name, selection enters explicit mode:
- Only that provider is used.
- Provider failure does not fall back to other providers.
- Unsupported provider name returns
INVALID_INPUT. brave,openserp,searxng,tavily,serpermust be enabled in their corresponding config before explicit use.- All explicit providers run the same technical availability check during selection.
- Missing API keys for keyed providers return
PROVIDER_AUTH_FAILED; invalid endpoints returnINVALID_INPUT.
Auto mode
When web.provider="auto", selection constructs a candidate list from providers that are enabled and technically available. Source code divides providers into three tiers:
- commercial:
tavily,serper,brave - self-host-or-open:
openserp,searxng - zero-config:
ddgs
Within each tier, providers are sorted by web.providerPriority, then concatenated. Default priority:
tavily → serper → brave → openserp → searxng → ddgsSelection availability combines a config enabled gate with each provider adapter's technical isAvailable() check:
ddgs: always enabled and technically available.brave/tavily/serper/openserp: requiresenabled=true, valid baseUrl, and API key.searxng: requiresenabled=trueand valid HTTP/HTTPS baseUrl.
Provider adapter isAvailable() implementations intentionally do not check enabled; selectSearchProvider owns the enabled gate.
In auto mode, when one provider fails, web_search will try the next candidate provider; when all candidates fail, it returns the last error or WEB_SEARCH_FAILED.
Default behavior
Default configuration is:
{
"web": {
"provider": "ddgs"
}
}Therefore default is not auto, but explicitly using ddgs.
Provider error behavior
Provider errors are ultimately classified by src/modules/web/search.ts into WebToolError.
| Scenario | Current error behavior |
|---|---|
| Explicit unsupported provider | INVALID_INPUT |
| Explicit provider not enabled or endpoint config invalid | INVALID_INPUT |
| API key missing or HTTP 401/403 | PROVIDER_AUTH_FAILED |
| HTTP 429 | PROVIDER_RATE_LIMITED |
| HTTP 5xx | PROVIDER_UNAVAILABLE |
fetch failed / DNS ENOTFOUND / ECONN* | NETWORK_ERROR |
| timeout / abort | WEB_SEARCH_TIMEOUT |
| Provider request failed or uncategorized exception | WEB_SEARCH_FAILED |
| Provider JSON parse / response shape anomaly | Currently continues to be classified as WEB_SEARCH_FAILED; does not directly return reserved code PARSE_ERROR |
| Provider returns empty results | Success response, results = []; does not return WEB_SEARCH_NO_RESULTS |
For complete error code status, see web-tools-error-codes.md.
Jina fallback
Jina Reader fallback is an internal content extraction recovery mechanism within fetch_content, not a web_search provider:
- Does not participate in
web.provider/web.providerPriority. - Not registered in
src/modules/web/providers/registry.ts. - May only trigger when
fetch_contentprocesses HTML andweb.enableJinaFallback=true. preferReader=trueis a singlefetch_contenttool parameter, not provider selection.- Private network URLs are not sent to Jina.
- Jina failure currently does not introduce new
JINA_*error codes; timeout/abort may returnCONTENT_FETCH_TIMEOUT, other exceptions may be classified asCONTENT_FETCH_FAILED, and non-2xx or empty content falls back to original HTML extraction result.
Related configuration: configuration.md#jina-reader-configuration.
Source map
| Document topic | Source |
|---|---|
| Provider registry | src/modules/web/providers/registry.ts |
| Provider adapter interface | src/modules/web/providers/types.ts |
| Provider selection | src/modules/web/providers/select-provider.ts |
| ddgs | src/modules/web/providers/ddgs.ts |
| brave | src/modules/web/providers/brave.ts |
| tavily | src/modules/web/providers/tavily.ts |
| serper | src/modules/web/providers/serper.ts |
| openserp | src/modules/web/providers/openserp.ts |
| searxng | src/modules/web/providers/searxng.ts |
| Search error classification | src/modules/web/search.ts |
| Provider config types | src/shared/types.ts |
| Provider config defaults/normalize | src/config/load-config.ts |