Tavily is a trademark of its respective owner, used here for identification and migration comparison only. No endorsement, partnership, or affiliation is implied.
search returns ranked, snippet-rich results; extract pulls clean content from URLs; crawl and
map traverse whole sites. It does agent-grade retrieval well — a purpose-built ranker, an
LLM-friendly response shape, and a generous free tier. But when the thing doing the searching is an
agent that also holds cards, an inbox, a phone number, and a KYC’d identity, a Tavily account is
a disconnected vendor account:
- Retrieval lives behind a Tavily API key (
tvly-...) — a credential that exists only inside Tavily, unrelated to where the same agent’s cards, email, and KYC live. - Whether the agent may search, and how much it may spend doing so, is not something Tavily governs at execution time — it is a key you hold. There is no “can this agent research right now?” check tied to the agent’s broader permission set.
- “Which agent ran this query, for which end-user, and who let it?” is answered in Tavily’s usage dashboard for search, and in unrelated systems for the agent’s spend, comms, and identity.
search primitive gives the agent the same capability —
web_search, read_url, and research — but it is one primitive on one governed account. And
it is honest to say the results are the same: Naive’s search is Tavily-backed under the hood
(with a Brave fallback), so web_search proxies Tavily Search and read_url proxies Tavily Extract.
You keep Tavily-quality retrieval and gain:
- The Account Kit that decides whether the agent may
issue a card or send email also decides whether
it may use
searchat all — checked at execution time, not in a separate dashboard. - Search spend is metered in the same credit balance and budget ceiling as every other primitive, so a runaway agent hits one cap, not a separate Tavily bill.
- Every query records the acting agent and lands in the same activity log as its card spend, email sends, and KYC events.
search exposes a much smaller set
of knobs than Tavily (no crawl/map, no per-request search_depth/topic/domain filters), and
that search is workspace-scoped, not per-end-user isolated.
Tested against: the Tavily JavaScript SDK
@tavily/core v0.7.6 (against the Tavily REST API,
base https://api.tavily.com, /search and /extract endpoints, docs snapshot July 2026), and the
Naive search primitive over the Naive REST API (base
https://api.usenaive.ai/v1, /search, /search/url, /search/research endpoints, docs snapshot
July 2026). The Naive @usenaive-sdk/server SDK does not ship a dedicated search
sub-client yet — call search over REST (shown below) or via the naive search CLI.Version notes:- Auth model differs. Tavily authenticates with an account-wide bearer key (
tvly-...). Naive authenticates per workspace withNAIVE_API_KEY, and you scope an end-user withnaive.forUser(id)/POST /v1/users/:id/search— the same handle that reaches every other primitive. - Same provider under the hood. Naive
web_searchcalls Tavily/search; Naiveread_urlcalls Tavily/extract. If Naive is configured with Brave instead, results come from Brave — you don’t choose the provider, and you can’t pass your own Tavily plan/key. See what doesn’t map yet. - Smaller parameter surface. Naive
web_searchtakesquery+count(1–20). Tavily’ssearch_depth,topic,time_range,include_domains/exclude_domains,country,include_answer,include_raw_content, andexact_matchhave no pass-through today. - Naive adds
research.POST /v1/search/researchdoes multi-step search + synthesis with citations (a synchronousquickmode and asyncthorough/exhaustivejobs). On Tavily you’d composesearchwith your own LLM (or useinclude_answerfor a one-line answer).
Concept map
| Tavily | Naive | Notes |
|---|---|---|
tavily({ apiKey }) | new Naive({ apiKey }) then naive.forUser(id) | Server-side credential in both cases |
Account-wide tvly-... API key | tenant user via naive.forUser(id) — scopes every primitive | Search data is workspace-scoped today; see the isolation caveat |
tvly.search(query, { maxResults }) → POST /search | POST /v1/search { query, count } | Ranked web results; count maps to maxResults (1–20) |
Result { title, url, content, score } | Result { title, url, snippet } | Naive renames content → snippet; no score/rawContent in the response |
tvly.extract(urls, { extractDepth }) → POST /extract | POST /v1/search/url { url, extract? } | Naive reads one URL/call; extract is an optional AI focus prompt |
Extract result { url, rawContent } | { url, content, extracted? } | extracted returned only when you pass extract |
include_answer: true (one-line LLM answer) | POST /v1/search/research { query, depth: "quick" } → { summary, sources } | Naive synthesizes a multi-source answer with citations |
— (compose search + your LLM) | POST /v1/search/research { query, depth: "thorough" | "exhaustive" } → async job | Returns 202 { job_id }; poll GET /v1/jobs/:id |
tvly.crawl(url, ...) / tvly.map(url, ...) | — | Site traversal / sitemap not available on Naive |
Per-request topic, time_range, include_domains, country, search_depth, exact_match | — | Not surfaced by Naive web_search — see gaps |
| Whether an agent may search = a key you hold | Account Kit search primitive enabled: true/false | Permission is execution-time policy on the identity — see gain #2 |
| Account-wide Tavily credits / bill | Same credit balance + budget ceiling as every primitive | web_search/read_url cost 1 credit; research costs 3–8 |
| Tavily usage dashboard (per key) | Same per-user activity log as cards, email, KYC | Every query records the acting agent |
Before / after: the core path
The path that matters for almost every agent is search the web, then read the best result — the classic grounding / RAG loop. Here it is on both platforms.maxResults→count. Both cap at 20; Naive defaults to 5.- Result field rename. Tavily’s per-result
contentsnippet becomessnippeton Naive; Naive’s response is{ title, url, snippet }and does not includescoreorrawContent. extractreads one URL. Tavily’sextractaccepts up to 20 URLs per call; Naive’sread_urltakes a singleurl(loop if you have several), plus an optionalextractfocus prompt that returns an AI-narrowedextractedfield.- Synthesis is a separate call. Where you’d set Tavily
include_answer: true, call NaivePOST /v1/search/researchfor a cited, multi-source answer (see below).
The synthesis path
If you were leaning on Tavily’sinclude_answer (or wiring search results into your own LLM), Naive’s
research tool does the multi-step version natively:
Minimal viable migration
The smallest swap that keeps a working agent running is search + read (and optionally research).Set your key
Use your existing
NAIVE_API_KEY (a server-side key from the
dashboard). No separate search vendor account or tvly-... key
to provision.Swap the search call
Map
tvly.search(query, { maxResults }) → POST /v1/search { query, count }. Read results from
results[].snippet instead of results[].content. Drop Tavily-only options
(search_depth, topic, include_domains, …) — they have no pass-through yet
(gaps).Swap the extract call
Map
tvly.extract([url]) → POST /v1/search/url { url }, reading content (Tavily’s
rawContent). One URL per call — loop for several. Add an extract focus prompt to get an
AI-narrowed extracted field.Swap synthesis (optional)
Replace
include_answer / your own LLM-over-results with POST /v1/search/research
{ query, depth }. Use quick for an inline { summary, sources }; use thorough/exhaustive
for a 202 { job_id } you poll at GET /v1/jobs/:id.Reach for the CLI where handy
The same primitive is on the CLI:
naive search "...", naive search url <url> --extract "...",
and naive search research "..." --depth thorough --wait.Consolidate further once you’re on Naive
This is where the migration pays for itself. On Tavily, search is a self-contained account: an API key, its own credit pool, and its own dashboard — disconnected from the agent’s money, comms, and identity. On Naive,search is one primitive on the same account and Account Kit that governs the
agent’s cards, email, phone, and vault.
Gain #1 — one account across primitives
- With Tavily, search is an island behind its own key and credit pool. With Naive,
naive.forUser(acme.id)is a single handle to search and cards and email and phone and KYC — no second vendor account, notvly-...key to provision and rotate, no separate invoice. - Third-party connections (OAuth grants like Slack or Notion) and the agent’s native primitives sit under one identity and one Account Kit — the SaaS the agent connected and the web it searches, governed the same way.
Gain #2 — execution-time permission enforcement
- Whether an agent may search at all is the
searchprimitive on the user’s Account Kit, decided at execution time — not a key you gate separately from everything else the agent can do.
- The agent’s code path stays the same for every tier — the same
POST /v1/search. Whether it runs is decided by policy: a user whose Account Kit does not enablesearchis refused withforbidden(primitive_disabled_by_kit), with no code change on your side. - Each
web_search/read_urlspends 1 credit (andresearch3–8) against the same budget ceiling as the agent’s cards, phone, and LLM calls — so a misbehaving agent trips one combined cap, not a separate Tavily limit you monitor on its own.
The
search primitive is gated by the Account Kit toggle and the budget, but it is not frozen for
human approval by default — unlike money-moving primitives (cards, trading, domains). And it is
company-scoped: the kit gates whether a user’s agent may search, but search itself doesn’t hold
per-end-user data to isolate. See what doesn’t map yet.Gain #3 — unified accountability
- Every query records the acting agent and lands in one per-user activity log — alongside that customer’s card spend, email sends, and KYC events, not in a separate Tavily usage dashboard:
- That is the question that is hard to answer when queries live in Tavily, cards live in Stripe, and the inbox lives somewhere else. Under Naive it is a single query.
What does not map yet
A migration guide that hides gaps is worse than none. The core path (search → read → synthesize) maps cleanly, but the following Tavily capabilities have no direct equivalent on Naive’ssearch
primitive today. Check this list against your app before you commit.
| Tavily feature | Status on Naive | Workaround |
|---|---|---|
crawl (agent-guided site traversal) and map (sitemap discovery) | Not supported | Keep Tavily for crawl/map, or drive traversal yourself from web_search + read_url |
Per-request search knobs — search_depth (advanced/fast/ultra-fast), topic (news/finance), time_range/start_date/end_date, include_domains/exclude_domains, country, exact_match, chunks_per_source | Not surfaced — Naive web_search takes only query + count | Refine within the query text; use Tavily directly if these filters are load-bearing |
include_images / image results | Not returned by web_search | Use the images primitive for image needs |
include_raw_content on search results + result score | Not in the Naive search response ({ title, url, snippet }) | Follow up with read_url to get page content |
Multi-URL extract (up to 20 URLs/call) | read_url reads one URL per call | Loop over URLs client-side |
| Provider / plan choice + BYO Tavily key | Naive picks the provider (Tavily, or Brave fallback); you don’t select it or supply a tvly-... key | None — this is by design; keep a direct Tavily account if you need plan-level control |
Session / project tracking headers (X-Session-Id, projectId) | Not applicable — attribution is Naive’s per-user logs | Use naive.forUser(id) + logs for attribution |
| Tavily Hybrid RAG (MongoDB-backed local + web retrieval) | Not provided | Pair web_search with your own vector store |
| Per-end-user data isolation | search is company-scoped — the kit gates a user’s agent access, but there is no per-user search data to isolate | Use a Naive workspace per end-user only if you need hard separation of other per-user data |
Where to go next
searchprimitive — fullweb_search/read_url/researchreference, depth levels, and the choose-the-right-tool guidesearchCLI —naive search,naive search url,naive search research- Account Kits — enabling/disabling the
searchprimitive per user at execution time - Tenant users — the identity that owns search, cards, email, and phone
- Billing — the shared credit balance and budget ceiling search spends against
- Logs — the unified per-user activity trail every query lands in