> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aireserve.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Server tools

> Let any model search the web, read a page, or check the clock — tools the gateway executes for the model inside a bounded loop, on your AI Reserve key and wallet. No tool loop to write, no provider lock-in.

Server tools are tools **the gateway runs**. Declare them on a chat completion next to your own
function tools and the model can call them like any other tool — except that when it does, the
gateway executes the call, appends the result, and calls the model again until it answers. You
get the final answer in one request; your code never sees a server tool call.

Three tools, declared by a reserved `type`:

| Tool | What the model gets | Billed |
| - | - | - |
| `web_search` | Ranked live-web results (title, URL, snippet, publish date) plus any matching knowledge cards, from [Tako](https://docs.aireserve.com/capabilities/live-search.md). The model writes the query. | One `tako-search` row per search, at the [live-search rate](https://docs.aireserve.com/capabilities/live-search.md#billing). |
| `web_fetch` | The readable text of a public page the model names — headings, paragraphs, links; scripts, styles and navigation removed. | One `web-fetch` row per fetch. **Currently \$0.000 per fetch** — a placeholder, see [Billing](#billing). |
| `datetime` | The current date and time in an IANA time zone the model asks for (UTC by default). | Free. No row. |

They work with **every model** the gateway serves — the tools are presented to the model as
ordinary function tools, so any model that can call functions can use them, on any provider and
through fallback chains alike.

## Quickstart

```bash theme={"dark"}
curl -s https://api.aireserve.com/v1/chat/completions \
  -H "Authorization: Bearer $AIRESERVE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v4-flash",
    "messages": [{ "role": "user", "content": "Summarize what the latest Fed statement says about rates." }],
    "tools": [{ "type": "web_search" }, { "type": "web_fetch" }, { "type": "datetime" }]
  }'
```

The model typically calls `datetime` to anchor "latest", `web_search` to find the statement,
`web_fetch` to read it, and answers — three tool rounds, one HTTP request. The response is a
normal chat completion; the header `x-aireserve-server-tools: web_search=1;web_fetch=1;datetime=1`
says what ran.

```python Python theme={"dark"}
from openai import OpenAI

client = OpenAI(base_url="https://api.aireserve.com/v1", api_key=AIRESERVE_API_KEY)

response = client.chat.completions.with_raw_response.create(
    model="deepseek-v4-flash",
    messages=[{"role": "user", "content": "What time is it in Tokyo, and what is on the front page of the FT right now?"}],
    tools=[{"type": "web_search"}, {"type": "web_fetch"}, {"type": "datetime"}],
)
print(response.headers["x-aireserve-server-tools"])
print(response.parse().choices[0].message.content)
```

```typescript TypeScript theme={"dark"}
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api.aireserve.com/v1", apiKey: process.env.AIRESERVE_API_KEY });

const { data, response } = await client.chat.completions
  .create({
    model: "deepseek-v4-flash",
    messages: [{ role: "user", content: "What time is it in Tokyo, and what is on the front page of the FT right now?" }],
    tools: [{ type: "web_search" }, { type: "web_fetch" }, { type: "datetime" }],
  })
  .withResponse();
console.log(response.headers.get("x-aireserve-server-tools"));
console.log(data.choices[0].message.content);
```

> [!NOTE]
>
> The OpenAI SDKs type `tools` as function tools; pass the server-tool entries through as shown
> (the Python SDK accepts them as-is, in TypeScript cast the array if your compiler objects). The
> gateway strips them before the request reaches any provider.

## Declaring the tools

```json theme={"dark"}
{
  "tools": [
    { "type": "web_search", "web_search": { "effort": "fast", "max_results": 5 } },
    { "type": "web_fetch" },
    { "type": "datetime" },
    { "type": "function", "function": { "name": "get_portfolio", "parameters": { "type": "object" } } }
  ],
  "server_tools": { "max_rounds": 3 }
}
```

| Field | Meaning |
| - | - |
| `tools[].type: "web_search"` | Model-chosen search. Optional `web_search: { "effort", "max_results" }` sets the defaults for every search in the request — `effort` `instant` / `fast` / `deep` (`deep` bills as `tako-search-deep`), `max_results` 1–10 (default 5). The model may lower `max_results` per call. `web_search_preview` is accepted as a synonym. |
| `tools[].type: "web_fetch"` | Page reading. No options. |
| `tools[].type: "datetime"` | Clock. No options. |
| `server_tools.max_rounds` | How many rounds of server-tool calls the model may make before it must answer: 1–5, default 3. See [Loop bounds](#loop-bounds). |

Your own function tools stay in `tools` and reach the model unchanged. Function names starting
with `aireserve__` are reserved for the gateway; a request that declares one while server tools
are on is rejected with `400`.

### Compatibility with grounded search

`tools: [{ "type": "web_search" }]` **on its own** has meant
[grounded search](https://docs.aireserve.com/capabilities/live-search.md#grounded-search-on-chat-completions) since it
shipped: one search on your latest user message, run by the gateway *before* the model call, with
the sources injected as context. That behaviour is unchanged. The rule is:

| You send | What runs |
| - | - |
| `web_search` (or `web_search_preview`) alone, no other server tool | **Grounded search** — pre-dispatch, one search, `x-aireserve-web-search` header. |
| `web_fetch` or `datetime` declared (with or without `web_search`) | **Server-tool loop** — the model decides what to call and when. |
| `{ "type": "web_search", "mode": "tool" }` | **Server-tool loop** with search only — the model chooses the query. |
| Any server tool plus a `server_tools` block | **Server-tool loop.** |

Inside the loop there is no pre-dispatch search and no injected context: if the model wants
sources it calls `web_search` itself. The `:online` suffix, `plugins` and `web_search_options`
forms always mean grounded search and are independent of the loop (they can be combined, in
which case the grounded context is injected first and the loop runs as usual).

## How the loop runs

1. The gateway removes the server-tool entries from `tools` and offers the model the equivalent
   function tools (`aireserve__web_search`, `aireserve__web_fetch`, `aireserve__datetime`) beside
   your own.
2. If the model's turn consists **only of server-tool calls**, the gateway executes them — in
   parallel, each under its own timeout — appends the assistant turn and one `tool` message per
   call to the conversation, and calls the model again. That is one **round**.
3. If the model's turn contains **any call to one of your own tools**, the turn is yours: the
   gateway returns it exactly as the model produced it and executes nothing — not even the
   server-tool calls in the same turn. Run your tools, append the results and send the next
   request; the server tools are offered again on it.
4. A turn with no tool calls is the answer and is returned as usual.

Errors inside a tool — a page that answers `404`, a URL the gateway refuses, a search that cannot
be billed, a timeout — are returned **to the model** as a tool result of the form
`{"error": "web_fetch failed: the page answered HTTP 404"}`. The model reads it and decides what
to do; the request itself never fails because a tool did.

### Loop bounds

A request makes at most `max_rounds` rounds of server-tool calls (default 3, maximum 5). After
the last allowed round the model is called once more **with the server tools withheld**, so it
has to answer from what it gathered. Within a round at most 8 server-tool calls are executed; the
rest receive a tool error asking for fewer calls per turn. Per-call timeouts: 12 s for a search,
10 s for a fetch, 1 s for the clock. The whole request still runs under the route's normal
deadline.

### Streaming

With `stream: true` the tool rounds complete **before the first byte** of the response; only the
answer round streams, as an ordinary SSE stream ending in `[DONE]`. Time to first token grows by
the rounds' duration. The `x-aireserve-server-tools` header is on the stream's head with the
final counts. A turn made of your own tool calls streams through untouched, tool-call deltas
included.

## What the model receives

**`web_search`** — arguments `{ "query": string, "max_results"?: number }`:

```json theme={"dark"}
{
  "query": "fed statement november 2026 rates",
  "results": [
    { "title": "Federal Reserve issues FOMC statement", "url": "https://www.federalreserve.gov/newsevents/pressreleases/monetary20261105a.htm", "snippet": "…", "published": "2026-11-05", "source": "Federal Reserve" }
  ],
  "knowledge_cards": [{ "title": "Federal Funds Rate", "description": "…", "url": "https://tako.com/card/…" }],
  "retrieved_at": "2026-11-05T14:03:11.000Z"
}
```

**`web_fetch`** — arguments `{ "url": string }`:

```json theme={"dark"}
{
  "url": "https://www.federalreserve.gov/newsevents/pressreleases/monetary20261105a.htm",
  "final_url": "https://www.federalreserve.gov/newsevents/pressreleases/monetary20261105a.htm",
  "status": 200,
  "content_type": "text/html",
  "title": "Federal Reserve Board - Federal Reserve issues FOMC statement",
  "text": "# Federal Reserve issues FOMC statement\n\nRecent indicators suggest …",
  "truncated": false,
  "bytes": 48213,
  "redirects": 0
}
```

Headings come back as markdown headings, list items as bullets, links as `text (absolute URL)`
so the model can fetch them next. Text is capped at 20,000 characters (`truncated: true` when
cut). Plain-text, JSON, XML and markdown bodies are returned as-is under the same cap.

**`datetime`** — arguments `{ "timezone"?: string }`:

```json theme={"dark"}
{ "timezone": "Asia/Tokyo", "iso": "2026-11-05T23:03:11.000+09:00", "utc_iso": "2026-11-05T14:03:11.000Z", "unix": 1793887391, "weekday": "Thursday", "formatted": "Thursday 5 November 2026 at 23:03:11 GMT+9" }
```

An unknown zone is a tool error naming the problem, never a silent UTC.

## Fetch guardrails

The model names the URL, so `web_fetch` treats every URL as untrusted:

* **`http` and `https` only.** No credentials in the URL (`https://user:pass@…` is refused).
* **Public addresses only.** The host is resolved and every address it maps to must be public:
  private (`10/8`, `172.16/12`, `192.168/16`), loopback, link-local (including the cloud
  metadata address `169.254.169.254`), carrier-grade NAT, multicast and the IPv6 equivalents
  (`::1`, `fc00::/7`, `fe80::/10`, IPv4-mapped and NAT64 forms) are refused, as are `localhost`
  and `.internal` / `.local` names. The check is repeated for **every redirect target**.
* **At most 3 redirects**, a **10-second** budget for the whole fetch, and a **1 MB** byte
  budget — the read stops at the budget and the result is marked truncated.
* **No cookies, no stored credentials**, a fixed `User-Agent`
  (`AIReserveBot/1.0 (+https://docs.aireserve.com/capabilities/server-tools)`).
* Only text-like bodies (HTML, plain text, JSON, XML) are read; PDFs, images and binaries are
  refused before download.

**Fetched content is never stored.** It goes to the model and nowhere else: the usage row and the
gateway's logs carry the URL's host name and the byte count, never the path, the query string or
any part of the page. Searches follow the live-search [data-handling](https://docs.aireserve.com/capabilities/live-search.md#data-handling)
terms, including the data-retention consent gate on `tako-search`.

## Billing

Every model call in the loop is a normal chat-completion row, billed at the model's rate with the
tool results counted in its prompt tokens:

| Row | Request id | What it is |
| - | - | - |
| Chat completion | `<request id>` | The **final** model call — the one whose answer you received. |
| Chat completion | `<request id>:round-1`, `:round-2`, … | Each earlier model call whose turn was server-tool calls only (a *tool round*). Same model, same rate; the row's metadata carries `serverToolRound: { parentRequestId, round, calls }`. |
| `tako-search` / `tako-search-deep` | `<request id>:web-search-1`, `-2`, … | Each executed search, at the live-search per-search rate. A search that fails is never charged. |
| `web-fetch` | `<request id>:web-fetch-1`, `-2`, … | Each executed fetch, flat per fetch. A fetch that fails is never charged. |

`datetime` writes no row. The final chat row's usage metadata carries `serverTools` — what was
declared, rounds made, calls executed per tool, errors, whether the round cap was hit, and
whether the turn returned to you carried your own tool calls.

> [!WARNING]
>
> \*\*`web-fetch` is priced at $0.000 per fetch today.** That is a placeholder, not a promise:
>     the per-fetch price is a pending platform decision and will be announced on the   [pricing page](https://aireserve.com/pricing) before it takes effect. The row is written at $0
> now so your analytics already show fetch volume per key.

Spend caps and budgets apply to every row. If the wallet cannot cover a tool round, the loop stops
and the request answers `402` like any other request the wallet refuses; the model calls already
made are billed.

## Errors

| Status | Meaning |
| - | - |
| `400` | A function tool named `aireserve__…` was declared with server tools on, or the request was otherwise malformed. |
| `402` | Spend cap exceeded or insufficient wallet balance for a model call in the loop. |
| `4xx` / `5xx` from the model | Relayed as on any chat completion. A model-call failure on a later round surfaces the same way; the earlier rounds are billed. |

Tool failures are **not** HTTP errors: they reach the model as tool results (see
[How the loop runs](#how-the-loop-runs)). Check `x-aireserve-server-tools` and the `serverTools`
usage metadata when you need to know what ran.

> [!NOTE]
>
> **Responses API.** `/v1/responses` is a transparent passthrough on the gateway today and does
> not run the server-tool loop; declaring these tools there has no effect. Support on that surface
> follows.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.