Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Optional top-level fields. Stripped before the request is forwarded to the provi
| `provider` | object | **Deprecated** — prefer `X-0G-Provider-*` request headers. See [Routing](../routing) |
| `verify_tee` | boolean | Ask the Router to synchronously verify the provider's TEE signature — see [Verifiable Execution](./verifiable-execution) |

To attach PDF or text files to a message, see [File Inputs](./file-inputs).

## Streaming

Set `"stream": true` to receive Server-Sent Events in the OpenAI SSE format. Any OpenAI client library that handles streaming will work unchanged.
Expand Down Expand Up @@ -91,6 +93,8 @@ Models that advertise tool-calling capability accept the standard OpenAI `tools`

The response shape (`tool_calls` in the assistant message, `tool` role for results) matches OpenAI exactly.

To let the model read web pages by URL, add the Router's `{"type": "0g:web_fetch"}` tool — see [Web Fetch](./web-fetch).

## JSON Mode

For models that support structured output:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
id: file-inputs
title: File Inputs
sidebar_position: 5
description: "Attach PDF and text files inline to chat requests; the Router extracts the text for models that cannot read files themselves."
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# File Inputs

You can attach files inline to `POST /v1/chat/completions` and `POST /v1/messages` requests, in each API's standard shape. Any chat model can answer questions about them, except the [end-to-end encrypted models](#end-to-end-encrypted-models): when the provider serving the request cannot read files itself, the Router extracts the text and puts it in the message where you attached the file.

Supported formats are **PDF** (with a text layer) and **UTF-8 text** files such as `.txt`, `.md`, `.csv`, `.json` or source code. The format is detected from the file's content, not from its name or declared MIME type.

## Request

Send the file as base64, either as a `data:` URI or as bare base64.

<Tabs>
<TabItem value="chat" label="Chat Completions" default>

```bash
curl https://router-api.0g.ai/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-YOUR_API_KEY" \
-d '{
"model": "deepseek-v4-flash",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "What is the new launch date in these notes?"},
{"type": "file", "file": {
"filename": "notes.txt",
"file_data": "data:text/plain;base64,TWVldGluZyBub3RlcwpEZWNpc2lvbjogbGF1bmNoIGRhdGUgbW92ZWQgdG8gMjAyNi0xMS0xNS4K"
}}
]
}]
}'
```

For a PDF, use `data:application/pdf;base64,...`.

</TabItem>
<TabItem value="messages" label="Messages">

```bash
curl https://router-api.0g.ai/v1/messages \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-YOUR_API_KEY" \
-d '{
"model": "glm-5.3-flash",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "What is the new launch date in these notes?"},
{"type": "document", "title": "notes.txt", "source": {
"type": "base64",
"media_type": "text/plain",
"data": "TWVldGluZyBub3RlcwpEZWNpc2lvbjogbGF1bmNoIGRhdGUgbW92ZWQgdG8gMjAyNi0xMS0xNS4K"
}}
]
}]
}'
```

For a PDF, set `media_type` to `application/pdf`.

</TabItem>
</Tabs>

The file name (`filename`, or `title` on `/v1/messages`) is shown to the model with the file's text, so give it a meaningful name.

## How the file is read

- **Provider reads files itself.** The file is forwarded unchanged and the provider reads it, including content the Router cannot extract, such as scanned pages.
- **Otherwise, the Router extracts the text.** Each file is replaced by a text part holding its content, between `[File: <name>]` and `[End of file: <name>]` markers. The response's `x_0g_trace.request_rewritten` then contains `"file_parse"`:

```json
"x_0g_trace": {
"request_id": "096a7a44-a82d-4a3d-b84b-b9528ddffb3c",
"request_rewritten": ["file_parse"]
}
```

Because the provider received a modified request, its TEE signature will not match the request you sent when `request_rewritten` is present.

## End-to-end encrypted models

Requests to `0gm-1.0-35b-a3b` and `glm-5.3` are encrypted before they reach the Router, so the Router cannot read the file or extract its text. The file reaches the provider unchanged, and the provider rejects the request with a `400` error. To ask about a file with these models, paste its text into the message instead, or use another model, such as `glm-5.3-flash`.

## Limits

The limits and errors below apply when the Router extracts the text. When the provider reads files itself, the file is forwarded as sent and the provider decides what it accepts.

| Limit | Value |
| --- | --- |
| Files per request | 20, counted across all messages, including files attached in earlier turns |
| Text kept per file | 200,000 bytes; the rest is cut and the model is told the file was truncated |
| Request body | 20 MB, after base64 encoding |

Files are not stored: send the file again in every request that needs it, as in a normal conversation history.

## Errors

A file the Router cannot read fails the request with `400` and `invalid_request_error`, rather than letting the model answer without it. The message names the file.

| `code` | Cause |
| --- | --- |
| `unsupported_file_type` | The file is neither a PDF nor UTF-8 text (for example an image, a `.docx` or a `.zip`) |
| `file_parse_failed` | The PDF has no extractable text (a scanned PDF; OCR is not supported), or is damaged or encrypted |
| `unsupported_file_source` | The file is given by `file_id`, by URL, or by an Anthropic `source.type` other than `base64`. Only inline base64 data is accepted |
| `invalid_file_data` | The file data is empty or not valid base64 |
| `too_many_files` | More than 20 files in the request |

These errors are not charged.

## Billing

There is no fee for files. When the Router extracts the text, that text is billed as input tokens at the model's normal price. When the provider reads the file itself, you pay the input tokens the provider reports for it.

## Related

