tornad

Search · Fetch · Render · Map · Crawl

The web, read back as text your agent can use.

One HTTP service in front of search, page extraction and a real browser. Ask for a query and get ranked results with the pages already read into them — not a list of links you then have to go fetch one by one.

request
→
response 

Why not self-host

You can run SearXNG yourself in an afternoon. These are the four things that break in month two.

The page is empty

Half the web renders client-side. A static fetch returns the shell, and your extractor happily hands your agent nothing at all.

A browser is a function call away

The datacenter IP is refused

Bot management refuses a cloud address whatever headers the request carries. The exit IP decides whether the page can be read — and a residential IP alone isn't enough, because Go's TLS handshake matches no browser.

Residential exit and a real fingerprint, on one path

An engine goes down

An upstream gets rate-limited and your general category quietly returns less, with nothing in the response to say so.

Brave backs the general category, running its own index

One slow category holds the answer

Academic engines take seconds. Concatenating their scores with the web's buries them; waiting on them hangs the call.

Reciprocal rank, a deadline, and partial: true

The numbers you'll hit

All of them are ceilings, published up front — so nothing surprises you in production.

Search deadline
4000ms

Default. Ask for less, never more than the deployment's own.

Render timeout
20s hard

8s by default. Clamped regardless of what is asked.

Pages per crawl
100

Read behind the request, twenty per page of results.

URLs per map
1000

Default 100. Synchronous, deadline capped at 50s.

Inline page reads
10

Per search. 4000 runes each unless you say otherwise.

Page cache
15min

Holds full text, so a later, larger read still hits.

Who may call

A service credential, never a browser one.

Present your application key as a bearer token, or a token from your identity provider carrying the tornad:search scope.

Create one key per application from your account, so it gets revoked without touching another's — and every log line can name who called.

Never ship this key to a browser. Every route reaches a URL of the caller's choosing and /render executes its JavaScript. A credential a browser can hold is one an attacker can spend scanning whatever your deployment can reach.
# the whole integration
curl https://tornad.dev/search \
  -H "Authorization: Bearer $TORNAD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"q":"gramsci","content":3}'

# or, inside the cluster, none at all
TORNAD_UNGUARDED=true

The contract is served, not copied.

GET /openapi.json returns this API's OpenAPI 3 description, generated from the handlers themselves. CI regenerates it and fails on a difference — so a handler cannot change without the contract following it.