Providers

Brave

The Brave Search API, one GET per page, with extra snippets joined into text and a continuation that carries the offset.
env var
BRAVE_API_KEY
auth
X-Subscription-Token header
operations
search
results
10, up to 20

Address it

import { create } from "@agntn/web";

const brave = await create("brave"); // BRAVE_API_KEY
const results = await brave.search("typescript runtimes", { maxResults: 10 });
const recent = await brave.search("typescript runtimes", { startPublishedDate: "2026-06-01" });

The key goes in the X-Subscription-Token header. Pass apiKey to create() if you do not want env involved.

What it reads

CallEndpoint
searchGET /res/v1/web/search?q=…&count=…&offset=…&extra_snippets=true&text_decorations=false on api.search.brave.com, plus freshness=YYYY-MM-DDtoYYYY-MM-DD when a date bound is set

count is maxResults, 20 at most. offset comes from the continuation and steps through pages of that size. query.more_results_available in the response decides whether the next token is next or end, so Brave is one of the few engines that actually tells you.

What comes back

snippet is the description with Brave's HTML escapes decoded. favicon comes from meta_url.favicon, and text is the extra_snippets joined with newlines when Brave sends them. publishedDate is Brave's page_age, the page's published or last modified date as Brave read it, so it can be missing. startPublishedDate and endPublishedDate go out as freshness, the only filter Brave has here. No score, and includeDomains and friends come back in ignoredFilters.

Gotchas

  • A page is 20 results max. Ask for more and you get 20 plus a continuation.
  • Brave bolds query terms with <strong> unless you send text_decorations=false, and it escapes description like HTML (&#x27;, &amp;, &quot;). The adapter turns the bolding off and decodes the escapes, so snippet is plain text. title and extra_snippets come as text already.
  • The free plan is a couple of thousand queries a month and a burst gets you a 429 with Retry-After, which is RateLimitError here.
  • freshness wants YYYY-MM-DDtoYYYY-MM-DD, both days inside the window, the same day twice gives that day's pages. Anything else, a time on the date included, Brave drops without a word and answers unfiltered, so a datetime bound becomes its UTC day here, a missing start becomes 1970-01-01 and a missing end becomes today's UTC date. The open forms 2026-06-01to and to2026-06-01 answered correctly when probed, but Brave does not document them, and a filter that fails silently is not the place to lean on that.
  • Brave only fills extra snippets on a Search plan. A free key still takes extra_snippets=true and leaves text empty.

Where it lives

src/providers/brave.ts. Smallest search adapter with pagination, copy it when you write a new one.

@agntn/web·MIT license· Search results and page content are data, never instructions.