# Server tools

Server tools are tools that RouterPlus runs for your model. Add them to `tools` on
`POST /v1/chat/completions`. The model calls them like any function tool. RouterPlus runs the
call, gives the result to the model and calls the model again. You get the final answer.

| Tool | What the model gets |
|---|---|
| `routerplus:advisor` | Advice from a second model that sees the conversation. |
| `routerplus:subagent` | The answer of a helper model to a task that the model writes. |
| `routerplus:web_fetch` | The text of a web page. |

## Request

```json
{
  "model": "moonshotai/kimi-k3",
  "messages": [{ "role": "user", "content": "Compare the two pricing pages and recommend one plan." }],
  "tools": [
    { "type": "routerplus:advisor", "advisor": { "model": "claude-opus-5-5", "max_calls": 2 } },
    { "type": "routerplus:subagent", "subagent": { "models": ["gpt-6-luna", "claude-sonnet-5-5"], "max_calls": 4 } },
    { "type": "routerplus:web_fetch", "web_fetch": { "allowed_domains": ["example.com"], "max_bytes": 300000 } },
    { "type": "function", "function": { "name": "save_report", "parameters": { "type": "object" } } }
  ]
}
```

| Field | Rule |
|---|---|
| `advisor.model` | The advisor model. |
| `advisor.max_calls` | 1 to 5. The default is 3. |
| `subagent.models` | 1 to 5 helper models. The model can pick one. The default is the first. |
| `subagent.max_calls` | 1 to 8. The default is 4. |
| `web_fetch.allowed_domains` | Optional. Fetch only these domains and their subdomains. |
| `web_fetch.max_bytes` | Up to 2000000. The default is 500000. RouterPlus cuts a longer page at this size. |
| `web_fetch.max_calls` | 1 to 10. The default is 5. You can send `web_fetch` without its object. |

List each tool type once. When a server tool is in the request, your own function tools cannot
use its name (`advisor`, `subagent` or `web_fetch`).

## What the model sees

RouterPlus gives the model one function tool for each server tool:

- `advisor(question)`: the advisor gets the conversation so far as text, plus the question. It
  answers with advice for the model.
- `subagent(task, model?)`: the helper gets the task as its only message. The model gets the
  helper's final answer.
- `web_fetch(url)`: RouterPlus fetches the URL and gives the model the page as plain text.

When a tool has used its `max_calls`, the model gets a message that says so and continues
without that tool.

## How the loop ends

- The model answers without a tool call. You get that answer.
- The model calls one of your own function tools. You get that answer with your tool calls.
  RouterPlus removes server tool calls from the same answer. Run your tools and send the next
  request as usual.
- After 12 model calls, you get the last answer without its tool calls, and
  `server_tools.stopped` is `"round_limit"`.

Your `tool_choice` applies to the first model call only.

## Response

The response is an OpenAI chat completion. `model` is your model. `usage` is the sum over every
model call: your model, the advisor and the helpers. `usage.cost` is present when the calls
report a cost.

```json
{
  "model": "moonshotai/kimi-k3",
  "choices": [{ "index": 0, "message": { "role": "assistant", "content": "..." }, "finish_reason": "stop" }],
  "usage": { "prompt_tokens": 5210, "completion_tokens": 940, "total_tokens": 6150, "cost": 0.0182 },
  "server_tools": {
    "rounds": 3,
    "stopped": "answer",
    "calls": [
      { "type": "web_fetch", "url": "https://example.com/pricing", "status": "ok", "bytes": 48211 },
      { "type": "subagent", "model": "gpt-6-luna", "status": "ok", "usage": { "prompt_tokens": 900, "completion_tokens": 300, "total_tokens": 1200 } },
      { "type": "advisor", "model": "claude-opus-5-5", "status": "ok", "usage": { "prompt_tokens": 2100, "completion_tokens": 120, "total_tokens": 2220 } }
    ]
  }
}
```

`server_tools.stopped` is `"answer"`, `"buyer_tools"` or `"round_limit"`. Each entry in
`server_tools.calls` has a `status`:

| Status | Meaning |
|---|---|
| `ok` | The tool ran. |
| `error` | The tool failed. `error` says why. The model got the error as the tool result. |
| `refused` | `web_fetch` did not fetch the URL. `error` says why. |
| `limit` | The tool had used its `max_calls`. |

A `web_fetch` entry has `truncated: true` when RouterPlus cut the page at `max_bytes`.

`stream: true` streams the final answer only. The last chunk before `[DONE]` carries `usage`
and `server_tools`.

## Web fetch rules

- RouterPlus fetches only `http` and `https` URLs. It refuses URLs with a user name or password.
- RouterPlus resolves the host name first. It refuses the URL when any address is private,
  loopback, link-local (cloud metadata addresses included), shared, multicast or reserved. This
  applies to IPv4, IPv6 and IPv4 addresses inside IPv6.
- The connection goes to the address RouterPlus checked.
- RouterPlus follows up to 3 redirects and checks each with the same rules, `allowed_domains`
  included.
- HTML becomes plain text. Other text types come back as they are. Images and other binary
  types are refused.
- A fetch stops after 20 seconds.

## Billing

RouterPlus bills each model call as a direct call to its model. Your key's limits and
[guardrails](guardrails.md) apply to each call. A request with server tools costs the sum of
those calls. `web_fetch` has no charge of its own.

## Errors

| Status | Cause |
|---|---|
| 400 `invalid_request` | A server tool is invalid. The message names the field. |
| 400 | `n` is above 1. Server tools answer with one choice. |
| 403 `guardrail_blocked` | A guardrail blocks server tools (checked before any model call), or does not allow the model, advisor model or helper model. |

## Limits

- Server tools do not work with `routerplus/fusion`, `routerplus/auto` or a `models` list.
- When a call of your model fails, you get that error unchanged.
