Connectors
/
Brave Search
Live · 17 tools

Brave Search Integration for AI Agents

The Brave Search API is well-documented. Connecting your agent to it correctly — with per-user key isolation, plan-gated tool selection, and multi-step flows like the summarizer — is the part that takes longer than it should.
Brave Search
Live

Web Search

Privacy-first

Status
Live
Tools
17 pre-built
Auth
API Key
Credential storage
Sandbox support

API Key

Per-user isolation

Plan-tiered tools

Privacy-first search

The real problem

Why this is harder than it looks

Brave Search uses API key authentication, not OAuth. That sounds simpler until you try to build it for multiple customers. In a single-user prototype you paste the key into an environment variable and move on. In a product where each customer connects their own Brave Search subscription, you need per-user key storage that is cryptographically isolated — one customer’s key must never be used for another customer’s requests, even under the same backend process.

The second problem is plan-gated tool availability. Brave Search’s 17 tools are not uniformly available — they are split across four subscription tiers. The Free plan covers core web, news, image, video, and suggest search (2,000 queries/month, 1 req/s). Pro adds LLM grounding context and the full summarizer suite. The AI plan adds chat completions. The Data for AI plan adds local search and POI tools. An agent that calls brave_llm_context or brave_summarizer_search against a Free-plan key will fail at runtime with a 401 that looks like a credential error, not a plan error. Debugging this across multiple customers with different subscription tiers is a real operational burden.

The third problem is multi-step tool flows. The summarizer is not a single call — you first call brave_web_search with summary: true to obtain a summarizer.key, then pass that key to brave_summarizer_search, brave_summarizer_followups, or any of the other summarizer tools. Similarly, local search requires a two-step pattern: brave_local_place_search returns location IDs that expire after approximately 8 hours, and you must call brave_local_pois or brave_local_descriptions within the same session. Getting either flow wrong produces silent partial results or stale-ID errors with no guidance in the error message. Scalekit handles key storage and per-user isolation so your agent only has to name the tool and pass parameters.

Capabilities

What your agent can do with Brave Search

Once connected, your agent has 17 pre-built tools covering Brave Search’s full API surface:

  • Real-time web, news, image, and video search: privacy-first results with filtering by country, language, recency, and custom Goggles re-ranking
  • LLM grounding context: token-budgeted, snippet-optimised search output specifically structured for grounding LLM responses — no post-processing required
  • Search-augmented chat completions: OpenAI-compatible /v1/chat/completions interface backed by live Brave Search results, with citations
  • AI summarizer suite: multi-part summarizer flow returning full summaries, enrichments, follow-up queries, entity metadata, and titles from a single summarizer key
  • Local place search and POI details: search 200M+ points of interest by coordinates or location name, then fetch addresses, hours, ratings, and AI-generated descriptions
  • Autocomplete and spellcheck: query suggestions and spelling corrections for search UIs and query pre-processing
Setup context

What we’re building

This guide connects a research and search assistant agent to Brave Search — letting your users query the web, get grounded AI answers, and surface local results without leaving your product.

🤖
Example agent
Research assistant running privacy-first web searches and generating cited answers on behalf of each user
🔐
Auth model
API Key — each user connects their own Brave Search account. identifier = your user ID
⚙️
Scalekit account
app.scalekit.com — Client ID, Secret, Env URL
🔑
Brave Search API key
Obtain at api.search.brave.com — plan tier determines which tools are available
Setup

1 Setup: One SDK, One credential

Install the Scalekit SDK. The only credential your application manages is the Scalekit API key — no Brave Search secrets, no user API keys, nothing belonging to your customers.

pip install scalekit-sdk-python
npm install @scalekit-sdk/node
import scalekit.client import os from dotenv import load_dotenv load_dotenv() scalekit = scalekit.client.ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), env_url=os.getenv("SCALEKIT_ENV_URL"), ) actions = scalekit.actions
import { ScalekitClient } from '@scalekit-sdk/node'; import 'dotenv/config'; const scalekit = new ScalekitClient( process.env.SCALEKIT_ENV_URL, process.env.SCALEKIT_CLIENT_ID, process.env.SCALEKIT_CLIENT_SECRET ); const actions = scalekit.actions;
Already have credentials?
Connected Accounts

2 Per-User Auth: Creating connected accounts

Each user gets their own Connected Account backed by their Brave Search API key. The identifier is any unique string from your system — a UUID, email, whatever you use internally.

