Tavily was built for retrieval pipelines rather than adapted for one: every result carries a relevance score, and a single call can also return an LLM-ready answer synthesized from what it found, so there’s no separate summarization step. NativePort proxies Tavily’s API under one key and balance; this guide covers the /search endpoint, which covers the bulk of what agents built on Tavily actually call.
Why through NativePort
Tavily bills in credits and reports what each response used. Here you pay exactly that, and your balance covers your scraping and your model calls too.
What you’ll need
- A NativePort API key. Sign up to get a key and $5 in credits.
- Python 3, standard library only — the example below uses
urllib.request, nopip install. - Your key exported as an environment variable, never hardcoded:
export NATIVEPORT_API_KEY="np_..."
What’s admitted
/tavily/<endpoint> forwards to the matching endpoint on api.tavily.com, POST only — the method Tavily’s own action endpoints document:
search— the general-purpose query endpoint this guide covers.extract— pull the full content of one or more known URLs, no search involved.crawl— start from a URL and follow links outward, gathering content per page.map— discover a site’s URL structure without extracting content.
Tavily’s /usage endpoint (account-wide credit and limit metadata) is deliberately not exposed — it’s account-wide billing state, not a search interface, and a per-client key has no business reading the shared account’s usage. Any endpoint outside the four above 404s before it ever reaches Tavily.
Run a search
import json
import os
import urllib.request
GATEWAY = "https://api.nativeport.ai"
API_KEY = os.environ["NATIVEPORT_API_KEY"]
def search(query, **kwargs):
body = json.dumps({"query": query, **kwargs}).encode()
req = urllib.request.Request(f"{GATEWAY}/tavily/search", data=body, method="POST")
req.add_header("Authorization", f"Bearer {API_KEY}")
req.add_header("Content-Type", "application/json")
req.add_header("User-Agent", "nativeport-python-guide/1.0")
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read())
result = search("what changed in the latest Kubernetes release", max_results=5)
for item in result["results"]:
print(f"{item['score']:.2f} {item['title']} {item['url']}")
query is the only required field; everything else is Tavily’s own optional parameter set, forwarded verbatim — max_results (0–20, default 5), topic (general, news, finance), time_range, include_domains/exclude_domains, and the two fields worth calling out on their own:
include_answer:true(or"basic"/"advanced") adds a synthesizedanswerstring to the response, built from the result set Tavily itself just retrieved.include_raw_content:true(or"markdown"/"text") adds each result’s full page content alongside its snippet, so a follow-upextractcall isn’t needed just to get the whole page.
The response is Tavily’s own shape, untouched:
{
"query": "what changed in the latest Kubernetes release",
"answer": "The release adds ...",
"results": [
{
"title": "Kubernetes v1.34 release notes",
"url": "https://kubernetes.io/releases/...",
"content": "This release introduces...",
"score": 0.94
}
],
"response_time": 1.12,
"usage": {"credits": 1},
"request_id": "..."
}
answer is present only when include_answer was set on the request; usage likewise only shows up when include_usage: true is on the request — set it if you want the response itself to confirm what a call cost, since that’s also the same field the gateway bills from (see below).
Errors you’ll actually hit
402 Payment Required: the NativePort balance is at $0. Every request answers this deterministically rather than degrading, so top up and retry.401 Unauthorized: theAuthorization: Bearer <NATIVEPORT_API_KEY>header is missing, malformed, or doesn’t resolve to an account.403 Forbidden: the key is valid but the account isn’t active.404on/tavily/usageor anything outsidesearch/extract/crawl/map: not admitted at the gateway — see “What’s admitted” above.- An empty
resultsarray with a200: not a gateway error — Tavily itself found nothing for that query at the requestedsearch_depth. Retrying withsearch_depth: "advanced"or a broadertime_rangeis a Tavily-side tuning question, not something to retry blindly.
Security
Treat NATIVEPORT_API_KEY like any other credential: environment variable or secret store, never a literal string in source, never logged. Tavily’s own credential never reaches your process — the gateway injects it server-side.
What this costs
The gateway bills from usage.credits when the response carries it (set include_usage: true to guarantee that), falling back to a 1-credit charge if it’s missing — at Tavily’s own per-credit rate, no NativePort markup. A basic-depth search is 1 credit; search_depth: "advanced" is 2; extract, crawl and map price per URL or page processed rather than per call. Current per-credit pricing is on the Tavily provider page. A zero balance answers every request with 402 instead of degrading. Full breakdown: pricing.
Where to go next
- Tavily provider page: current pricing and benchmark standing.
- Web search leaderboard and sourced answers leaderboard: how Tavily’s search and
include_answeroutput score against the rest of the field. - Pricing: the full billing model.
- Evaluating a unified AI platform instead of a native call like this one? The trade-off against Eden AI lays it out.