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.
The two kinds
Section titled “The two kinds”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.
Defining a tool
Section titled “Defining a tool”| 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_listingskind: linkdescription: 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.
How placeholders get filled
Section titled “How placeholders get filled”- Each argument the model supplies is trimmed, capped at 200
characters, and percent-encoded (
%20for 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[]=retailwith bare brackets, write it that way and it stays that way.
Access levels
Section titled “Access levels”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.
Follow the visitor’s site
Section titled “Follow the visitor’s site”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.
What a fetch tool is allowed to do
Section titled “What a fetch tool is allowed to do”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
fetchtool 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
fetchtools 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.