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

# Web fetch

> Read a public web page and answer questions about its content.

Give Lume a public URL and ask it to summarize, compare, or extract information from the page. OverControl retrieves the page during the request and gives the content to the model.

**Lume 3.5**, **Lume 3.5 Max**, and **Lume Omni** support `web_fetch` on Chat Completions and Messages. Lume 2 does not support this server-side tool.

## Before you start

Create an API key in the [dashboard](https://app.overcontrolgroup.com/dashboard) with `chat:completions` for Chat Completions or `messages:create` for Messages. If your key has a model allowlist, include `lume-3.5`. Keep the key on your server and set it in the shell where you run the examples:

```bash theme={null}
export OVERCONTROL_API_KEY="your_api_key"
```

Send one authentication header per request: `Authorization: Bearer` or `x-api-key`. Supplying both with values returns HTTP 401.

For SDK examples, install the client you use. cURL examples do not need an SDK.

```bash Python: Chat Completions theme={null}
python -m pip install openai
```

```bash Python: Messages theme={null}
python -m pip install anthropic
```

Run Python examples from a `.py` file.

Messages accepts an optional `anthropic-version` header. If supplied, it must be one valid calendar date in `YYYY-MM-DD` form. It is a compatibility hint and does not change the supported features. The SDK supplies its version header automatically.

## Chat Completions

Call `POST /v1/chat/completions` with `chat:completions`. Declare the tool in `tools` and use the named choice shown below to require it. Omit `tool_choice` when the model should decide whether it needs the tool.

```bash cURL theme={null}
curl https://api.overcontrolgroup.com/v1/chat/completions \
  -H "Authorization: Bearer $OVERCONTROL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "lume-3.5",
  "max_tokens": 512,
  "messages": [
    {
      "role": "user",
      "content": "Read https://docs.python.org/3/tutorial/ and summarize what the tutorial covers, with sources."
    }
  ],
  "tools": [
    {
      "type": "web_fetch",
      "max_uses": 1,
      "allowed_domains": [
        "docs.python.org"
      ],
      "max_content_tokens": 2000,
      "citations": {
        "enabled": true
      }
    }
  ],
  "tool_choice": {
    "type": "web_fetch"
  }
}'
```

```python Python theme={null}
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OVERCONTROL_API_KEY"],
    base_url="https://api.overcontrolgroup.com/v1",
)

response = client.chat.completions.create(
    model="lume-3.5",
    max_tokens=512,
    messages=[
        {
            "role": "user",
            "content": "Read https://docs.python.org/3/tutorial/ and summarize what the tutorial covers, with sources."
        }
    ],
    tools=[
        {
            "type": "web_fetch",
            "max_uses": 1,
            "allowed_domains": [
                "docs.python.org"
            ],
            "max_content_tokens": 2000,
            "citations": {
                "enabled": True
            }
        }
    ],
    tool_choice={
        "type": "web_fetch"
    },
)

choice = response.choices[0]
if choice.finish_reason == "length":
    raise RuntimeError("The answer reached its output limit.")
answer = choice.message.model_dump(exclude_none=True)
print(answer.get("content", ""))
for annotation in answer.get("annotations", []):
    if annotation.get("type") == "url_citation":
        source = annotation["url_citation"]
        print(source["title"], source["url"])
print(response.usage.model_dump(exclude_none=True))
```

Read the answer from `choices[0].message.content`. Source references appear in `choices[0].message.annotations` with `type: "url_citation"`. Each `url_citation` has `url`, `title`, `start_index`, and `end_index`. Use those offsets to associate a source with the cited text, and display source links with the answer.

Chat returns the final answer and annotations; it does not return the native Messages server-tool result blocks. An absent citation is not evidence that a claim was verified. Check the answer and successful-operation counts when the task depends on retrieval.

## Messages

Call `POST /v1/messages` with `messages:create`. The named choice is `{"type":"tool","name":"web_fetch"}`. The declaration uses the same tool options as Chat.

```bash cURL theme={null}
curl https://api.overcontrolgroup.com/v1/messages \
  -H "x-api-key: $OVERCONTROL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "lume-3.5",
  "max_tokens": 512,
  "messages": [
    {
      "role": "user",
      "content": "Read https://docs.python.org/3/tutorial/ and summarize what the tutorial covers, with sources."
    }
  ],
  "tools": [
    {
      "type": "web_fetch",
      "max_uses": 1,
      "allowed_domains": [
        "docs.python.org"
      ],
      "max_content_tokens": 2000,
      "citations": {
        "enabled": true
      }
    }
  ],
  "tool_choice": {
    "type": "tool",
    "name": "web_fetch"
  }
}'
```

```python Python theme={null}
import os
from anthropic import Anthropic

client = Anthropic(
    api_key=os.environ["OVERCONTROL_API_KEY"],
    base_url="https://api.overcontrolgroup.com",
)

message = client.messages.create(
    model="lume-3.5",
    max_tokens=512,
    messages=[
        {
            "role": "user",
            "content": "Read https://docs.python.org/3/tutorial/ and summarize what the tutorial covers, with sources."
        }
    ],
    tools=[
        {
            "type": "web_fetch",
            "max_uses": 1,
            "allowed_domains": [
                "docs.python.org"
            ],
            "max_content_tokens": 2000,
            "citations": {
                "enabled": True
            }
        }
    ],
    tool_choice={
        "type": "tool",
        "name": "web_fetch"
    },
)

if message.stop_reason == "max_tokens":
    raise RuntimeError("The answer reached its output limit.")
for block in message.content:
    item = block.model_dump(exclude_none=True)
    if item["type"] == "text":
        print(item["text"])
        for citation in item.get("citations", []):
            print("Citation:", citation)
    elif item["type"] == "web_fetch_tool_result":
        result = item["content"]
        if isinstance(result, dict) and result.get("type", "").endswith("_error"):
            print("Tool error:", result["error_code"])
        else:
            print("Tool result:", result)
print(message.usage.model_dump(exclude_none=True))
```

Messages returns a `content` array with server tool calls, tool results, and answer text. Read all blocks; `content[0]` may be a tool call rather than text. HTTP 200 does not imply every tool operation succeeded.

A `server_tool_use` block identifies the fetch. Its paired `web_fetch_tool_result` contains a `web_fetch_result` with the URL and a document holding retrieved text, or a `web_fetch_tool_result_error`. With citations enabled, answer text can carry `char_location` citations referring to a returned document by `document_index` and character range. Match the citation to the document and expose its source URL.

## Choose an eligible URL

Include the URL explicitly in a user message, as in the examples. A URL from an accepted server-side web-search result is also eligible. A URL invented by the model, placed only in a system instruction, or copied into an arbitrary client function result does not by itself authorize a fetch.

Fetch targets must be public HTTP or HTTPS URLs. Private network addresses, localhost, URL credentials, and nonstandard ports are not supported. Domain filters narrow the eligible targets; they do not grant permission to retrieve a URL that lacks accepted provenance. A URL can still fail because the page is unavailable, inaccessible, or not supported by the fetch operation.

## Control fetched content

| Tool option | Behavior |
| - | - |
| `max_uses` | Positive integer, default 3; values above 16 are reduced to 16. Limits fetch operations for the request. |
| `allowed_domains` | Restrict targets to these domains and their subdomains. |
| `blocked_domains` | Exclude these domains and their subdomains. Use this instead of an allowlist. |
| `max_content_tokens` | Optional positive integer limiting how much retrieved text is made available to the model for each fetch. |
| `citations` | `{"enabled":true}` enables fetch citations. The default is disabled. |

Each domain list supports up to 100 entries. Fetch filters are hostnames without schemes, ports, or paths, such as `docs.python.org`. Do not combine nonempty allowlists and blocklists. Path restrictions belong to web search, not fetch.

Large pages can be shortened to fit `max_content_tokens`, the remaining context, or response limits. Messages marks partial fetched documents in their `context` field. Treat the result as an excerpt when it is partial; do not assume the model has read the whole page. A content limit bounds retrieved context, not the request's total output or cost.

## Continue a conversation

For Chat, include the earlier user and assistant turns with your next question. The returned annotations are source metadata, not a reusable server-tool transcript. To fetch a previously cited URL on a new Chat request, include that URL explicitly in user text.

For Messages, preserve the full returned assistant `content`, including server calls, results, citations, and opaque fields, before appending the next user turn. Keep the relevant tool declarations on the follow-up. The returned server-tool history is account-bound and has a seven-day replay lifetime. Do not fabricate or edit source blocks, and do not reuse another account's history. Expired or invalid history is rejected; start a fresh retrieval when needed.

## Streaming and output limits

Set `stream: true` to receive protocol-native events. This server-side workflow runs before emitting the result: the connection can send SSE comment keepalives while model and tool work completes, then emits the answer and source events. It does not provide live token-by-token or per-operation progress during that work.

Chat citations arrive in `choices[].delta.annotations`. To receive final usage, add `stream_options: {"include_usage":true}` and handle the usage-only chunk with `choices: []` before `[DONE]`. Messages sends server-tool/result blocks, text and citation deltas, and final usage in `message_delta`, followed by `message_stop`. Check in-stream errors as well as the initial HTTP status. [Streaming](https://docs.overcontrolgroup.com/text/streaming.md) provides complete SDK stream readers.

The supported models have a 400,000-token context window and up to 32,768 output tokens. Both APIs reduce the output budget to the model maximum and remaining context space. Messages requires a positive `max_tokens`; Chat defaults to the model maximum when no budget is supplied. The examples set 512 output tokens and one tool use. Tool output and intermediate model work also need context; high `max_uses` does not guarantee every permitted operation will run.

## Usage and pricing

The request consumes model tokens plus successful tool operations. Retrieved content and intermediate model turns contribute to token usage. Failed operations do not incur the successful-operation charge, but model tokens already used can still consume allowance. Declaring a tool does not itself incur an operation charge.

Read `usage.server_tool_use.web_fetch_requests` for successful operations and the normal token fields for model usage. Streaming clients must read final usage. [Pricing](https://docs.overcontrolgroup.com/pricing.md#server-side-tool-pricing) lists rates per 1,000 successful operations and a combined-cost example.

Before execution, the request reserves estimated usage; unused reserved usage is released afterward. Set an explicit output budget and a small `max_uses` to bound work. A request can require more available usage to start than its final cost. `max_uses` is an operation ceiling, not a complete monetary ceiling.

## Common failures

| Failure | What to do |
| - | - |
| HTTP 400 for the declaration | Check the model, tool type, positive limits, and domain-filter format. |
| HTTP 401 or 403 | Check the key, send one authentication header, and enable the inference endpoint's scope. |
| HTTP 404 on Messages | Check model ID and the key's model allowlist. |
| HTTP 429 with `Insufficient credits` | Review available usage or reduce the output/tool budget. |
| HTTP 502, `server_tool_workflow_failed` on Chat | The workflow could not complete; retry only when appropriate for your application. Messages uses its native error body. |
| Messages `max_uses_exceeded` | The tool reached its operation limit. Increase it only if the task needs more work. |
| Messages `invalid_tool_input` | The tool invocation was invalid. Adjust the request or try again. |
| Messages `too_many_requests` or `unavailable` | The web operation could not complete; it may be worth retrying later. |
| Messages `url_not_allowed` | Supply an explicit eligible URL and check domain restrictions. |
| Messages `unsupported_content_type` | Use a page the fetch operation can process. |

Messages tool errors appear inside the tool-result `content` object as an `error_code`, even on HTTP 200. Chat exposes the final answer, annotations, and usage rather than those native error blocks. On Chat, inspect the answer and counters instead of looking for a Messages result object. Keep `X-Request-Id` for support; Messages also returns `request-id`.


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