Providers

Tavily

The Tavily search API, with an optional generated answer in response metadata, and Tavily Extract for reading pages.
IDtavily12 / 13api.tavily.com
Provider

Tavily

  • create("tavily")
  • TAVILY_API_KEY
Auth
request body for search, Bearer header for read
Results
10, up to 20 · one page
Free tier
1k queries a month

Capabilities

Options

FiltersincludeDomains, excludeDomains, category, startPublishedDate, endPublishedDate
Fieldsscore, publishedDate, text
Categorygeneral, news, finance
Readmarkdown, text · format, timeout

Address it

ts
import { create, readUrl, searchProviderDetailed } from "@agntn/web";

const tavily = await create("tavily"); // TAVILY_API_KEY
const results = await tavily.search("query", { includeDomains: ["docs.python.org"] });
const recent = await tavily.search("query", { category: "news", startPublishedDate: "2026-06-01" });

const answer = await searchProviderDetailed("tavily", "query", { summary: true, fullText: true });
answer.metadata?.answer; // the generated answer for the query

const page = await readUrl("https://docs.python.org/3/", { provider: "tavily", format: "text" });

Search puts the key in the body of a POST to api.tavily.com, Extract takes it as a Bearer token.

What it reads

CallEndpoint
searchPOST /search with max_results, search_depth: "basic", include_answer, include_raw_content, include_published_date: true, and topic, start_date, end_date when asked
readPOST /extract with one URL in urls, extract_depth: "basic", format and timeout

What comes back

snippet is Tavily's content, a few passages of the page glued together with [...]. That runs past 1,000 characters more often than not, so it stops at 500 with …. Results also carry score, publishedDate and, with fullText, the whole page as text from raw_content. summary asks for the query answer, which lands in response metadata.answer, not on a result. Include and exclude domains work, startPublishedDate and endPublishedDate go through as start_date and end_date, and category set to general, news or finance picks Tavily's topic.

Reads give markdown or text in content, the page title and Tavily's request_id in metadata.requestId. timeout is passed on in seconds, clamped to Tavily's 1 to 60. No links, images or selectors, and maxTokens and noCache are ignored.

Gotchas

  • The answer is one string for the whole query, so it lives on the response, not on a result. search() alone cannot show it, searchDetailed() can.
  • search_depth is always basic. Advanced depth costs more credits and is not exposed, on purpose.
  • Tavily dates a result only when asked, so every search asks. It writes the date as Tue, 11 Mar 2025 17:00:00 GMT, which becomes ISO 8601 here like everyone else's, and a page it couldn't date has no publishedDate. Tavily calls the field beta.
  • The window is by day, so a datetime bound becomes its UTC day. Pages Tavily couldn't date stay in, three of five on a plain query, so filter_by_published_date isn't sent on purpose.
  • A spent plan or pay-as-you-go cap comes back as HTTP 432 or 433, not 402. Both are PaymentError here, so automatic search and read move on to the next configured provider. A wrong key is still 401 and still stops the chain.
  • A page Extract cannot fetch is not an HTTP error. The response is a 200 with an empty results and the reason in failed_results (404 page not found, Failed to fetch url, which is also what a PDF gets). Failed to fetch url means Tavily's own fetcher could not get the page, so it becomes a PageFetchError and an automatic read moves on to the next reader. 404 page not found and any reason not known here stay a WebError with the reason, not a fallback.
  • url on a read is the one you asked for. Extract does not report where a redirect ended up.
  • Extract is basic depth, advanced (tables and embedded content, twice the credits) is not exposed. Basic costs one credit per five successful pages.

Where it lives

src/providers/tavily.ts.