Turn any web page into clean Markdown.
A self-hosted scraper with a Firecrawl-compatible API, in one Go binary and one container. It renders JavaScript with Lightpanda and strips ads, trackers and cookie banners. No Redis, no Playwright, no Chrome.
Try it on any page
# One platform to build and run your business operations with agents. Open source. Connected records, decisions, and actions across operations. Bring your existing tools and replace them at your own pace. [Sign up for Cloud](https://app.tulipfarm.ai/register?utm_source=tulipfarm.ai&utm_medium=website&utm_campaign=signup&utm_content=home_hero) [Try it yourself](https://tulipfarm.ai/try/) [view source](https://github.com/TulipFarm/tulipfarm)
3.0× the pages in flight of Firecrawl4.0× the pages per secondSame 8 vCPU machine; see the benchmarks
Benchmarks
Each engine self-hosted alone on the same ARM machine, scraping 1,000 live URLs at rising load.
Pages scraped at once
2 vCPU · 4 GB memory
- Huntsman45 pages at once4.58 pages/s · 90.9% scraped
- Firecrawl8 pages at once0.91 pages/s · 92.5% scraped
- Crawl4AICrashed or ran out of memory even at 2 pages at once
5.6× the pages in flight of Firecrawl, and 5.0× the pages per second.
4 vCPU · 8 GB memory
- Huntsman75 pages at once7.74 pages/s · 91.8% scraped
- Firecrawl15 pages at once1.58 pages/s · 92.2% scraped
- Crawl4AI15 pages at once1.35 pages/s · 80.6% scraped
5.0× the pages in flight of Firecrawl, and 4.9× the pages per second.
8 vCPU · 16 GB memory
- Huntsman129 pages at once11.54 pages/s · 90.8% scraped
- Firecrawl43 pages at once2.90 pages/s · 92.3% scraped
- Crawl4AI22 pages at once1.62 pages/s · 81.8% scraped
3.0× the pages in flight of Firecrawl, and 4.0× the pages per second.
How we measured
1,000 random URLs from the test split of huntsman-scrape-dataset-v1, scraped live on ARM cloud machines with each engine self-hosted alone. A binary search found each engine’s exact limit: a level passes if the engine doesn’t crash, success stays within 10 points of its best level, and the median page takes under 3× as long as at 8 pages. Firecrawl ran in scrape-only mode on the small machine. One run per level; methods and raw results.
Why Huntsman
Everything an agent or search pipeline needs from a page, and nothing it doesn’t.
The page
In the browser
No ads, trackers or cookie banners
150,000+ blocklist rules stop them before they load.
- blocked
- www.googletagmanager.com/gtm.js
- rule
- ||googletagmanager.com^
- list
- easyprivacy.txt:3716
You get
Just the content
Navigation, footers and boilerplate are stripped out.
# Go 1.22 is released! Eli Bendersky, on behalf of the Go team 6 February 2024 Today the Go team is thrilled to release Go 1.22, which you can get by visiting the [download page](https://go.dev/dl/).
Swap in for Firecrawl
Same
/v1API. Change one base URL.- https://api.firecrawl.dev/v1/scrape + http://localhost:8080/v1/scrape
JavaScript sites and PDFs
Single-page apps render. PDFs are read directly.
url engine time react.dev/learn lightpanda 2.4s arxiv.org/pdf/ 1706.03762v7 pdf 0.95s 1,000 URLs in one call
Batch a list, or crawl a whole site.
POST /v1/batch 100 urls
99 completed · 1 failed · 1m 58s
Safe to self-host
Blocks internal addresses and respects
robots.txt.- url
- http://169.254.169.254/latest/meta-data.pdf
- 502
- dial tcp 169.254.169.254:80: blocked private network address
Get started
From an empty machine to your first page of Markdown in about a minute.
Run the container
The image is published for
linux/amd64andlinux/arm64. The volume keeps the job queue and result cache across restarts.Run Huntsman with Docker docker run -d --name huntsman -p 127.0.0.1:8080:8080 \ -v huntsman-data:/data ghcr.io/tulipfarm/huntsman:latestScrape a page
Send a URL to
/v1/scrapeand get back the page’s main content as Markdown, plus its links.Scrape example.com curl -s localhost:8080/v1/scrape \ -H 'Content-Type: application/json' \ -d '{"url": "https://example.com"}'Example response, shortened { "success": true, "data": { "markdown": "This domain is for use in documentation examples …", "links": ["https://iana.org/help/example-domains"], "metadata": { "title": "Example Domain", "statusCode": 200, "engine": "lightpanda" } } }Size it for your machine
HUNTSMAN_WORKERSsets how many pages are scraped at once. By default Huntsman sizes it to the machine: 16 per CPU core, capped at one per 96 MB of memory, so 2 vCPU and 4 GB gets 32. Set it yourself to go further; these are the highest values that held up in the benchmark above. Memory grows over runs of many hours, so leave headroom for long-running servers.Highest tested HUNTSMAN_WORKERS per machine size Machine Tested max Small 2 vCPU · 4 GB 45 Medium 4 vCPU · 8 GB 75 Large 8 vCPU · 16 GB 129 Run Huntsman with 64 workers docker run -d --name huntsman -p 127.0.0.1:8080:8080 \ -v huntsman-data:/data \ -e HUNTSMAN_WORKERS=64 \ ghcr.io/tulipfarm/huntsman:latestLock it down for production
Pin a version and set
HUNTSMAN_TOKEN. Every endpoint except/healththen needs the token as a bearer header. Private, loopback and link-local addresses are blocked by default.Run a pinned version with a token docker run -d --name huntsman -p 8080:8080 \ -v huntsman-data:/data \ -e HUNTSMAN_TOKEN="$(openssl rand -hex 32)" \ ghcr.io/tulipfarm/huntsman:0.1.0Scrape with the token curl -s https://scraper.example.com/v1/scrape \ -H "Authorization: Bearer $HUNTSMAN_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"url": "https://example.com", "onlyMainContent": true}'
Reference
API
Requests and responses follow Firecrawl’s /v1 shapes, so most clients only need a new base URL. Unknown fields are rejected, so typos fail loudly.
| Endpoint | What it does |
|---|---|
POST /v1/scrape | Scrape one URL and return Markdown and links |
POST /v1/batch | Queue 1–1,000 URLs with shared scrape options |
GET /v1/jobs/{id} | Status and result of a queued job |
POST /v1/crawl | Crawl a site up to a page limit and depth |
GET /v1/crawl/{id} | Crawl progress and pages, paged with ?offset= |
GET /health | Liveness and storage check |
Coming from Firecrawl? Keep calling /v1/scrape. Huntsman deliberately does less: it returns Markdown and links only, with no screenshots, HTML, JSON extraction, actions or search.
Crawl a site
curl -s localhost:8080/v1/crawl \
-H 'Content-Type: application/json' \
-d '{"url": "https://example.com", "limit": 50, "maxDepth": 2}'
# → {"success": true, "id": "…"}
curl -s 'localhost:8080/v1/crawl/<id>?offset=0'Configuration
Everything is an environment variable. The most used ones are below; the full list is in the README.
| Variable | Default | Meaning |
|---|---|---|
HUNTSMAN_WORKERS | 16 per CPU | Pages scraped at once, capped at one per 96 MB of memory. Container limits count (see step 3 above). |
HUNTSMAN_PER_HOST | 2 | Most pages scraped at once from a single site, so one site isn’t flooded. Max 64. |
HUNTSMAN_TOKEN | empty | Bearer token; empty turns auth off |
HUNTSMAN_TOKENS | empty | Named keys, name:token,... One per client, so you can revoke one without the others |
HUNTSMAN_CACHE_TTL | 1h | Default result cache age, up to 168h |
HUNTSMAN_OBEY_ROBOTS | true | Respect robots.txt |
HUNTSMAN_QUEUE_CAPACITY | 10000 | Queued jobs before 429 |
Run it on your own machine.
One Go binary and one container. No Redis, no Playwright, no Chrome.
docker run -d --name huntsman -p 127.0.0.1:8080:8080 \
-v huntsman-data:/data ghcr.io/tulipfarm/huntsman:latest