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

# Website Tools

> Build a read-only tool for a website that has no integration, then call it like any other tool

> [!WARNING]
>
> Early access: not yet enabled for every account. Check with `GET /v1/website-tools/status`; while the feature is off, the build endpoints answer 503.

Some sites your agents need have no integration at all: a supplier portal, a public registry, a club's booking page. A website tool fills that gap. You give Danube a URL and a task in plain words. Danube opens the site in a browser, works out how to do the task, writes a script, tests it, and publishes it as a **private tool** that belongs to you.

From then on the tool is called like any other Danube tool (`execute_tool` over MCP, `tools.execute` in the SDKs, `danube execute` in the CLI). A call runs the saved script with no model in the loop, so it takes seconds.

## What it covers

* **Read-only.** A website tool reads and returns data. It does not submit forms that change anything, buy, post or delete.
* **Sites with a login.** Save a login for the site first (username, password and, if the account uses one, its authenticator secret). Builds and runs sign in with it, and generate authenticator codes themselves. The login is stored encrypted and never returned.
* **SMS and email codes.** When a site sends a one-time code by text or email, the build pauses and asks you for it. A call to the finished tool cannot wait for a code, so for a site that texts a code at every sign-in, set up an authenticator app instead and save its secret with the login.
* **Questions during a build.** A build can stop on a question for you (status `waiting_input`): which of two tables you meant, which store, or a sign-in code. Answer it and the build carries on.
* **Automatic repair.** When a run fails because the site changed, Danube rebuilds the tool on its own (a repair build, with `repair_of` set to the tool) and saves a new version. Your tool id stays the same.

## How a build goes

| Status | Meaning |
| - | - |
| `queued`, `claimed` | Waiting for a builder, then exploring the site and testing a script. |
| `waiting_input` | Paused on a question. Read `question` and answer it. |
| `succeeded` | Published. `tool_id` is your new tool. |
| `failed` | `error` says why. |
| `cancelled`, `expired` | Stopped by you, or by Danube after it ran out of time. |

Building again with the same `name` saves a new version of the same tool.

## From an agent (MCP)

Agents connected to `https://mcp.danubeai.com/mcp` get four tools:

| Tool | What it does |
| - | - |
| `build_website_tool(url, task, name?, example_input?, sign_in?)` | Starts a build. Use it when `search_tools` finds nothing for a site. |
| `get_website_build(build_id, wait_seconds)` | Checks a build, waiting up to 30 seconds for it to finish or ask. |
| `answer_website_build(build_id, answers)` | Answers a paused build. |
| `save_website_login(url, username, password, totp_secret?)` | Saves the login a signed-in build uses. Only save a login the user gave for this. |

A typical run: `build_website_tool`, then `get_website_build` until the status is `succeeded` (answering any question on the way with `answer_website_build`), then `execute_tool` with the new `tool_id`.

### Answers

`answers` is an object keyed by question id:

| Question type | Answer |
| - | - |
| `choice` | An option id, or `{"other": "your words"}` |
| `multi_choice` | A list of option ids |
| `confirm` | `{"confirmed": true}` or `{"confirmed": false}` |
| `text`, `secret` | A string (a secret is usually a code the site just sent) |

## From the dashboard

Go to **Tools > Publish tool > Build one for a website** ([/dashboard/tools/website](https://danubeai.com/dashboard/tools/website)). Enter the URL and the task, follow the build, answer questions inline, and manage saved logins on the same page.

## From the SDKs

```python Python theme={null}
import os

from danube import DanubeClient

client = DanubeClient()

# Only for a site that needs a login:
client.website_tools.save_login(
    "https://portal.example.com",
    "me@example.com",
    os.environ["PORTAL_PASSWORD"],
    totp_secret=os.environ.get("PORTAL_TOTP_SECRET"),
)

build = client.website_tools.build(
    "https://portal.example.com",
    "Get the total of my latest invoice",
    sign_in=True,
)
build = client.website_tools.wait_for_build(build.id)

if build.needs_input:
    for q in build.question.questions:
        print(q.id, q.type, q.prompt)
    build = client.website_tools.answer(build.id, {"code": "123456"})
    build = client.website_tools.wait_for_build(build.id)

if build.status == "succeeded":
    result = client.tools.execute(tool_id=build.tool_id, parameters={})
```

```typescript TypeScript theme={null}
import { DanubeClient } from 'danube';

const client = new DanubeClient();

let build = await client.websiteTools.build(
  'https://example.com/events',
  'List upcoming events with date and title'
);
build = await client.websiteTools.waitForBuild(build.id);

if (build.status === 'waiting_input') {
  build = await client.websiteTools.answer(build.id, { [build.question!.questions[0].id]: 'oslo' });
  build = await client.websiteTools.waitForBuild(build.id);
}

if (build.status === 'succeeded') {
  const result = await client.tools.execute({ toolId: build.toolId!, parameters: {} });
}
```

Both SDKs also have `status`, `get_build` / `getBuild`, `list_builds` / `listBuilds`, `cancel`, `list_logins` / `listLogins` and `delete_login` / `deleteLogin`. Building with `sign_in` when no login is saved for the site raises `LoginRequiredError`, which carries the site's `host`.

## From the CLI

```bash theme={null}
# Optional: save a login (asks for the password and authenticator secret without showing them)
danube website login https://portal.example.com --username me@example.com

danube website build https://portal.example.com --task "Get the total of my latest invoice" --sign-in --wait
danube website status <build_id> --wait
danube website answer <build_id> --answers '{"code": "123456"}' --wait
danube website list
danube website cancel <build_id>
danube website logins

# Run the finished tool
danube execute <tool_id> -d '{}'
```

`danube website login` never takes the password as an argument. When stdin is not a terminal it reads the password from the first line and an optional authenticator secret from the second.

## REST

All routes are under `https://api.danubeai.com/v1/website-tools` and take the `danube-api-key` header.

| Method and path | Purpose |
| - | - |
| `GET /status` | `enabled`, `worker_online`, `builds_per_day`, `scope` |
| `POST /builds` | Start a build: `url`, `task`, optional `name`, `example_input`, `sign_in`, `authentication_origins` |
| `GET /builds` | Your builds, newest first |
| `GET /builds/{id}?wait_seconds=0..30` | One build, optionally waiting for it to finish or ask |
| `POST /builds/{id}/answer` | `{"answers": {...}}` |
| `POST /builds/{id}/cancel` | Cancel a build |
| `POST /logins` | Save a login: `url`, `username`, `password`, optional `totp_secret` |
| `GET /logins` | Saved logins (host, username hint, whether an authenticator is set; never secrets) |
| `DELETE /logins/{service_id}` | Delete a saved login |

`POST /builds` answers 400 for a URL that is not a public web address, 429 over the daily limit, 503 while the feature is off, and 409 with `{"error": "login_required", "service_id", "host", "message"}` when `sign_in` is set and no login is saved for the site.

## Limits

* **5 builds a day** per account. Repair builds Danube starts on its own do not use your allowance.
* **A build takes minutes.** Poll with `wait_seconds` rather than in a tight loop.
* **A run takes seconds**, since it replays the saved script without a model.
* Only public web addresses can be built; private and local addresses are refused.
* Sites that block automated browsers may fail to build.


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