Because Brave Search uses API key auth, the setup flow is different from OAuth connectors. You register the user’s key directly via upsert_connected_account — for example, when they paste their API key into a settings page in your product.

scalekit.actions.upsert_connected_account( connection_name="brave-search", identifier="user_brave_123", # your internal user ID credentials={"api_key": "BSA..."} # user's Brave Search API key ) # Account is immediately ACTIVE — no OAuth redirect needed
await scalekit.actions.upsertConnectedAccount({ connectionName: "brave-search", identifier: "user_brave_123", // your internal user ID credentials: { api_key: "BSA..." } // user's Brave Search API key }); // Account is immediately ACTIVE — no OAuth redirect needed
No OAuth flow — key storage is immediate
There is no authorization link or redirect for Brave Search. Once you call upsert_connected_account with the user’s API key, Scalekit stores it encrypted in its vault and the account is immediately ACTIVE. The key is injected as the X-Subscription-Token header on every request automatically — your agent code never touches it. If the key is revoked, the account moves to REVOKED. Check account.status before critical operations.
Bring Your Own Credentials — required
Brave Search uses API key authentication. Each user must supply their own key from api.search.brave.com. Register the connection in the Scalekit dashboard, then use upsert_connected_account to register each user’s key. Scalekit handles encrypted storage and per-request injection.
Calling Brave Search

3 Calling Brave Search: What your agent writes

With the connected account active, your agent calls Brave Search tools using execute_tool. Name the tool, pass parameters. Scalekit handles key injection, request construction, and response parsing.

Web search

Search the web with Brave’s privacy-first index. Use freshness to scope results by recency. Omit extra_snippets and summary on Free plan keys — both require Pro.

result = actions.execute_tool( identifier="user_brave_123", tool_name="brave_web_search", tool_input={ "q": "open source LLM inference frameworks 2025", "count": 10, "freshness": "pw", # pw = past 7 days "country": "US", "search_lang": "en" } ) # Returns: web.results[] with url, title, description, age
const result = await actions.executeTool({ identifier: "user_brave_123", toolName: "brave_web_search", toolInput: { "q": "open source LLM inference frameworks 2025", "count": 10, "freshness": "pw", // pw = past 7 days "country": "US", "search_lang": "en" } }); // Returns: web.results[] with url, title, description, age

LLM grounding context

Retrieve search results pre-formatted for grounding LLM responses. Use token_budget to stay within your model’s context window. Requires Pro plan.

result = actions.execute_tool( identifier="user_brave_123", tool_name="brave_llm_context", tool_input={ "q": "vector database comparison 2025", "count": 5, "token_budget": 4000 # fits within most model context windows } ) # Pass result["context"] directly into your LLM prompt
const result = await actions.executeTool({ identifier: "user_brave_123", toolName: "brave_llm_context", toolInput: { "q": "vector database comparison 2025", "count": 5, "token_budget": 4000 // fits within most model context windows } }); // Pass result.context directly into your LLM prompt

AI summary with follow-up queries

Two-step flow: first call brave_web_search with summary: true to get a summarizer.key, then pass that key to the summarizer tools. Requires Pro plan.

# Step 1: web search with summary: true to obtain the summarizer key search = actions.execute_tool( identifier="user_brave_123", tool_name="brave_web_search", tool_input={ "q": "benefits of RAG vs fine-tuning for enterprise LLMs", "summary": True, "count": 5 } ) summarizer_key = search["summarizer"]["key"] # Step 2: retrieve the full AI summary summary = actions.execute_tool( identifier="user_brave_123", tool_name="brave_summarizer_search", tool_input={"key": summarizer_key, "entity_info": True} ) print(summary["title"]) print(summary["summary"]) # Optional step 3: follow-up queries for a conversational search experience followups = actions.execute_tool( identifier="user_brave_123", tool_name="brave_summarizer_followups", tool_input={"key": summarizer_key} ) for q in followups["queries"]: print("-", q)
// Step 1: web search with summary: true to obtain the summarizer key const search = await actions.executeTool({ identifier: "user_brave_123", toolName: "brave_web_search", toolInput: { "q": "benefits of RAG vs fine-tuning for enterprise LLMs", "summary": true, "count": 5 } }); const summarizerKey = search.summarizer.key; // Step 2: retrieve the full AI summary const summary = await actions.executeTool({ identifier: "user_brave_123", toolName: "brave_summarizer_search", toolInput: { key: summarizerKey, entity_info: true } }); console.log(summary.title); console.log(summary.summary); // Optional step 3: follow-up queries for a conversational search experience const followups = await actions.executeTool({ identifier: "user_brave_123", toolName: "brave_summarizer_followups", toolInput: { key: summarizerKey } }); followups.queries.forEach(q => console.log("-", q));

Local place search and POI details

Two-step flow: brave_local_place_search returns location IDs, then brave_local_pois fetches full details. Location IDs expire after ~8 hours — always complete both steps in the same session. Requires Data for AI plan.

# Step 1: find nearby places places = actions.execute_tool( identifier="user_brave_123", tool_name="brave_local_place_search", tool_input={ "q": "specialty coffee", "location": "San Francisco, CA", "count": 5 } ) location_ids = [p["id"] for p in places["results"]] # Step 2: fetch full details (hours, ratings, address) # IDs expire ~8 hours after issue — do not cache or store them pois = actions.execute_tool( identifier="user_brave_123", tool_name="brave_local_pois", tool_input={"ids": location_ids} ) for poi in pois["results"]: print(poi["name"], poi["address"], poi["rating"])
// Step 1: find nearby places const places = await actions.executeTool({ identifier: "user_brave_123", toolName: "brave_local_place_search", toolInput: { "q": "specialty coffee", "location": "San Francisco, CA", "count": 5 } }); const locationIds = places.results.map(p => p.id); // Step 2: fetch full details (hours, ratings, address) // IDs expire ~8 hours after issue — do not cache or store them const pois = await actions.executeTool({ identifier: "user_brave_123", toolName: "brave_local_pois", toolInput: { ids: locationIds } }); pois.results.forEach(poi => console.log(poi.name, poi.address, poi.rating));
Framework wiring

4 Wiring into your agent framework

Scalekit integrates directly with LangChain. The agent selects the right search tool based on user intent; Scalekit handles key injection on every invocation. No credential plumbing in your agent logic.

from langchain_anthropic import ChatAnthropic from langchain.agents import AgentExecutor, create_tool_calling_agent from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from scalekit.langchain import get_tools brave_tools = get_tools( connection_name="brave-search", identifier="user_brave_123" ) prompt = ChatPromptTemplate.from_messages([ ("system", "You are a research assistant with access to Brave Search. Use the available tools to search the web, retrieve news, answer questions with citations, and find local businesses."), MessagesPlaceholder("chat_history", optional=True), ("human", "{input}"), MessagesPlaceholder("agent_scratchpad"), ]) agent = create_tool_calling_agent(ChatAnthropic(model="claude-sonnet-4-6"), brave_tools, prompt) result = AgentExecutor(agent=agent, tools=brave_tools).invoke({ "input": "What are the top AI regulation news stories from the past week? Summarize each with a source link." })
import { ChatAnthropic } from "@langchain/anthropic"; import { AgentExecutor, createToolCallingAgent } from "langchain/agents"; import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts"; import { getTools } from "@scalekit-sdk/langchain"; const braveTools = getTools({ connectionName: "brave-search", identifier: "user_brave_123" }); const prompt = ChatPromptTemplate.fromMessages([ ["system", "You are a research assistant with access to Brave Search. Use the available tools to search the web, retrieve news, answer questions with citations, and find local businesses."], new MessagesPlaceholder("chat_history", true), ["human", "{input}"], new MessagesPlaceholder("agent_scratchpad"), ]); const agent = await createToolCallingAgent({ llm: new ChatAnthropic({ model: "claude-sonnet-4-6" }), tools: braveTools, prompt }); const result = await AgentExecutor.fromAgentAndTools({ agent, tools: braveTools }).invoke({ input: "What are the top AI regulation news stories from the past week? Summarize each with a source link." });
Other frameworks supported
Tool reference

All 17 Brave Search tools

Grouped by capability. Plan requirement noted per group. Your agent calls tools by name — no API wrappers to write.

Search — Free plan and above
brave_web_search
Search the web with privacy-first results. Filter by country, language, recency (freshness), and Goggles re-ranking. Pass summary: true to obtain a summarizer key for Pro plan tools
brave_news_search
Search recent news articles with freshness filtering (pd = 24h, pw = 7 days, pm = 31 days). Returns titles, snippets, publication dates, and source metadata
brave_image_search
Search for images with thumbnails, source URLs, and dimensions. Returns up to 3 results per call
brave_video_search
Search for videos with titles, URLs, thumbnails, durations, and publisher metadata. Supports freshness and pagination
brave_suggest_search
Get autocomplete suggestions for a query prefix. Useful for query completion UIs and exploring related search terms
brave_spellcheck
Check and correct query spelling using Brave’s spellcheck engine. Supports locale-aware corrections via country code
LLM Grounding — Pro plan and above
brave_llm_context
Retrieve search results structured as grounding context for LLMs. Supports token_budget and snippet_budget to control output size. Prefer over raw web search results when feeding an LLM
AI Chat — AI plan
brave_chat_completions
OpenAI-compatible chat completions grounded in real-time Brave Search results. Returns cited answers with source references. Drop-in replacement for /v1/chat/completions for search-augmented generation
Local Search — Data for AI plan
brave_local_place_search
Search 200M+ points of interest by coordinates or location name with an optional keyword filter. Returns location IDs to use with brave_local_pois and brave_local_descriptions
brave_local_pois
Fetch detailed POI data for up to 20 location IDs — address, phone, opening hours, ratings, and reviews. Location IDs expire after ~8 hours; always fetch in the same session
brave_local_descriptions
Fetch AI-generated natural language descriptions for locations using IDs from brave_local_place_search. IDs expire after ~8 hours
Summarizer — Pro plan and above
brave_summarizer_search
Retrieve the full AI-generated summary for a summarizer key. Requires a key from brave_web_search with summary: true. Returns title, content, enrichments, follow-ups, and entity details
brave_summarizer_summary
Fetch only the summary text for a summarizer key — use when you don’t need enrichments or follow-up data
brave_summarizer_enrichments
Fetch enrichment data for a summarizer key: associated images, Q&A pairs, entity details, and source references
brave_summarizer_followups
Fetch suggested follow-up queries for a summarizer key. Useful for building conversational search flows
brave_summarizer_entity_info
Fetch structured metadata for a specific named entity mentioned in a summary — people, places, organisations, and concepts
brave_summarizer_title
Fetch only the title component of a summary. Use when you need a short heading without loading the full summary content
Connector notes

Brave Search-specific behavior

Tools are plan-gated — calling above your tier returns a 401
brave_llm_context and all brave_summarizer_* tools require Pro plan or above. brave_chat_completions requires the AI plan. brave_local_place_search, brave_local_pois, and brave_local_descriptions require the Data for AI plan. A 401 on these tools means the connected user’s API key is on an insufficient plan — not a credential error. Verify the user’s subscription at api.search.brave.com before surfacing an error to them.
Summarizer requires a two-step flow — the key comes from web search
You cannot call brave_summarizer_search directly with a query. You must first call brave_web_search with summary: true to receive a summarizer.key in the response, then pass that key to any brave_summarizer_* tool. Calling summarizer tools without a valid key returns an error. The key is single-use per search session.
Location IDs from local search expire after ~8 hours
IDs returned by brave_local_place_search are ephemeral and expire after approximately 8 hours. Always call brave_local_pois or brave_local_descriptions in the same session as the search — do not cache or store IDs for later use. A stale ID returns a result-not-found error, not an auth error.
Rate limits: 1 req/s on Free, up to 20 req/s on paid plans
The Free plan allows 1 request per second and 2,000 queries per month. Paid plans scale to 20 req/s. Exceeding either limit returns a 429 Too Many Requests error. Build backoff logic around 429 responses — especially for agents that may fan out multiple search calls in parallel.
Infrastructure decision

Why not build this yourself

The Brave Search API is documented. Storing an API key isn’t technically hard. But here’s what you’re actually signing up for:

PROBLEM 01
Per-user API key isolation across a multi-tenant system — one customer’s Brave Search key must never be used for another customer’s requests, even under the same backend process
PROBLEM 02
Encrypted key storage and per-request injection as X-Subscription-Token — not a plaintext database column or env variable, but a proper secrets management layer with access controls
PROBLEM 03
Plan-tier enforcement at runtime — surfacing a clear re-subscription prompt when a user’s key hits a plan-gated tool, rather than returning a confusing 401 that looks like a broken integration
PROBLEM 04
Key revocation detection — when a user rotates or deletes their Brave Search API key, your agent must stop making calls immediately and surface a re-connection prompt rather than silent failures

That’s one connector. Your agent product will eventually need Salesforce, GitHub, Gmail, HubSpot, and whatever else your customers ask for. Each has its own auth quirks and failure modes.

Scalekit maintains every connector. You maintain none of them.

Ready to ship

Search the web. Ship faster.

Free to start. Per-user key isolation handled.
Brave Search
Live

Web Search

Privacy-first

Status
Live
Tools
17 pre-built
Auth
API Key
Credential storage
Sandbox support