CodeWorlds
Back to collections
Guide17 min readCodeWorlds Team

Tavily, a search engine that returns ready context

Tavily returns scored page snippets to an agent instead of a link list. Credits, the free plan limit, the MIT licence and the tool's real boundaries.

Tavily, a search engine that returns ready context

Tavily is a search API built for language models: it answers a query with a set of content snippets carrying a numeric relevance score rather than a list of addresses for you to fetch yourself. The tavily-python package sits at version 0.7.27, @tavily/core at 0.7.7, both under the MIT licence, and billing runs on credits priced at 0.008 dollars under pay-as-you-go.

What actually comes back from a query

The difference against an ordinary search API comes down to one thing: who performs the work between finding an address and putting text into the prompt.

A classic API returns what you see on a results page: a title, an address, and a short description the search engine wrote for a human about to click a link. For a model to use that, your code has to fetch every page, cope with browser-side rendering, cut away navigation, footer and consent banners, split the text into chunks, and decide which of them matter for the question. That is four separate places where something breaks.

Tavily performs that chain on its own side and returns an already processed result. In the results field each item carries title, url, content and score, the last being a floating point number describing relevance against the query. The content field is not the results-page description but a set of snippets cut straight from the body text, where a single snippet runs to at most 500 characters and their count per source is set by the chunks_per_source parameter within a range of 1 to 3. Snippets are joined with a [...] separator, so the text shows where one ends and the next begins.

That shift of responsibility has a price and it is worth naming plainly. You pay for ready context rather than raw material, so you give up control over how the text gets split and what the relevance number is computed from. The scoring algorithm is not described in the documentation, and the score value has no defined reference scale. The authors themselves say only, in the best practices guide, that higher is better and that the cut-off threshold depends on the use case. If you are building a system where you must be able to explain why this particular paragraph made it into the answer, that opacity will get in the way.

The second approach is represented by Firecrawl, where you buy raw material: you supply an address, you get cleaned markdown of the whole page, and you decide about splitting, scoring and selection yourself. Choosing between the two is a choice between fast integration and control over the process, not between a better and a worse tool.

Versions, licence and the state of the packages

The licence here is clean, which after auditing other tools in this collection sounds like a compliment. I checked three sources and all of them say the same thing.

The LICENSE file in the tavily-ai/tavily-python and tavily-ai/tavily-js repositories carries the MIT licence text with a 2024 copyright notice for Alpha AI Technologies Inc. The license field in the npm registry for @tavily/core reads MIT. The published packages do actually contain a licence file: the npm archive has package/LICENSE, the PyPI source archive has LICENSE at its root, and the installation wheel carries it at tavily_python-0.7.27.dist-info/licenses/LICENSE.

One small inconsistency does exist, though. The license field in PyPI metadata for tavily-python is empty, as is the newer license_expression field. The MIT information lives solely in the License :: OSI Approved :: MIT License classifier. A tool harvesting licences only from the license field will therefore see an empty value and mark the package as unspecified. That is not a legal problem but a configuration one, and yet a compliance report has to be able to explain it.

Two things need separating here. What is open under MIT are the clients, a few thousand lines of code wrapping HTTP calls. The search engine itself is a closed service running at api.tavily.com with no self-hosted variant. Vendor lock-in is real and total: if Tavily disappears or raises prices, there is no fallback path short of rewriting the search layer against another provider.

Dependencies are modest. The Python client pulls in requests, httpx and tiktoken at version 0.5.1 or newer. The JavaScript client pulls in axios, https-proxy-agent and js-tiktoken. A token counting library is present because the SDK can trim results to a given token budget before handing them to the model.

The first call and the shape of the response

The shortest route to checking whether the tool fits does not even need an account. Keyless mode is switched on with a header and covers search plus content extraction.

Code
Bash
pip install tavily-python
npm i @tavily/core

curl -X POST https://api.tavily.com/search \
  -H "Content-Type: application/json" \
  -H "X-Tavily-Access-Mode: keyless" \
  -d '{"query": "digital accessibility act amendments", "max_results": 3}'

The documentation describes this mode as free and rate-limited, without stating a specific threshold. The response is meant to be schema-identical to a keyed call, so it suits checking result quality before opening an account.

