Tavily
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
Options
- Filters
includeDomains, excludeDomains, category, startPublishedDate, endPublishedDate - Fields
score, publishedDate, text - Category
general, news, finance - Read
markdown, text · format, timeout
Address it
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
| Call | Endpoint |
|---|---|
search | POST /search with max_results, search_depth: "basic", include_answer, include_raw_content, include_published_date: true, and topic, start_date, end_date when asked |
read | POST /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_depthis alwaysbasic. 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 nopublishedDate. 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_dateisn't sent on purpose. - A spent plan or pay-as-you-go cap comes back as HTTP 432 or 433, not 402. Both are
PaymentErrorhere, 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
resultsand the reason infailed_results(404 page not found,Failed to fetch url, which is also what a PDF gets).Failed to fetch urlmeans Tavily's own fetcher could not get the page, so it becomes aPageFetchErrorand an automatic read moves on to the next reader.404 page not foundand any reason not known here stay aWebErrorwith the reason, not a fallback. urlon a read is the one you asked for. Extract does not report where a redirect ended up.- Extract is
basicdepth,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.