# Create websearch

URL: https://docs.hanzo.ai/docs/openapi/websearch/search-web

> Searches the live web and answers with ranked results.

`POST /v1/websearch`

> [Websearch →](https://docs.hanzo.ai/docs/openapi/websearch) · [All API references →](https://docs.hanzo.ai/docs/openapi) · [Get started →](https://docs.hanzo.ai/docs/guides)

|               |                                        |
| ------------- | -------------------------------------- |
| **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 `&#123;query, number_of_results,
results:[&#123;url, title, content, engine&#125;]&#125;`, 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.

Two refusals in the order they have to be asked, both in the PREAMBLE. A typed
op is also an MCP tool, a call-plane operation, a graph field and a CLI
command, and every one of those invokes it with no route and therefore no
middleware — so what admits a caller here is asked where every caller reaches
it rather than in a middleware only one of them passes through.

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.

THEN THE ANTI-FORGERY TOKEN, immediately before the money, because that is
what it is about. This search is the SAME bought meta-search the compat
endpoint runs — the engines cost, and account.Shared/meter.go bills the
caller's ledger for the answer — so a page the caller never visited must not
be able to spend for them by sending their browser here with a cookie they
already hold. Nothing leaks; the answer is unreadable cross-origin. What
moves is money.

It is account's control, the one every operation in this estate asks, and it
is a no-op the moment a caller PRESENTS a credential (Bearer, gateway, API
key) — which is every service and console caller here — so it costs a CLI, an
agent and an API client nothing. Only the ambient-cookie path is asked for the
echoed token. The raw /v1/websearch/search route asks the same control on its
group (see Mount), so the two addresses of one search are admitted alike.

## 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`     | websearch.webSearchResults | ok      |
| `default` | problem-details            | refused |

`200` body — 11 fields.

| Field               | In   | Type                   | Always | Description                                                                                               |
| ------------------- | ---- | ---------------------- | ------ | --------------------------------------------------------------------------------------------------------- |
| `engines`           | body | websearch.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 (int64)        | —      | Results is how many hits this engine contributed, before the merge deduplicated them against the others.  |
| `number_of_results` | body | integer (int64)        | —      | 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 | websearch.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](https://docs.hanzo.ai/docs/errors).

## Examples

**CLI**

```bash
hanzo websearch create
```

**SDK**

**.ts**

```ts
import { 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>" });
```

**.py**

```python
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>")
```

**.go**

```go
cfg := hanzoai.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := hanzoai.NewAPIClient(cfg)

resp, _, err := client.WebsearchAPI.SearchWeb(context.Background()).Execute()
if err != nil {
	return err
}
```

**.rs**

```rust
use hanzo_client::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?;
```

**.java**

```java
import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.WebsearchApi;

ApiClient client = new ApiClient();
client.setBearerToken(System.getenv("HANZO_API_KEY"));

var result = new WebsearchApi(client).searchWeb();
```

**HTTP**

```bash
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>"
     }'
```

**MCP**

Tool `websearch`, op `search_web` — POST the JSON-RPC envelope to `https://api.hanzo.ai/v1/mcp`.

```bash
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>"
           }
         }
       }
     }'
```

---

[Websearch API](https://docs.hanzo.ai/docs/openapi/websearch) · [All Hanzo APIs](https://docs.hanzo.ai/docs/openapi) · [Interactive reference](https://docs.hanzo.ai/docs/openapi/reference)