A proper call looks like this. Every parameter name below comes from the OpenAPI specification of the /search endpoint.

Code
Python
from tavily import TavilyClient

client = TavilyClient(api_key="tvly-...")

response = client.search(
    query="legal status of the NIS2 directive in Poland",
    search_depth="advanced",
    chunks_per_source=3,
    max_results=5,
    topic="general",
    time_range="month",
    include_domains=["gov.pl", "sejm.gov.pl"],
    include_raw_content=False,
    include_answer=False,
    country="poland",
)

for item in response["results"]:
    print(round(item["score"], 3), item["url"])
    print(item["content"][:200])

Several things in that call deserve comment. The search_depth parameter accepts four values: ultra-fast, fast, basic and advanced, ordered from lowest latency to highest relevance. The default is basic. The ultra-fast variant behaves differently from the rest: it returns one summary per source instead of snippets, so chunks_per_source stops having any effect. The max_results parameter ranges from 0 to 20 with a default of 5. The include_domains list holds up to 300 entries, exclude_domains up to 150. The country parameter works only when topic is set to general, so it has no effect on news searches.

The response has a fixed shape, and that is its greatest asset when writing code.

Code
JSON
{
  "query": "legal status of the NIS2 directive in Poland",
  "results": [
    {
      "title": "National cybersecurity system",
      "url": "https://www.gov.pl/web/baza-wiedzy/nis2",
      "content": "First snippet [...] second snippet",
      "score": 0.81025416,
      "raw_content": null,
      "id": "a3f9c2-04"
    }
  ],
  "auto_parameters": { "topic": "general", "search_depth": "basic" },
  "response_time": 1.67,
  "usage": { "credits": 1 },
  "request_id": "123e4567-e89b-12d3-a456-426614174111"
}

The query, results, images, response_time and answer fields are marked required in the specification. The usage field appears once include_usage is set to true and is the only way to learn the cost of a single call at the moment it runs, rather than waiting for the billing panel.

Five endpoints and the division of labour between them

The documentation contradicts itself in places, and it is better to know that before you go hunting for something that supposedly does not exist. The frequently asked questions section lists three endpoints: Search, Extract and Crawl. The API reference additionally describes Map, Research, Usage and Logs. The reference matches reality, the questions section is simply out of date.

The division of duties is sensible and maps directly onto cost. Search finds pages and returns snippets from them. Extract takes known addresses and returns their full content. Map walks a site like a graph and returns the address list alone, without fetching content. Crawl combines Map with Extract, walking the site and pulling content as it goes. Research starts an agent that searches, analyses sources and writes a report with citations.

Code
Python
content = client.extract(
    urls=[
        "https://www.gov.pl/web/baza-wiedzy/nis2",
        "https://eur-lex.europa.eu/eli/dir/2022/2555/oj",
    ],
    extract_depth="advanced",
    format="markdown",
    query="obligations of essential entities",
    chunks_per_source=5,
    timeout=30,
    include_usage=True,
)

site_map = client.map(url="https://docs.tavily.com", instructions="pages about the Python SDK")

For extraction, extract_depth accepts basic or advanced, with the advanced variant reaching for tables and embedded content at the cost of longer response times. The format field accepts markdown or text, markdown by default, and the text variant can be slower. The timeout parameter ranges from 1 to 60 seconds, and without an explicit value the defaults are 10 seconds for basic and 30 for advanced. Supplying query switches on reranking of snippets against that intent, and only then does chunks_per_source apply, here within a range of 1 to 5.

The Research endpoint behaves differently from the rest because it is asynchronous. You create a task, receive a request_id, and either poll for status or listen to a stream.

Code
Python
task = client.research(
    input="Compare incident reporting requirements in NIS2 and in DORA",
    model="pro",
    citation_format="numeric",
    output_length="long",
    include_domains=["eur-lex.europa.eu"],
    stream=False,
)

print(task["request_id"], task["status"])

The mini model is described as geared towards narrow, well-scoped questions, and pro towards complex topics needing multiple angles. That difference translates into cost ranges, covered shortly. A separate rate limit for creating research tasks stands at 20 requests per minute and is the same for development and production keys, while polling for task status falls under the general limit.

Credits, pricing and the free plan limit

