Search the live web
Searches the live web and answers with ranked results.
POST /v1/websearch
| Address | https://api.hanzo.ai/v1/websearch |
| Method | POST |
| Operation | search_web |
| Auth | Authorization: Bearer $HANZO_API_KEY |
Searches the live web and answers with ranked results.
This is the fleet's path to what is happening RIGHT NOW — today's weather, an
outage, a release that postdates any model's training. q is the query and
language narrows it to a locale. The answer is {query, number_of_results, results:[{url, title, content, engine}]}, where content is the ENGINE's
snippet and not the page: read a page with POST /v1/crawl.
It is served in-process by a Go meta-search over keyless public engines — never a third-party search API and never a search key. The enabled engines run concurrently and their hits are merged, deduplicated by normalised URL (host and path, trailing slash and fragment dropped, query kept, so distinct queries stay distinct results) and capped at 30. Ranking is deterministic rather than scored: the first configured engine's hits lead.
It fails SOFT on the engines. One that errors, times out or is served a
bot-challenge page contributes zero results and never fails the call, so an
empty results is a real answer — nothing was found — and not an outage. The
array is always present, never null.
A VALIDATED PRINCIPAL IS REQUIRED, and there is no tenant beyond that: the results are public web pages, identical for every caller, so nothing here is scoped and nothing here can leak across orgs. A typed op is also an MCP tool and a CLI command, and tools/call invokes it with no route and therefore no middleware — so the gate is in the handler, where every door reaches it, rather than in a middleware only one door passes through.
Request
2 fields, body application/json (required).
| Field | In | Type | Required | Description |
|---|---|---|---|---|
language | body | string | — | Language narrows the engines to a locale, BCP-47-ish ("en", "ja", "de"). |
q | body | string | — | Q is the query. |
Response
| Status | Body | Meaning |
|---|---|---|
200 | webSearchResults | ok |
200 body — 11 fields.
| Field | In | Type | Always | Description |
|---|---|---|---|---|
engines | body | webEngine[] | — | Engines is one entry per engine asked, in the order they were asked. |
engines[].name | body | string | — | Name is the engine, matching the engine stamped on each result. |
engines[].outcome | body | string | — | Outcome is "answered", "blind" or "failed" — see outcome.go. |
engines[].results | body | integer | — | Results is how many hits this engine contributed, before the merge deduplicated them against the others. |
number_of_results | body | integer | — | NumberOfResults is len(results) — what this answer carries, never an estimate of what the web holds. |
query | body | string | — | Query is the query that ran, echoed back. |
results | body | webResult[] | — | Results are the merged hits, deduplicated by normalised URL and capped at 30. |
results[].content | body | string | — | Content is the ENGINE's snippet — the few lines shown under the title, not the page's text. |
results[].engine | body | string | — | Engine names the backend that found this hit, so one engine's view of a query can be told from another's. |
results[].title | body | string | — | Title is the page's title. |
results[].url | body | string | — | URL is the page's address, as the engine reported it. |
Failure carries the platform error shape — see Errors.
Examples
hanzo websearch createimport { Configuration, WebsearchApi } from 'hanzoai';
const api = new WebsearchApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.searchWeb({ language: "<language>", q: "<q>" });from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import WebsearchApi
client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = WebsearchApi(client).search_web(language="<language>", q="<q>")cfg := cloud.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := cloud.NewAPIClient(cfg)
resp, _, err := client.WebsearchAPI.SearchWeb(context.Background()).Execute()
if err != nil {
return err
}use hanzo_cloud::apis::{configuration::Configuration, websearch_api};
let mut cfg = Configuration::new();
cfg.bearer_access_token = std::env::var("HANZO_API_KEY").ok();
let result = websearch_api::search_web(&cfg, Default::default()).await?;import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.WebsearchApi;
ApiClient client = new ApiClient();
client.setRequestInterceptor(b -> b.header("Authorization", "Bearer " + System.getenv("HANZO_API_KEY")));
var result = new WebsearchApi(client).searchWeb();curl -X POST https://api.hanzo.ai/v1/websearch \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"language": "<language>",
"q": "<q>"
}'Tool websearch, op search_web — POST the JSON-RPC envelope to https://api.hanzo.ai/v1/mcp.
curl -X POST https://api.hanzo.ai/v1/mcp \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "websearch",
"arguments": {
"op": "search_web",
"input": {
"language": "<language>",
"q": "<q>"
}
}
}
}'How is this guide?