A zero-dependency, token-efficient CLI for the Brave Search API, built for AI agents and LLMs.
One binary, JSON in/out, no runtime dependencies. bx context replaces search + scrape + extract in a single call with token-budgeted output — purpose-built for RAG and LLM grounding.
macOS/Linux (macOS 11.7.1 or later)
curl -fsSL https://raw.githubusercontent.com/brave/brave-search-cli/main/scripts/install.sh | shWindows (PowerShell)
powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/brave/brave-search-cli/main/scripts/install.ps1 | iex"npm (requires Node ≥ 16)
npm install -g @brave/brave-search-clibx config set-key YOUR_API_KEY # get a key at https://api-dashboard.search.brave.com
bx "your search query" # shorthand for: bx context "your search query"
bx --help # see all commands; bx <command> --help for flags
# To search for a word matching a subcommand name: bx -- web or bx context "web"Use context by default. It returns pre-extracted, relevance-scored web content ready for LLM prompt injection. One API call replaces the search → scrape → extract pipeline.
# RAG grounding with token budget
bx context "Python TypeError cannot unpack non-iterable NoneType" --max-tokens 4096
# Direct AI answer (OpenAI-compatible, streams by default)
bx answers "explain Rust lifetimes with examples"
# Raw web search when you need site: scoping or result filtering
bx web "site:docs.rs axum middleware" --count 5| Your need | Command | Why |
|---|---|---|
| Look up docs, errors, code patterns | context |
Pre-extracted text, token-budgeted |
| Get a synthesized explanation | answers |
AI-generated, cites sources |
| Search a specific site (site:) | web |
Supports search operators |
| Find discussions/forums | web --result-filter discussions |
Forums often have solutions |
| Check latest versions/releases | context or news --freshness pd |
Fresh info beyond training data |
| Research security vulnerabilities | context or news |
CVE details, advisories |
| Boost/filter specific domains | --goggles on context/web/news |
Custom re-ranking, no other API has this |
bx context — RAG/grounding (recommended)
{
"grounding": {
"generic": [
{ "url": "...", "title": "...", "snippets": ["extracted content...", "..."] }
]
}
}bx answers — AI answer (streaming, one JSON chunk per line)
{"choices": [{"delta": {"content": "Rust lifetimes ensure references..."}}]}bx answers --no-stream — AI answer (single response)
{"choices": [{"message": {"content": "Rust lifetimes ensure references..."}}]}bx web — Full search results
{
"web": { "results": [{"title": "...", "url": "...", "description": "..."}] },
"news": { "results": [...] },
"videos": { "results": [...] },
"discussions": { "results": [...] }
}Debugging an error:
# Agent encounters: "TypeError: cannot unpack non-iterable NoneType"
bx "Python TypeError cannot unpack non-iterable NoneType" --max-tokens 4096Evaluating a dependency:
# Is this crate maintained? Any security issues?
bx context "reqwest crate security issues maintained 2026" --threshold strict
bx news "reqwest Rust crate" --freshness pmCorrective RAG loop:
# 1. Broad search
bx "axum middleware authentication" --max-tokens 4096
# 2. Too general? Narrow down
bx "axum middleware tower layer authentication example" --threshold strict --max-tokens 4096
# 3. Still need synthesis? Ask for an answer
bx answers "how to implement JWT auth middleware in axum" --enable-researchChecking for breaking changes before upgrading:
bx context "Next.js 15 breaking changes migration guide" --max-tokens 8192
bx news "Next.js 15 release" --freshness pmFocused search with Goggles (custom re-ranking):
# Boost official docs, prefer /docs/ paths, suppress SEO spam
bx "Python asyncio gather vs wait" \
--goggles '$boost=3,site=docs.python.org
/docs/$boost=3
/blog/$downrank=2
$discard,site=geeksforgeeks.org
$discard,site=w3schools.com' --max-tokens 4096Token budget control:
bx context "topic" --max-tokens 4096 --max-tokens-per-url 1024 --max-urls 5Brave Goggles let you define custom re-ranking rules for search results. Boost domains, URL paths, or content patterns; downrank noise; discard SEO spam — from simple domain allow/deny lists to complex multi-rule ranking profiles. No other search API offers this. Supported on context, web, and news.
For the common case of restricting to or excluding specific domains, use the convenience flags (available on context, web, news):
# Only include results from these domains (allowlist)
bx "rust axum" --include-site docs.rs --include-site github.com
# Exclude specific domains
bx web "rust tutorial" --exclude-site w3schools.com --exclude-site medium.comThese generate Goggles rules internally. For more advanced re-ranking (boosting, path patterns, wildcards), use --goggles directly. The three flags are mutually exclusive.
- Domain allow/deny lists: Boost, downrank, or discard entire domains — but that's just the start
- URL path patterns: Boost
/docs/paths, downrank/blog/paths, discard/ads/— fine-grained URL targeting with wildcards - Full power of
site:— Brave convertssite:operators to Goggles internally, but explicit Goggles unlock the full DSL: hundreds of rules, path patterns, content matching, boost/downrank strengths — without bloating the query string - Keep queries clean: A single
--gogglesparameter (or URL) replaces longsite:X OR site:Ychains in the query, saving tokens - Reusable & shareable: Host a
.gogglefile on GitHub — share among team members, agents, CI pipelines. Document your ranking preferences as code - Community-maintained: Leverage existing Goggles like Tech Blogs that are continuously updated
# Domain boosting — prioritize official sources for a Python error
bx "TypeError cannot unpack non-iterable NoneType" \
--goggles '$boost=3,site=docs.python.org
$boost=2,site=github.com
$discard,site=geeksforgeeks.org
$discard,site=w3schools.com'
# Path-based boosting — prefer /docs/ over /blog/ across all sites
bx context "axum middleware tower" \
--goggles '/docs/$boost=5
/api/$boost=3
/blog/$downrank=3' --max-tokens 4096
# Ecosystem focus — boost Rust sources for crate research
bx context "serde custom deserializer" \
--goggles '$boost=5,site=docs.rs
$boost=5,site=crates.io
$boost=3,site=github.com' --max-tokens 4096
# Downrank blog spam in news results
bx news "npm security advisory" --freshness pd \
--goggles '$downrank=5,site=medium.com'| Rule | Effect | Example |
|---|---|---|
$boost=N,site=DOMAIN |
Promote domain (N=1-10) | $boost=3,site=docs.rs |
$downrank=N,site=DOMAIN |
Demote domain (N=1-10) | $downrank=5,site=medium.com |
$discard,site=DOMAIN |
Remove domain entirely | $discard,site=w3schools.com |
/path/$boost=N |
Boost matching URL paths | /docs/$boost=5 |
*pattern*$boost=N |
Wildcard URL matching | *api*$boost=3 |
Generic $discard |
Allowlist mode — discard all unmatched | $discard (as last rule) |
Separate multiple rules with newlines. Full DSL + pattern syntax: goggles-quickstart.
Agents can generate a .goggle file on the fly and reference it:
# Agent writes rules to a file, then uses it across multiple queries
cat > /tmp/rust.goggle << 'EOF'
$boost=5,site=docs.rs
$boost=5,site=crates.io
$boost=3,site=github.com
/blog/$downrank=3
$discard,site=w3schools.com
$discard,site=geeksforgeeks.org
EOF
bx context "axum middleware tower" --goggles @/tmp/rust.goggle --max-tokens 4096
bx context "serde custom deserializer" --goggles @/tmp/rust.goggle --max-tokens 4096# Pipe rules from another command
echo '$boost=5,site=docs.rs
$boost=3,site=github.com' | bx web "tokio runtime" --goggles @-Host a .goggle file on GitHub/GitLab, submit it to Brave, then reference by URL:
# Use the official Tech Blogs goggle
bx web "distributed systems" \
--goggles 'https://raw.githubusercontent.com/brave/goggles-quickstart/main/goggles/tech_blogs.goggle'Community Goggles: brave/goggles-quickstart | Discover page
- Sign up at https://api-dashboard.search.brave.com/register
- Choose a plan — all plans include $5/month free credits (~1,000 free queries)
- Different endpoints may require different plans (e.g. Search vs Answers)
- Go to "API Keys" in the dashboard and generate a key
Settings are resolved in priority order: CLI flag > environment variable > config file > default.
| Setting | CLI Flag | Env Var | Config Key | Default |
|---|---|---|---|---|
| API key | --api-key |
BRAVE_SEARCH_API_KEY |
api_key |
(interactive prompt) |
| Base URL | --base-url |
BRAVE_SEARCH_BASE_URL |
base_url |
https://api.search.brave.com |
| Timeout | --timeout |
— | timeout |
30 (300 for answers --enable-research; max 86400) |
Streamed answers responses have no total deadline — every other command keeps one. The
timeout bounds idle time: connecting, and then each read. A response that keeps arriving is
never cut off however long it runs, and a server that only sends keep-alives never ends. Deep
research pauses for minutes while it synthesises, so it defaults to 300 seconds of silence;
--timeout or a timeout in your config file always wins, and applies to connecting too.
Config file (bx config path to see location):
- Linux:
~/.config/brave-search/config.json - macOS:
~/Library/Application Support/brave-search/config.json - Windows:
%APPDATA%\brave-search\config.json
{
"api_key": "BSA...",
"base_url": "https://api.search.brave.com"
}Use --config <path> to load a different config file (e.g. one for dev, one for prod):
bx --config ~/.config/brave-search/dev.json web "test"Security tip: Prefer the env var or config file over --api-key, which is visible in process listings. Use bx config set-key without an argument to enter the key interactively, avoiding shell history.
Saving a key writes a new file and renames it into place: mode 0600 on Linux and macOS, with the default directory ~/.config/brave-search kept 0700 (a directory you pass via --config is left as it is). Because the file is replaced rather than written in place, a symlink, hard link, or bind mount at the config path does not survive a save, and neither do extended attributes or ACLs on the old file — deliberate, so a key is never written through an indirection bx does not control. On Windows no ACL is set and the file inherits whatever %APPDATA%\brave-search grants — restrict that directory yourself if the machine has other users.
You can point bx at a local reverse proxy instead of the Brave API directly. This is useful for sandboxed environments where credentials are managed outside the sandbox.
Allowed localhost forms:
http://127.0.0.1[:<port>][/path]http://[::1][:<port>][/path]http://localhost[:<port>][/path](resolved and rewritten to a literal loopback IP)
# Point bx at a local proxy on port 8080 with path prefix /brave
BRAVE_SEARCH_BASE_URL=http://127.0.0.1:8080/brave bx "your query"
# Or in config.json
# { "base_url": "http://127.0.0.1:8080/brave" }The proxy receives requests at the configured base URL (e.g. http://127.0.0.1:8080/brave/res/v1/web/search?q=...) and can inject the X-Subscription-Token header before forwarding to api.search.brave.com. Since bx still requires an API key, set a placeholder: BRAVE_SEARCH_API_KEY=unused.
Only loopback addresses are allowed for http:// URLs — non-loopback IPs and arbitrary hostnames are rejected. The localhost hostname is resolved at startup and rewritten to a literal IP to prevent DNS rebinding.
| Command | Description | Output Shape |
|---|---|---|
context |
RAG/LLM grounding — pre-extracted web content | .grounding.generic[] → {url, title, snippets[]} |
answers |
AI answers — OpenAI-compatible, streaming | .choices[0].delta.content (stream) |
web |
Full web search — all result types | .web.results[], .news.results[], etc. |
news |
News articles with freshness filters | .results[] → {title, url, age} |
images |
Image search (up to 200 results) | .results[] → {title, url, thumbnail.src} |
videos |
Video search with duration/views | .results[] → {title, url, video.duration} |
places |
Local place/POI search (200M+ POIs) | .results[] → {title, postal_address, contact} |
suggest |
Autocomplete/query suggestions | .results[] → {query} |
spellcheck |
Spell-check a query | .results[0].query |
pois |
POI details by ID | (use IDs from places) |
descriptions |
AI-generated POI descriptions | .results[].description |
config |
Manage configuration | set-key, show-key, show, path |
# Default subcommand is context
bx "tokio spawn async task example"
# RAG grounding with token budget
bx context "Python TypeError cannot unpack non-iterable NoneType" --max-tokens 4096 --threshold strict
# AI answer (streaming)
bx answers "explain Rust lifetimes with examples"
# AI answer (non-streaming)
bx answers "compare SQLx and Diesel for Rust database access" --no-stream | jq .
# Research mode (multi-step, thorough)
bx answers "what changed in React 19 vs React 18?" --enable-research
# Web search with site: scoping
bx web "site:docs.rs axum middleware" | jq '.web.results[].url'
# News from the past day
bx news "npm security advisory" --freshness pd | jq '.results[].title'
# Image search
bx images "system architecture diagram microservices" | jq '.results[].thumbnail.src'
# Autocomplete
bx suggest "how to implement" --count 10 | jq '.results[].query'
# Local places
bx places --location "San Francisco CA US" -q "coffee" | jq '.results[].title'
# Location-aware web search
bx web "restaurants near me" --lat 37.7749 --long -122.4194 --city "San Francisco"
# Filter result types
bx web "rust" --result-filter "web,discussions"Pass additional API parameters not covered by named flags. Repeatable. Merged into the JSON body (POST endpoints) or query string (GET endpoints). Warns if the key collides with an existing flag.
For POST endpoints, values are auto-typed: integers and floats become JSON numbers, true/false become JSON booleans, everything else stays a string. GET values are always strings.
bx web "rust" --extra custom_param=value
bx web "rust" --extra count=5 # overrides --count (with warning)
bx images "sunset" --extra safesearch=offOverride the default API path. For internal or beta Brave endpoints. Must start with /.
bx web "test" --endpoint /res/v2/web/searchThe answers command supports two modes:
Pass a question directly:
bx answers "explain Rust lifetimes with examples" # streaming (default)
bx answers "compare SQLx and Diesel" --no-stream # single JSON response
bx answers "what changed in React 19?" --enable-research # deep research
bx answers "Hello" --model brave # faster model--enable-citations, --enable-entities, and --enable-research all require streaming, so
none combines with --no-stream. Citations and entities are not supported in research mode,
but are fine together. Rejected combinations exit 2, nothing sent:
bx answers "what changed in React 19?" --enable-research --enable-citations
# error: the argument '--enable-research' cannot be used with '--enable-citations'Pass - to read a full JSON request body from stdin:
echo '{"messages":[{"role":"user","content":"review this code for security issues"}],"model":"brave-pro"}' | bx answers -
cat request.json | bx answers -| Code | Meaning | Agent action |
|---|---|---|
| 0 | Success | Process results |
| 1 | The request was made; the result is unusable | Fix query/parameters |
| 2 | Usage error — nothing was sent | Fix the CLI arguments, config, or input |
| 3 | Auth/permission error (401/403) | Check API key or plan: bx config show-key |
| 4 | Rate limited (429) | Retry after delay |
| 5 | Server/network error | Retry with backoff |
| 127 | npm launcher could not start bx | Reinstall, or check platform support |
Errors are printed to stderr with a human-readable summary, recovery hints, and the full JSON error body:
error: rate limited (429) — Request rate limit exceeded for plan.
hint: retry after a short delay, or upgrade plan for higher rate limits
{"type":"ErrorResponse","error":{"code":"RATE_LIMITED","status":429,...}}
Exit codes are differentiated — see Exit Codes above. The line between 1 and 2 is whether a request was made: 2 means bx could not act on what it was given and never contacted the API, so retrying without changing the command will not help.
# Debug build
cargo build
# Release build (~2 MB)
cargo build --releaseRequires Docker, GNU Make, and a POSIX shell (sh/bash). Produces fully static, cross-compiled binaries.
On Windows, run these targets from Git Bash or WSL. Running make from native cmd/PowerShell can fail because the Makefile uses POSIX utilities (grep, sed, shell redirection).
make linux-amd64 # → dist/bx-<version>-linux-amd64
make linux-arm64 # → dist/bx-<version>-linux-arm64
make darwin-amd64 # → dist/bx-<version>-darwin-amd64
make darwin-arm64 # → dist/bx-<version>-darwin-arm64
make windows-amd64 # → dist/bx-<version>-windows-amd64.exe
make windows-arm64 # → dist/bx-<version>-windows-arm64.exe
make dist-all # → all of the aboveBoth macOS binaries require macOS 11.7.1 or later — zig sets that deployment
target and MACOSX_DEPLOYMENT_TARGET does not override it. The smoke-darwin
job asserts it, so a toolchain bump that raises the floor fails CI.
# End-to-end API smoke suite (requires bash + jq + BRAVE_SEARCH_API_KEY/BRAVE_API_KEY)
make test
# Unit tests (no network/API key required)
make test-unit