Billing runs on credits, and the conversion depends on which endpoint you use and with what parameters. Credits reset on the first day of each month, regardless of the billing date.

OperationCost in credits
Search basic, fast, ultra-fast1 per request
Search advanced2 per request
Extract basic1 per 5 successful fetches
Extract advanced2 per 5 successful fetches
Map without instructions1 per 10 pages returned
Map with instructions2 per 10 pages returned
Crawlmapping cost plus extraction cost
Research mini4 to 110 per request
Research pro15 to 250 per request

A failed page fetch is not counted, and neither is a failed mapping. An example from the documentation: walking ten pages with basic extraction costs 1 credit for mapping plus 2 for extraction, 3 credits in total. The same ten pages with advanced extraction cost 1 plus 4, 5 credits in total.

The monthly plans run as follows: Researcher gives 1,000 credits a month for free, Project 4,000 credits for 30 dollars, Bootstrap 15,000 credits for 100 dollars, Startup 38,000 credits for 220 dollars, Growth 100,000 credits for 500 dollars. Pay-as-you-go costs 0.008 dollars per credit. Enterprise is priced individually.

The arithmetic checks out, which after experiences with other price lists is worth verifying. Dividing price by credit count gives 0.0075, 0.00667, 0.00579 and 0.005 dollars in turn, while the documentation states 0.0075, 0.0067, 0.0058 and 0.005, the same numbers once rounded. The declared monthly plan range of 0.0075 down to 0.005 dollars per credit holds as well.

The free plan limit is a genuine constraint and needs converting into your own use case. A thousand credits means a thousand basic searches or five hundred advanced ones per month. An agent running three advanced searches per conversation will burn that allowance after fewer than 170 conversations. Enough for a prototype, not for production. One line in the documentation is easy to miss here: production keys, with a limit of 1,000 requests per minute instead of 100, require an active paid plan or pay-as-you-go enabled. On the free plan you stay on the development limit.

A student discount exists, but the terms are not published and eligibility is settled by email with support. Email support goes to paid plans, and an availability and response time agreement to the Enterprise plan alone.

Result freshness and what nobody guarantees

This question usually gets asked after the first stale answer reaches production, so it is better explained beforehand.

There is no freshness guarantee of any kind. The documentation describes results as up to date and real-time, but nowhere states index lag, the window within which a new page becomes findable, or any contractual commitment on the matter. The only binding statements about service availability concern the Enterprise plan and speak of uptime and support response, not of data currency.

What you do get are date filters rather than a coverage promise. The time_range parameter accepts day, week, month, year and their single-letter abbreviations. The start_date and end_date parameters accept dates in year-month-day format. The documentation flags an important detail: filtering works on the publication date or the date the page was last updated. A site that bumps its modification date on every rebuild will pass a time_range filter set to a day even though the content dates back years. The filter narrows the set, it does not verify it.

The practical conclusion is that use cases depending on currency need a control layer of your own. Set topic to news or finance, since those categories are described as geared towards current events. Ask the model to report the date visible inside the snippet text and compare it against today on your side. For questions about current state where the cost of an error is high, going straight to the data source beats going to a search engine.

Tavily against the alternatives

Four approaches to the same problem, ordered by how much work stays on your side.

FeatureTavilyFirecrawlClassic search APIProvider-side search
What comes backscored content snippetsfull page as markdowntitle, address, short descriptionmodel answer with references
Who fetches and cleans HTMLthe providerthe provideryour codethe provider
Who splits text into chunksthe provideryour codeyour codethe provider
Billing unitcredit per call or pagecredit per pagequerytokens plus a tool fee
Control over source selectiondomain lists, date filtersfull, you supply addressesdomain lists, provider dependentlimited
Self-hosting optionnoneopen server, AGPL-3.0 licencenonenone
Client licenceMITMITprovider dependentprovider SDK licence

The selection rule is short. Take Tavily when an agent needs to put a question to the web and receive material ready to paste into a prompt, and you would rather not maintain your own page fetching. Take Firecrawl when the addresses are known or come from one site and you want full content plus control over splitting. Pick a classic search API when you need addresses alone, for your own index for instance. Provider-side search, available in Claude and in OpenAI, is the pick when you want as few moving parts as possible rather than control over source selection.

