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

# Host read tools

> Let the guide read the visitor's own account data from your app — invoices, usage, seats — through read-only functions you register, bounded and privacy-screened.

Some answers need data only your app has for the signed-in visitor: "Do I have
unpaid invoices?", "How many API calls did we make this month?". Register a
**read tool** and the guide can ask your page to run it, then answer with what
it returned.

> **Host tools read. They never write, and what they return never authorizes
> anything.** Results ground answers exactly like
> [page-reported account facts](/account-facts#trust).

> **Release preview:** host read tools are implemented and tested in the
> repository and reach the hosted script with its next release. The
> `@puntego/react` hook below (`usePuntegoTool`) is not yet published to npm.
> Feature-detect with `typeof window.Puntego?.registerTool === 'function'`.

<h2 id="register">
  Register a tool
</h2>

```ts theme={null}
window.Puntego?.registerTool({
  name: 'list_invoices',
  description: "Lists the signed-in account's recent invoices with status and total.",
  parameters: {
    status: { type: 'string', enum: ['open', 'paid'], description: 'Only invoices with this status.' },
  },
  fields: ['invoices'],
  timeoutMs: 3000,
  handler: async ({ status }, { signal }) => {
    const response = await fetch(`/api/invoices?status=${status ?? ''}`, { signal });
    const invoices = await response.json();
    return { invoices: invoices.map(({ id, status, total, currency }) => ({ id, status, total, currency })) };
  },
});
```

With React, register it where your app has the data; it is withdrawn on
unmount and the latest handler is always the one called:

```tsx theme={null}
import { usePuntegoTool } from '@puntego/react';

usePuntegoTool({
  name: 'list_invoices',
  description: "Lists the signed-in account's recent invoices with status and total.",
  fields: ['invoices'],
  handler: async () => ({ invoices: await loadInvoices() }),
});
```

| Field | Rules |
| - | - |
| `name` | Lowercase letters, digits and `_`, starting with a letter; up to 48 characters. Registering a name again replaces the tool. |
| `description` | What the tool reads, up to 240 characters. The guide reads it as data, never as instructions. |
| `parameters` | Up to 6 top-level parameters: `string` (optionally an `enum` of up to 24 values), `number` or `integer` (optional `minimum`/`maximum`), `boolean`. |
| `required` | Parameters the guide must fill. Every other parameter may be left out. |
| `fields` | **The allowlist.** Up to 24 top-level result fields that may leave the page. A field named like a credential or personal data (`email`, `token`, `password`, …) is refused at registration. |
| `timeoutMs` | 250–8000 ms, 3000 by default. A slower tool counts as `timeout`. |
| `handler` | `(args, { signal, turnId }) => data`, sync or async. Honour `signal`: it aborts on the timeout and when the visitor stops the turn. |

A page offers at most 8 tools at once. `registerTool` returns `false` and logs
why in the console when a definition is refused; `unregisterTool(name)` removes
one. Tools belong to the page: `setIdentity()` keeps them, `destroy()` clears
them like every other registration.

<h2 id="turn">
  What happens on a turn
</h2>

1. On a question the visitor asked, the page offers the guide its tools (names,
   descriptions and parameters; never your code).
2. When the answer needs data only a tool returns, the guide asks the page to
   run up to **2** calls, with arguments of the types you declared. The card
   says "Looking up your account…" meanwhile.
3. Your handlers run in the visitor's browser session, with your own API's
   permissions. The page checks the arguments against your declaration first,
   then keeps only the listed `fields` of the result.
4. The same turn is sent again with the results and answered. That request
   offers no tools, so there is one round at most.

The guide does not call tools on turns the visitor did not start (automatic
prompts, campaigns), on a second look at the page, or for a generated button.

<h2 id="limits">
  Limits and privacy
</h2>

What a tool returns is plain JSON data. Before anything leaves the page:

* only the listed `fields` stay (of the object you return, or of each object in
  an array you return), whatever kind of object it is: a class instance or an
  SDK model keeps only its listed fields too, and an unlisted field is never
  read;
* keys named like a credential or a direct identifier are dropped, and a value
  that contains an email address, phone number, card number, IBAN, national ID
  or a signed token (a JWT) is dropped whole, at any depth;
* text is kept on one line and up to 400 characters, arrays up to 25 items,
  objects up to 32 keys, 4 levels deep, and 8 KB per result (16 KB for all
  results of a turn);
* an exception, a rejected promise, a timeout or data that is not JSON becomes a
  status (`error`, `timeout`, `invalid_result`, `too_large`), never an error
  message sent to the model.

Puntego checks all of this again on its server, and shows results to the model
as data inside a clearly delimited block of its input, never in its
instructions. Instruction-like text inside a result is data like any other.

Results are read for the turn that asked for them and are not saved with the
conversation. The answer the guide writes from them is kept like any other
answer.

<h2 id="signed">
  Signed results
</h2>

A result grounds the answer as the page's word. When a tool reads facts the
guide may state as verified (plan, seats, entitlements), return a token your
server signed instead of raw data:

```ts theme={null}
handler: async () => ({ signedAccountFacts: await fetchSignedFacts() }),
```

The token is the one [signed account facts](/account-facts#signed) use, verified
the same way (signature, app id, expiry, account). It is used when your host
context carries no signed token of its own. Return it on its own: next to other
fields, or nested in them, it is dropped and never sent.

<h2 id="events">
  Observe tool calls
</h2>

Each call reports `tool:result` with the tool's name, its status, how long it
took and the turn — never its arguments or data:

```ts theme={null}
window.Puntego?.on('tool:result', ({ name, status, durationMs }) => {
  analytics.track('Guide tool', { name, status, durationMs });
});
```

<h2 id="troubleshooting">
  Troubleshooting
</h2>

* **The guide never calls the tool.** The question must need data the page,
  the account facts and the conversation do not already give. Make the
  `description` say exactly what the tool reads.
* **`registerTool` returns `false`.** The console names the rule: the name
  pattern, a missing `fields` list, a field named like personal data, or the
  8-tool limit.
* **The answer says the data could not be read.** The tool returned a status
  other than `ok`; listen to `tool:result` to see which.


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