Skip to content

Custom bot tools

Published docs let the bot answer questions about your product. Custom tools let it do something: build a search URL into your own app, look up an order, or fetch a page and summarize it.

Tools live at /w/<workspace-id>/<slug>/tools. Viewing them needs tools.read; creating and editing them needs tools.write, which admin and owner hold — a fetch tool makes outbound requests from Vroxy’s servers, so it isn’t something a support operator should be able to add by wandering into the wrong page.

Every tool is built from a URL template with {placeholder} slots that the model fills in from the visitor’s question.

  • link — Vroxy builds the URL and hands it to the bot, which shares it with the visitor. Nothing is requested server-side.
  • fetch — Vroxy builds the URL, GETs it from our servers, and gives the bot the (truncated) response so it can summarize the results and still link to them.

Use link when the visitor just needs to land on the right page. Use fetch when the answer is in the page and you want the bot to say it out loud.

Field Meaning
Name What the model calls. Lowercase letters, digits and underscores, 2–40 characters, starting with a letter. Unique within your workspace.
Kind link or fetch.
Who can use it public, user or admin — see below.
Description Teaches the bot when to use the tool.
URL template An http(s) URL with {placeholder} slots. Up to 2,000 characters.
Parameters JSON array, up to 8 entries: {"name", "description", "required"}.
Enabled Off pauses the tool without deleting it.
Follow the visitor’s site See below.

A worked example:

name: search_listings
kind: link
description: Search our commercial real-estate listings. Use whenever a
visitor asks to find, browse or filter properties — e.g.
"show me retail in San Francisco".
url_template: https://yoursite.com/search?q={query}&location={location}
[
{ "name": "query", "description": "Keywords, e.g. retail", "required": true },
{ "name": "location", "description": "City or region" }
]

Parameter names must match the {placeholders} in the template, and must themselves be lowercase letters, digits and underscores.

The description is the only thing that teaches the bot when to reach for the tool. The model sees each enabled tool as a name, a description and a parameter list — nothing else. A description like “searches listings” will be used rarely and badly; one that names the situations and gives an example phrasing, as above, gets used. Spend your effort there.

Guidance on what to do with a result travels back with the result itself: a link result tells the model to share that exact URL, and a fetch result tells it to summarize the body and include the URL so the visitor can open the full thing.

A few names are reserved because the bot already has built-in tools with them: search_docs, get_doc, request_human, remember and rails_query.

  • Each argument the model supplies is trimmed, capped at 200 characters, and percent-encoded (%20 for a space, never +), so placeholders work in query values and path segments alike.
  • A required parameter with no value aborts the call — the bot gets an error it can react to rather than a broken URL.
  • An optional parameter with no value becomes empty, and query pairs left with empty values are dropped, so the final URL stays clean.
  • Everything else in your template is passed through verbatim. If your app expects product_types[]=retail with bare brackets, write it that way and it stays that way.

Every tool has an access level, and it’s checked against the visitor’s verified level:

  • public — anyone, including anonymous visitors.
  • user — only visitors your server has vouched for as signed in.
  • admin — only visitors your server has vouched for as admins.

“Verified” is the load-bearing word. A visitor’s claimed identity is whatever your page passed to identify(), and anyone can call that from the browser console — so an unsigned claim never unlocks anything. The level only counts when your server signs it. See Identifying visitors for how the signature works and how each SDK does it for you.

Unsigned identify calls still personalize the conversation (the name and email show up in your workspace); they just leave the visitor at public tool access.

The check happens twice: tools above the visitor’s level are never even advertised to the model, and the level is checked again when a tool is actually called. The second check is the real boundary — models hallucinate tool names, and a transcript can replay across a change in who the visitor is.

When a tool exists but is out of reach, the bot is told so by name and instructed to tell the visitor to sign in with sufficient permissions and reopen the chat — rather than guessing at what the tool would have returned.

Callers using a workspace API token (server-to-server) run at admin, because that request is already authenticated as your own server.

Ticking Follow the visitor’s site (follow_origin) rebases the built URL’s scheme, host and port onto the origin of the page the widget is embedded on. Your template keeps the canonical production URL; a chat started on http://localhost:3000 or on your staging host gets links into that environment instead.

If there’s no page origin — an API or CLI caller has no page — or the origin can’t be parsed, the template’s own URL is used unchanged.

Leave it off for tools that point at a third-party site. With it on, a tool aimed at https://docs.example.com would follow the visitor to your app’s host and 404.

One interaction to know about: it works best on link tools. A fetch tool that follows the visitor onto a localhost or private-network origin will be refused by the address guard described below — the URL is fine to hand a visitor’s browser, but our servers won’t request it.

A fetch tool makes a real outbound request from Vroxy’s infrastructure, so it runs on hard rails:

  • Only http/https. Anything else is refused.
  • Internal addresses are refused. The hostname is resolved first, and if any resolved address falls in a loopback, private, link-local, carrier-NAT or unspecified range — IPv4 or IPv6 — the fetch is refused. The connection is then made to that validated address directly, so a host that resolves differently a moment later can’t redirect the request inward.
  • An unresolvable host is refused.
  • 6 seconds to connect and to read, and 10 seconds of wall clock for the whole request.
  • 100 KB of response body, maximum. Reading stops there.
  • HTML responses are stripped to plain text (scripts, styles and tags removed) before the bot sees them.
  • The result handed to the bot is truncated to about 6,000 characters.
  • Requests identify themselves as vroxyBot/1.0 (+https://vroxy.ai), so you can allow or block them at your own edge.

Failures never take down the conversation. A refused, failed or timed out fetch comes back to the model as a short error string, and the bot tells the visitor what it couldn’t find out.

Three things to design around:

  • The byte cap. A fetch tool pointed at a heavy HTML page may only see the top of it. Prefer a JSON endpoint or a lightweight results page.
  • No visitor session. The request comes from Vroxy’s servers with no cookies, so anything behind your app’s login comes back as a login page. Point fetch tools at endpoints that are safe to read unauthenticated.
  • Never put a secret in a URL template. The built URL is handed to the model along with the body, with instructions to share it so the visitor can open the full results — an API key in the template would end up in the chat. It’s also readable by anyone with tools.read.

And the general rule: anything a fetch tool can read, the bot can repeat to whoever is allowed to call it. Set the access level to match the data, not to match convenience.