One more approach is missing from this comparison. Exa builds its own index on embeddings, so a query describing the meaning of the page you want lands better there than a set of keywords. The price is twofold: for a query about a specific proper name, version number or error code, literal matching usually beats semantic matching, and the index and ranking model are closed, with no variant you can run yourself, so vendor lock-in there is complete.

The integration layer is rarely the problem. Tavily ships tools for LangChain and LlamaIndex, a remote server speaking the MCP protocol, and a command line interface. For tasks needing clicks in an interface, logging in or filling forms, none of these tools will do, and that is where browser driving of the browser-use kind comes in.

Common mistakes

The first is leaving search_depth at its default and being surprised by result quality. The basic mode is a compromise between time and relevance, and the authors in their agent guide recommend advanced outright, with chunks_per_source set to 3. It costs two credits instead of one, so the difference shows in the bill, but on niche questions it shows in the answers as well.

The second is switching on auto_parameters without reading the consequences. That flag lets the service pick parameters itself, but the documentation notes that search_depth may be raised to advanced where the service judges it helpful. Every such request then costs two credits rather than one, with no warning in your code.

The third is treating time_range as a freshness guarantee. The filter works on publication date or last modification, and a good share of sites bump the latter on every page rebuild. Narrowing to the last day does not mean the content is from the last day.

The fourth is running crawl to learn the size of a site. Walking with extraction costs mapping plus extraction, while map on its own costs one credit per ten pages and returns the address list. The correct order only becomes obvious after the first bill.

The fifth is not handling a 429 response. The limit for a development key is 100 requests per minute, for a production key 1,000, for site walking 100 regardless of key type, for creating research tasks 20, and for the usage endpoint 10 per ten minutes. The response carries a retry-after header with a second count, and that value is what should drive the retry.

The sixth is skipping include_usage. Without that field you do not know the cost of a call at the time it runs, and for research tasks the spread is enormous, since a single request on the pro model falls anywhere between 15 and 250 credits. That is the difference between one and sixteen percent of the entire free monthly allowance.

The seventh is keeping the key on the browser side. The JavaScript client runs in a browser too, so the temptation is real, and the outcome matches any other API key: the bill lands on you and the key is visible to anyone who opens developer tools.

FAQ

How does Tavily differ from an ordinary search API?

An ordinary API returns a title, an address and a results-page description, leaving fetching and cleaning to you. Tavily returns snippets cut from the body text, each at most five hundred characters, along with a numeric relevance value in the score field. You buy ready context instead of raw material, paying with control over how splitting and scoring happen.

How far does the free plan actually go?

A thousand credits a month, meaning a thousand basic searches or five hundred advanced ones. No card is required and credits reset on the first day of the month. A production key with a limit of 1,000 requests per minute does require a paid plan or pay-as-you-go enabled, though, so on the free plan you stay at 100 requests per minute.

Does Tavily guarantee result freshness?

No. The documentation describes results as current but states no index lag and no commitment on the matter. What you get are the time_range, start_date and end_date filters, working on the publication or last update date of a page. The availability and response time agreement covers the Enterprise plan alone and does not extend to data currency.

Can Tavily be self-hosted?

No. What is open under MIT are the clients, tavily-python and @tavily/core, while the search engine itself is a closed service at api.tavily.com. Vendor lock-in is total, so the search layer in your code is best kept behind an interface of your own, so that swapping providers does not mean rewriting the application.

When should I pick Firecrawl over Tavily?

When you know the addresses or work within a single site and need the full page content rather than selected snippets. Firecrawl also offers a self-hosted server under the AGPL-3.0 licence, which Tavily does not. The other way round: when the starting point is a question rather than an address, Tavily saves the entire fetching and splitting stage.

Do I need an account to try it?

No. Keyless mode covers search and content extraction, switches on with the X-Tavily-Access-Mode: keyless header, and returns responses schema-identical to keyed calls. The documentation describes it as free and rate-limited without stating a threshold, so it suits judging result quality rather than production work.

The full parameter specification sits in the API reference, the credit conversion in the pricing section, and the client code in the tavily-python and tavily-js repositories.

Read next

We use cookies to enhance your experience on the site