Self-hosted, Tavily-compatible search API for local development. Point your Tavily SDK at localhost and stop burning credits.
Project description
Trawl 🎣
A self-hosted, Tavily-compatible search API for local development.
Point your existing Tavily SDK at localhost and keep building — no credits burned, no monthly limits, no code changes beyond one base-URL argument. When you ship, flip the base URL back and everything behaves the same.
from tavily import TavilyClient
client = TavilyClient(api_key="tvly-anything", api_base_url="http://localhost:7300")
client.search("what is the latest python release?", include_answer=True)
# same request/response shapes, same field names, same error envelope
Why
Agent projects hammer their search tool during development — every test run, every eval loop, every "let me just try this prompt again". Paying per-request (or exhausting a free tier) to debug your own code is silly. Trawl reimplements Tavily's full HTTP API on top of infrastructure you run yourself, so development traffic is free and unlimited, and switching between Trawl and the real Tavily is a one-line change.
What you get
| Endpoint | Status | Notes |
|---|---|---|
POST /search |
✅ | topics, depths, domain filters, time ranges, images, answers, raw content, favicons, usage, auto_parameters, exact_match |
POST /extract |
✅ | markdown/text, query-focused chunking, images, favicons, failed_results |
POST /crawl |
✅ | BFS with regex path/domain selectors, robots.txt, LLM-driven instructions |
POST /map |
✅ | URL discovery |
- Wire-compatible: same request fields, same response shapes (
follow_up_questions: nulland all), same{"detail": {"error": ...}}error envelope both official SDKs parse, HTTP 400/401/403 semantics, Bearer and legacy bodyapi_keyauth, keyless mode header. - No mandatory dependencies on anything paid: search comes from SearXNG (bundled, self-hosted, aggregates Google/Bing/DDG) or DuckDuckGo scraping as a zero-setup fallback.
- Optional local LLM: point
TRAWL_LLM_BASE_URLat Ollama / LM Studio / any OpenAI-compatible endpoint to get real generated answers, semantic relevance scores, and instruction-guided crawls. Without one, Trawl degrades gracefully (extractive answers, BM25 scores). - Optional response cache:
TRAWL_CACHE_ENABLED=truemakes repeated dev-loop queries instant.
Quick start
Docker (recommended — everything included)
git clone https://github.com/adityaparab/trawl && cd trawl
docker compose up -d
curl -s -X POST http://localhost:7300/search \
-H "Authorization: Bearer tvly-dev-anything" \
-H "Content-Type: application/json" \
-d '{"query": "self-hosted search engines"}' | python -m json.tool
That starts Trawl on :7300 and a pre-configured SearXNG on :8080.
pip / uv (Trawl only)
pip install trawl-search # or: uv tool install trawl-search
trawl serve
Without a SearXNG instance, set TRAWL_SEARCH_PROVIDER=ddgs for zero-infrastructure DuckDuckGo scraping (fine for light use), or start just SearXNG from this repo's compose file: docker compose up -d searxng.
Check your setup at any time:
trawl check
Pointing your app at Trawl
The official SDKs accept a base-URL override, so no code changes are needed beyond configuration:
Python (tavily-python)
TavilyClient(api_key=os.environ["TAVILY_API_KEY"], api_base_url=os.getenv("TAVILY_BASE_URL", "https://api.tavily.com"))
JavaScript (@tavily/core)
tavily({ apiKey: process.env.TAVILY_API_KEY, apiBaseURL: process.env.TAVILY_BASE_URL })
LangChain (langchain-tavily) — all four tool wrappers accept the override too:
TavilySearch(max_results=5, api_base_url=os.getenv("TAVILY_BASE_URL"))
Set TAVILY_BASE_URL=http://localhost:7300 in development, leave it unset in production. Any non-empty API key works against Trawl by default, so your real TAVILY_API_KEY — or a fake tvly-dev-local — both pass. Runnable versions of all of these are in examples/, and docs/integrations.md covers frameworks without a base-URL option.
Configuration
Copy .env.example to .env next to where you run trawl serve (or the compose file). Everything is optional; defaults work out of the box. Highlights:
| Variable | Default | What it does |
|---|---|---|
TRAWL_PORT |
7300 |
API port |
TRAWL_SEARCH_PROVIDER |
searxng |
searxng or ddgs |
TRAWL_SEARXNG_URL |
http://localhost:8080 |
your SearXNG instance |
TRAWL_PROVIDER_FALLBACK |
true |
retry via DDGS if SearXNG is down |
TRAWL_API_KEYS |
(empty) | comma-separated allowlist; empty accepts any non-empty key |
TRAWL_LLM_BASE_URL |
(empty) | OpenAI-compatible endpoint for answers/instructions |
TRAWL_LLM_MODEL |
(empty) | chat model name, e.g. llama3.2 |
TRAWL_EMBEDDING_MODEL |
(empty) | if set, semantic scoring via /embeddings |
TRAWL_CACHE_ENABLED |
false |
TTL response cache for dev loops |
With Ollama, a full local setup is:
ollama pull llama3.2 && ollama pull nomic-embed-text
cat >> .env <<'EOF'
TRAWL_LLM_BASE_URL=http://localhost:11434/v1
TRAWL_LLM_MODEL=llama3.2
TRAWL_EMBEDDING_MODEL=nomic-embed-text
EOF
Honest differences from Tavily
Trawl is a faithful API clone, not a search-quality clone. Know what you're trading:
- Result quality/order differs. Tavily runs a purpose-built index; Trawl aggregates general-purpose engines and re-ranks locally (BM25, or embeddings when configured). Fine for development; validate ranking-sensitive logic against real Tavily before shipping.
topic=financebehaves likegeneral. There's no specialized finance index to draw from.search_depth: fast/ultra-fastbehave likebasic. They exist so requests don't fail.auto_parametersis a simple heuristic, not Tavily's learned tuning. It's echoed back in the response the same way.- Scores are comparable within a response, not across services. Don't hard-code thresholds tuned on Tavily scores.
usage.creditsreports what Tavily would charge (approximately, for crawl), so client-side accounting keeps working — no actual metering happens.- News dates depend on what the upstream engine exposes; undated results are not filtered out by
start_date/end_date.
Documentation
| docs/api-reference.md | every endpoint, parameter, response shape, and error code |
| docs/configuration.md | all TRAWL_* variables + Ollama/LM Studio/caching recipes |
| docs/integrations.md | wiring tavily-python, @tavily/core, LangChain, raw HTTP |
| docs/compatibility.md | exact parity vs. approximations vs. intentional differences |
| docs/providers.md | how search backends work / how to add one |
| docs/deployment.md | compose, systemd, hardening beyond localhost |
| examples/ | runnable snippets for every SDK |
Interactive OpenAPI docs are at http://localhost:7300/docs while serving.
Development & contributing
uv venv && uv pip install -e ".[dev]"
.venv/bin/pytest # offline test suite (mocked providers & pages)
.venv/bin/ruff check src tests
trawl serve --reload
See CONTRIBUTING.md for the parity-first ground rules and project layout, and docs/providers.md for the most-wanted contribution: new search providers (Brave, Kagi, Google CSE, ...). Security reports: SECURITY.md — please read its threat-model section before exposing Trawl beyond localhost. Release history: CHANGELOG.md.
License
MIT. Not affiliated with or endorsed by Tavily — Trawl exists so you can develop against their API shape without spending credits, and hopefully become a happier paying customer in production.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file trawl_search-0.1.0.tar.gz.
File metadata
- Download URL: trawl_search-0.1.0.tar.gz
- Upload date:
- Size: 50.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9134ae235eea668ab94007aca685b6468dac638daeecc7f1e39f5b8683433d1d
|
|
| MD5 |
83d5201130a992ef1885c71c51e38e1a
|
|
| BLAKE2b-256 |
771481fe497953ab584a814c50d26851442072aa9d3c5c3fafa1ffd813910bdf
|
Provenance
The following attestation bundles were made for trawl_search-0.1.0.tar.gz:
Publisher:
release.yml on adityaparab/trawl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
trawl_search-0.1.0.tar.gz -
Subject digest:
9134ae235eea668ab94007aca685b6468dac638daeecc7f1e39f5b8683433d1d - Sigstore transparency entry: 2145751517
- Sigstore integration time:
-
Permalink:
adityaparab/trawl@95e363ada7dff5fe92c115cd966a369e140cac80 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/adityaparab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@95e363ada7dff5fe92c115cd966a369e140cac80 -
Trigger Event:
release
-
Statement type:
File details
Details for the file trawl_search-0.1.0-py3-none-any.whl.
File metadata
- Download URL: trawl_search-0.1.0-py3-none-any.whl
- Upload date:
- Size: 37.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d0120417b204bfffaea3add8ff6dfbed2d80c824e240ce88f8bdaadd353c42a
|
|
| MD5 |
b85fd37b316fafffd437739e8a5cb085
|
|
| BLAKE2b-256 |
abc7fe538e9bb83ff6dfd715406b61656d3a2a54f1fbb1cd8d4903543e2405e9
|
Provenance
The following attestation bundles were made for trawl_search-0.1.0-py3-none-any.whl:
Publisher:
release.yml on adityaparab/trawl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
trawl_search-0.1.0-py3-none-any.whl -
Subject digest:
9d0120417b204bfffaea3add8ff6dfbed2d80c824e240ce88f8bdaadd353c42a - Sigstore transparency entry: 2145751540
- Sigstore integration time:
-
Permalink:
adityaparab/trawl@95e363ada7dff5fe92c115cd966a369e140cac80 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/adityaparab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@95e363ada7dff5fe92c115cd966a369e140cac80 -
Trigger Event:
release
-
Statement type: