* docs(secrets): remove retired web credential paths * refactor(web): remove retired provider compatibility paths * refactor(providers): delete retired compatibility routes * refactor(secrets): remove retired credential aliases * refactor(plugin-sdk): delete retired compatibility surfaces * docs(plugin-sdk): remove retired migration guidance * chore(plugin-sdk): refresh rebased surface budgets * chore(plugin-sdk): refresh API removal baseline * refactor(compat): migrate retired internal callers * chore(plugin-sdk): refresh current-main baselines * test(config): migrate plugin-owned secret assertions * test(gateway): narrow plugin secret refs * fix(plugin-sdk): preserve private boundary type identity * chore(compat): remove stale sweep references * chore(lint): lower max-lines budget * refactor(secrets): remove unused web helper * build(plugin-sdk): drop removed compat entries * chore(plugin-sdk): refresh rebased API baseline * chore(plugin-sdk): use Linux API baseline hash * fix(plugin-sdk): preserve private bundled build entries * fix(plugin-sdk): package private runtime facades * fix(plugins): preserve external credential contracts
29 KiB
summary, title, sidebarTitle, read_when
| summary | title | sidebarTitle | read_when | ||||
|---|---|---|---|---|---|---|---|
| web_search, x_search, and web_fetch -- search the web, search X posts, or fetch page content | Web search | Web Search |
|
web_search searches the web with your configured provider and returns
normalized results, cached by query for 15 minutes (configurable). OpenClaw
also bundles x_search for X (formerly Twitter) posts and web_fetch for
lightweight URL fetching. web_fetch always runs locally; web_search routes
through xAI Responses when Grok is the provider, and x_search always uses
xAI Responses.
Quick start
Pick a provider and complete any required setup. Some providers are key-free, others need an API key. See the provider pages below for details. ```bash openclaw configure --section web ``` This stores the provider and any needed credential. For API-backed providers you can instead set the provider's env var (for example `BRAVE_API_KEY`) and skip this step. ```javascript await web_search({ query: "OpenClaw plugin SDK" }); ```For X posts:
```javascript
await x_search({ query: "dinner recipes" });
```
Choosing a provider
Structured results with snippets. Supports `llm-context` mode, country/language filters. Free tier available. AI-synthesized grounded answers through your Codex app-server account. Key-free provider. No API key needed. Unofficial HTML-based integration. Neural + keyword search with content extraction (highlights, text, summaries). Structured results. Best paired with `firecrawl_search` and `firecrawl_scrape` for deep extraction. AI-synthesized answers with citations via Google Search grounding. AI-synthesized answers with citations via xAI web grounding. AI-synthesized answers with citations via Moonshot web search; ungrounded chat fallbacks fail explicitly. Structured results via the MiniMax Token Plan search API. Search via a signed-in local Ollama host or the hosted Ollama API. Paid Parallel Search API (`PARALLEL_API_KEY`); higher rate limits and objective tuning. Key-free opt-in. Parallel's free Search MCP, with LLM-optimized dense excerpts and no API key. Structured results with content extraction controls and domain filtering. Self-hosted meta-search. No API key needed. Aggregates Google, Bing, DuckDuckGo, and more. Structured results with search depth, topic filtering, and `tavily_extract` for URL extraction.Provider comparison
| Provider | Result style | Filters | API key |
|---|---|---|---|
| Brave | Structured snippets | Country, language, time, llm-context mode |
BRAVE_API_KEY |
| Codex Hosted Search | AI-synthesized + source URLs | Domains, context size, user location | None; uses Codex/OpenAI sign-in |
| DuckDuckGo | Structured snippets | -- | None (key-free) |
| Exa | Structured + extracted | Neural/keyword mode, date, content extraction | EXA_API_KEY |
| Firecrawl | Structured snippets | Via firecrawl_search tool |
FIRECRAWL_API_KEY |
| Gemini | AI-synthesized + citations | -- | GEMINI_API_KEY |
| Grok | AI-synthesized + citations | -- | xAI OAuth, XAI_API_KEY, or plugins.entries.xai.config.webSearch.apiKey |
| Kimi | AI-synthesized + citations; fails on ungrounded chat fallbacks | -- | KIMI_API_KEY / MOONSHOT_API_KEY |
| MiniMax Search | Structured snippets | Region (global / cn) |
MINIMAX_CODE_PLAN_KEY / MINIMAX_CODING_API_KEY / MINIMAX_OAUTH_TOKEN |
| Ollama Web Search | Structured snippets | -- | None for signed-in local hosts; OLLAMA_API_KEY for direct https://ollama.com search |
| Parallel | Dense excerpts ranked for LLM context | -- | PARALLEL_API_KEY (paid) |
| Parallel Search (Free) | Dense excerpts ranked for LLM context | -- | None (free Search MCP) |
| Perplexity | Structured snippets | Country, language, time, domains, content limits | PERPLEXITY_API_KEY / OPENROUTER_API_KEY |
| SearXNG | Structured snippets | Categories, language | None (self-hosted) |
| Tavily | Structured snippets | Via tavily_search tool |
TAVILY_API_KEY |
Result shape
web_search normalizes every bundled and external plugin provider at the core
tool boundary. Callers receive exactly one of these closed shapes:
type WebSearchOutput =
| {
kind: "error";
provider: string;
error: "provider_error";
message: string;
docs?: string;
}
| {
kind: "results";
provider: string;
query: string;
count: number;
tookMs?: number;
results: Array<{
title: string;
url: string;
snippet?: string;
published?: string;
siteName?: string;
}>;
externalContent: {
untrusted: true;
source: "web_search";
wrapped: true;
provider: string;
};
cached?: true;
}
| {
kind: "answer";
provider: string;
query: string;
tookMs?: number;
content: string;
citations?: Array<{ url: string; title?: string }>;
externalContent: {
untrusted: true;
source: "web_search";
wrapped: true;
provider: string;
};
cached?: true;
}
| {
kind: "raw";
provider: string;
data: unknown;
};
Structured providers use kind: "results"; synthesized providers use
kind: "answer". External plugin providers whose payloads match neither shape
pass through verbatim as kind: "raw" for compatibility. Provider-specific
fields such as raw scores, excerpts, related searches, inline-citation
offsets, model ids, or session metadata are not passed through on normalized
branches. Use a provider's dedicated tool when its richer response is part of
your workflow.
externalContent.wrapped: true is a trust marker the boundary itself makes
true: provider prose (title, snippet, siteName, content, citation
titles, error message) is stripped of any pre-existing envelope lines and
re-wrapped exactly once at the core boundary, so no provider metadata can spoof
the marker. query is always the requested query, citation and result URLs
must parse as http(s), published must be ISO-date shaped, URLs are emitted canonicalized, and a
payload carrying an error key is always reported as kind: "error" with the
raw provider code preserved inside the wrapped message. Raw passthrough
payloads keep whatever markers the provider set.
Auto-detection
Provider lists in docs and setup flows are alphabetical. Auto-detection uses a
separate, fixed precedence order and only picks a provider that needs a
credential (requiresCredential !== false) when it finds one configured. If
no provider is set, OpenClaw checks providers in this order and uses the
first one that is ready:
API-backed providers first:
- Brave --
BRAVE_API_KEYorplugins.entries.brave.config.webSearch.apiKey(order 10) - MiniMax Search --
MINIMAX_CODE_PLAN_KEY/MINIMAX_CODING_API_KEY/MINIMAX_OAUTH_TOKEN/MINIMAX_API_KEYorplugins.entries.minimax.config.webSearch.apiKey(order 15) - Gemini --
plugins.entries.google.config.webSearch.apiKey,GEMINI_API_KEY, ormodels.providers.google.apiKey(order 20) - Grok -- xAI OAuth,
XAI_API_KEY, orplugins.entries.xai.config.webSearch.apiKey(order 30) - Kimi --
KIMI_API_KEY/MOONSHOT_API_KEYorplugins.entries.moonshot.config.webSearch.apiKey(order 40) - Perplexity --
PERPLEXITY_API_KEY/OPENROUTER_API_KEYorplugins.entries.perplexity.config.webSearch.apiKey(order 50) - Firecrawl --
FIRECRAWL_API_KEYorplugins.entries.firecrawl.config.webSearch.apiKey(order 60) - Exa --
EXA_API_KEYorplugins.entries.exa.config.webSearch.apiKey; optionalplugins.entries.exa.config.webSearch.baseUrloverrides the Exa endpoint (order 65) - Tavily --
TAVILY_API_KEYorplugins.entries.tavily.config.webSearch.apiKey(order 70) - Parallel -- paid Parallel Search API via
PARALLEL_API_KEYorplugins.entries.parallel.config.webSearch.apiKey; optionalplugins.entries.parallel.config.webSearch.baseUrloverrides the endpoint (order 75)
Configured endpoint providers after that:
- SearXNG --
SEARXNG_BASE_URLorplugins.entries.searxng.config.webSearch.baseUrl(order 200)
Key-free providers such as Parallel Search (Free), DuckDuckGo,
Ollama Web Search, and Codex Hosted Search never win auto-detection,
even though they have an internal order value. They are used only when you
select them explicitly with tools.web.search.provider or through
openclaw configure --section web. OpenClaw does not send managed
web_search queries to a key-free provider just because no API-backed
provider is configured.
OpenAI Responses models are an exception: while tools.web.search.provider
is unset, they use OpenAI's native web search instead of the managed
providers above (see below). Set tools.web.search.provider to
parallel-free (or another provider) to route them through the managed path
instead.
Native OpenAI web search
Direct OpenAI Responses models (api: "openai-responses", provider openai,
no base URL or an official OpenAI API base URL) use OpenAI's hosted
web_search tool automatically when OpenClaw web search is enabled and no
managed provider is pinned. This is provider-owned behavior in the bundled
OpenAI plugin and does not apply to OpenAI-compatible proxy base URLs or Azure
routes. Set tools.web.search.provider to another provider such as brave to
keep the managed web_search tool for OpenAI models, or set
tools.web.search.enabled: false to disable both managed search and native
OpenAI search.
Native Codex web search
The Codex app-server runtime uses Codex's hosted web_search tool automatically
when web search is enabled and no managed provider is selected. Native hosted
search and OpenClaw's managed web_search dynamic tool are mutually exclusive,
so managed search cannot bypass native domain restrictions. OpenClaw uses the
managed tool when hosted search is unavailable, explicitly disabled, or
replaced by a selected managed provider. OpenClaw keeps Codex's standalone
web.run extension disabled (features.standalone_web_search: false)
because production app-server traffic rejects its user-defined web
namespace.
- Configure native search under
tools.web.search.openaiCodex - Set
tools.web.search.provider: "codex"to provision Codex Hosted Search as the managedweb_searchprovider for any parent model. Each call runs a bounded ephemeral Codex app-server turn and fails if Codex does not emit a hostedwebSearchitem. mode: "cached"is the default preference, but Codex resolves it to live external access for unrestricted app-server turns; set"live"to request live access explicitly- Set
tools.web.search.providerto a managed provider such asbraveto use OpenClaw's managedweb_searchinstead - Set
tools.web.search.openaiCodex.enabled: falseto opt out of Codex-hosted search; other managed providers remain available - Restricting the Codex native tool surface also keeps managed
web_searchavailable - When
allowedDomainsis set, automatic managed fallback fails closed if hosted search is unavailable so the native allowlist cannot be bypassed - Tool-disabled LLM-only runs disable both native and managed search
tools.web.search.enabled: falsedisables both managed and native search
Persistent effective Codex search-policy changes start a fresh bound thread so an already loaded app-server thread cannot keep stale hosted-search access. Transient per-turn restrictions use a temporary restricted thread and preserve the existing binding for later resume.
Direct OpenAI ChatGPT Responses traffic can also use OpenAI's hosted
web_search tool. That separate path remains opt-in through
tools.web.search.openaiCodex.enabled: true and only applies to eligible
openai/* models using api: "openai-chatgpt-responses".
{
tools: {
web: {
search: {
enabled: true,
// Optional: use Codex Hosted Search from non-Codex parent models too.
provider: "codex",
openaiCodex: {
enabled: true,
mode: "cached",
allowedDomains: ["example.com"],
contextSize: "high",
userLocation: {
country: "US",
city: "New York",
timezone: "America/New_York",
},
},
},
},
},
}
For runtimes and providers that do not support native Codex search, Codex can
use the managed web_search fallback through OpenClaw's dynamic tool namespace.
Use an explicit managed provider when you need OpenClaw's provider-specific
network controls instead of Codex-hosted search.
Selecting provider: "codex" enables the bundled codex plugin and uses the
same tools.web.search.openaiCodex restrictions shown above. Authenticate the
Codex app-server first with openclaw models auth login --provider openai.
The parent agent can use any model or runtime; only the bounded search worker
runs through Codex.
Network safety
Managed HTTP web_search provider calls use OpenClaw's guarded fetch path,
scoped to the current provider's own hostname. For that hostname only,
OpenClaw allows Surge, Clash, and sing-box fake-IP DNS answers in
198.18.0.0/15 and fc00::/7. Other private, loopback, link-local, and
metadata destinations remain blocked. Codex Hosted Search is the exception:
its bounded worker delegates network access to Codex app-server's hosted
web_search tool.
This automatic allowance does not apply to arbitrary web_fetch URLs. For
web_fetch, enable tools.web.fetch.ssrfPolicy.allowRfc2544BenchmarkRange and
tools.web.fetch.ssrfPolicy.allowIpv6UniqueLocalRange explicitly only when your
trusted proxy owns those synthetic ranges.
Config
{
tools: {
web: {
search: {
enabled: true, // default: true
provider: "brave", // or omit for auto-detection
maxResults: 5,
timeoutSeconds: 30,
cacheTtlMinutes: 15,
},
},
},
}
Provider-specific config (API keys, base URLs, modes) lives under
plugins.entries.<plugin>.config.webSearch.*. Gemini can also reuse
models.providers.google.apiKey and models.providers.google.baseUrl as lower-priority
fallbacks after its dedicated web-search config and GEMINI_API_KEY. See the
provider pages for examples.
Grok can also reuse an xAI OAuth auth profile from openclaw models auth login --provider xai --method oauth; API-key config remains the fallback.
tools.web.search.provider is validated against the web-search provider ids
declared by bundled and installed plugin manifests. A typo such as "brvae"
fails config validation instead of silently falling back to auto-detection. If a
configured provider only has stale plugin evidence, such as a leftover
plugins.entries.<plugin> block after uninstalling a third-party plugin,
OpenClaw keeps startup resilient and reports a warning so you can reinstall the
plugin or run openclaw doctor --fix to clean up the stale config.
web_fetch fallback provider selection is separate:
- choose it with
tools.web.fetch.provider - or omit that field and let OpenClaw auto-detect the first ready web-fetch provider from configured credentials
- non-sandboxed
web_fetchcan use installed plugin providers that declarecontracts.webFetchProviders; sandboxed fetches allow bundled providers and verified official plugin installs, but exclude third-party external plugins - the official Firecrawl plugin is the only bundled
webFetchProviderscontributor today, configured underplugins.entries.firecrawl.config.webFetch.*
When you choose Kimi during openclaw onboard or
openclaw configure --section web, OpenClaw can also ask for:
- the Moonshot API region (
https://api.moonshot.ai/v1orhttps://api.moonshot.cn/v1) - the default Kimi web-search model (defaults to
kimi-k2.6)
For x_search, configure plugins.entries.xai.config.xSearch.*. It uses the
same xAI auth profile as chat, or the XAI_API_KEY / plugin web-search
credential used by Grok web search.
Legacy tools.web.x_search.* config is auto-migrated by openclaw doctor --fix.
When you choose Grok during openclaw onboard or openclaw configure --section web,
OpenClaw also offers optional x_search setup with the same credential right
after Grok setup completes. This is a separate follow-up step inside the Grok
path, not a separate top-level web-search provider choice. If you pick another
provider, OpenClaw does not show the x_search prompt.
Storing API keys
Run `openclaw configure --section web` or set the key directly:```json5
{
plugins: {
entries: {
brave: {
config: {
webSearch: {
apiKey: "YOUR_KEY", // pragma: allowlist secret
},
},
},
},
},
}
```
```bash
export BRAVE_API_KEY="YOUR_KEY"
```
For a gateway install, put it in `~/.openclaw/.env`.
See [Env vars](/help/faq#env-vars-and-env-loading).
Tool parameters
| Parameter | Description |
|---|---|
query |
Search query (required) |
count |
Results to return (1-10, default: 5) |
country |
2-letter ISO country code (e.g. "US", "DE") |
language |
ISO 639-1 language code (e.g. "en", "de") |
search_lang |
Search-language code (Brave only) |
freshness |
Time filter: day, week, month, or year |
date_after |
Results after this date (YYYY-MM-DD) |
date_before |
Results before this date (YYYY-MM-DD) |
ui_lang |
UI language code (Brave only) |
domain_filter |
Domain allowlist/denylist array (Perplexity only) |
max_tokens |
Total content token budget, native Perplexity Search API only |
max_tokens_per_page |
Per-page extraction token limit, native Perplexity Search API only |
x_search
x_search queries X (formerly Twitter) posts using xAI and returns
AI-synthesized answers with citations. It accepts natural-language queries and
optional structured filters. OpenClaw constructs the built-in xAI x_search
tool per request rather than keeping it permanently registered, so it is only
active for the turn that actually calls it.
x_search config
With enabled omitted, x_search is exposed only when the active model's
provider is xai and xAI credentials resolve. For an active model with a known
non-xAI provider, set plugins.entries.xai.config.xSearch.enabled to true to
opt in to cross-provider use. If the active model provider is missing or
unresolved, the tool stays hidden. Set enabled to false to disable it for
every provider. xAI credentials are always required.
{
plugins: {
entries: {
xai: {
config: {
xSearch: {
enabled: true, // required for a known non-xAI model provider
model: "grok-4.3",
baseUrl: "https://api.x.ai/v1", // optional, overrides webSearch.baseUrl
inlineCitations: false,
maxTurns: 2,
timeoutSeconds: 30,
cacheTtlMinutes: 15,
},
webSearch: {
apiKey: "xai-...", // optional if an xAI auth profile or XAI_API_KEY is set
baseUrl: "https://api.x.ai/v1", // optional shared xAI Responses base URL
},
},
},
},
},
}
x_search posts to <baseUrl>/responses when
plugins.entries.xai.config.xSearch.baseUrl is set. If that field is omitted,
it falls back to plugins.entries.xai.config.webSearch.baseUrl, then the
public xAI endpoint (https://api.x.ai/v1).
x_search parameters
| Parameter | Description |
|---|---|
query |
Search query (required) |
allowed_x_handles |
Restrict results to at most 20 X handles |
excluded_x_handles |
Exclude at most 20 X handles |
from_date |
Only include posts on or after this date (YYYY-MM-DD) |
to_date |
Only include posts on or before this date (YYYY-MM-DD) |
enable_image_understanding |
Let xAI inspect images attached to matching posts |
enable_video_understanding |
Let xAI inspect videos attached to matching posts |
allowed_x_handles and excluded_x_handles are mutually exclusive.
x_search example
await x_search({
query: "dinner recipes",
allowed_x_handles: ["nytfood"],
from_date: "2026-03-01",
});
// Per-post stats: use the exact status URL or status ID when possible
await x_search({
query: "https://x.com/huntharo/status/1905678901234567890",
});
Examples
// Basic search
await web_search({ query: "OpenClaw plugin SDK" });
// German-specific search
await web_search({ query: "TV online schauen", country: "DE", language: "de" });
// Recent results (past week)
await web_search({ query: "AI developments", freshness: "week" });
// Date range
await web_search({
query: "climate research",
date_after: "2024-01-01",
date_before: "2024-06-30",
});
// Domain filtering (Perplexity only)
await web_search({
query: "product reviews",
domain_filter: ["-reddit.com", "-pinterest.com"],
});
Tool profiles
If you use tool profiles or allowlists, add web_search, x_search, or group:web:
{
tools: {
allow: ["web_search", "x_search"],
// or: allow: ["group:web"] (includes web_search, x_search, and web_fetch)
},
}
Related
- Web Fetch -- fetch a URL and extract readable content
- Web Browser -- full browser automation for JS-heavy sites
- Grok Search -- Grok as the
web_searchprovider - Ollama Web Search -- key-free web search through your Ollama host