- [**Chat Completions**](./chat-completions) — request and response shape
- [**Errors**](../errors) — response codes and error shapes
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
id: web-fetch
title: Web Fetch
sidebar_position: 6
description: "Let the model read web pages by URL, over several rounds, with the 0g:web_fetch tool and a per-page fee."
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Web Fetch

The `0g:web_fetch` tool lets the model read web pages by URL. The model decides which pages it needs, the Router fetches them and returns their text to the model, and the model can then fetch more pages (for example, a link on a page it just read) before it answers. It works on `POST /v1/chat/completions` and `POST /v1/messages`, with any model that supports tool calling.

## Request

Add `{"type": "0g:web_fetch"}` to `tools`.

<Tabs>
<TabItem value="chat" label="Chat Completions" default>

```bash
curl https://router-api.0g.ai/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-YOUR_API_KEY" \
-d '{
"model": "deepseek-v4-flash",
"messages": [{"role": "user", "content": "Read https://example.com, follow its link, and tell me the title of the linked page."}],
"tools": [{"type": "0g:web_fetch", "max_uses": 3}]
}'
```

</TabItem>
<TabItem value="messages" label="Messages">

```bash
curl https://router-api.0g.ai/v1/messages \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-YOUR_API_KEY" \
-d '{
"model": "glm-5.3-flash",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Read https://example.com, follow its link, and tell me the title of the linked page."}],
"tools": [{"type": "0g:web_fetch", "max_uses": 3}]
}'
```

</TabItem>
</Tabs>

| Field | Type | Description |
| --- | --- | --- |
| `type` | string | `"0g:web_fetch"` |
| `max_uses` | integer | Optional. How many different URLs the Router will try to fetch in this request; a failed fetch counts too. Default `5`, at most `20` |
| `max_content_tokens` | integer | Optional. How much of each page the model gets, in tokens (about 4 characters each). Default `5000`, at most `25000` |
| `allowed_domains` | string[] | Optional. Only fetch pages on these domains (subdomains included) |
| `blocked_domains` | string[] | Optional. Never fetch pages on these domains (subdomains included) |

OpenRouter's `{"type": "openrouter:web_fetch", "parameters": {...}}` form is accepted too.

## How it runs

1. The Router sends your request to the model with a `web_fetch` function added.
2. If the model answers without calling it, that answer is your response.
3. If it calls it, the Router fetches the requested pages, gives their text to the model as tool results, and asks the model again. The model can fetch more pages or answer.
4. After 5 rounds of fetching, the model is told to answer with what it has.

Things to know:

- **Fetch failures don't fail the request.** A page that cannot be fetched, a URL that is not `http`/`https`, a domain outside your filters, or reaching `max_uses` goes back to the model as an error for that call, and the model continues.
- **Page text only.** The model gets the page's URL, title and text, not images.
- **Your own tools work alongside it.** List your function tools in the same `tools` array. If the model calls one of yours without fetching in the same turn, you get that tool call as usual. If it calls yours and `web_fetch` in the same turn, your call is dropped; the model can call it again in a later turn.
- **Name clash.** If one of your own tools is named `web_fetch`, the Router removes the `0g:web_fetch` tool and sends the request without it.
- **Streaming waits for the answer.** With `"stream": true`, the Router gets the model's complete answer first (after any fetching), then sends it to you as Server-Sent Events. The first bytes arrive only when the answer is ready, even when the model fetches nothing.
- **Never sent to the provider.** The `0g:web_fetch` entry is always removed before the request is forwarded. If fetching is unavailable for the request, the model answers without it.
- **Fetched pages add no citations.** Unlike web search results, fetched pages are not returned as `url_citation` annotations.

## Billing

Each page fetched costs **$0.001**, on top of the model's token charges.

- **Free:** a fetch that fails, a page fetched again in the same request, and any request to a free model.
- **Tokens:** every round is a model turn billed on its own token counts. Fetched page text is part of the next turn's input, so it is billed as input tokens.

The fee shows up in [`x_0g_trace.billing`](./chat-completions#x_0g_trace-always-present). It is included in `input_cost` and `total_cost` and reported on its own in `web_fetch_cost`, in the same unit as the other cost fields: micro-USD for a USD-settled account, so two pages fetched show as `2000`:

```json
"x_0g_trace": {
"billing": {
"currency": "usd",
"input_cost": "2549",
"output_cost": "318",
"total_cost": "2867",
"web_fetch_cost": "2000"
}
}
```

`x_0g_trace.billing` covers every round, while `usage` in the response is the answering turn only. In your usage history, each fetching round is its own entry, with the request ID plus `-wf1`, `-wf2`, and so on.

A 0G-settled account pays the fee in 0G, converted from the USD price at the Router's current 0G/USD rate; the cost fields are then in wei. If no current rate is available, the request is served without the tool rather than charged.

## Related

- [**Chat Completions**](./chat-completions) — request and response shape
- [**Models**](../models) — check which models support tool calling
- [**Errors**](../errors) — response codes and error shapes
2 changes: 2 additions & 0 deletions sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ const sidebars: SidebarsConfig = {
'developer-hub/building-on-0g/compute-network/router/features/chat-completions',
'developer-hub/building-on-0g/compute-network/router/features/image-generation',
'developer-hub/building-on-0g/compute-network/router/features/audio',
'developer-hub/building-on-0g/compute-network/router/features/file-inputs',
'developer-hub/building-on-0g/compute-network/router/features/web-fetch',
'developer-hub/building-on-0g/compute-network/router/features/verifiable-execution',
],
},
Expand Down
Loading