Skip to content

The REST API

Everything the widget and the workspace do to your data, a server of yours can do too. The API is scoped to a single workspace, authenticated by a bearer token you issue and revoke yourself.

Base URL: https://vroxy.ai/api/v1.

Create a token at API tokens → New token in your workspace. The raw value is shown exactly once, on the page immediately after you create it — there is no way to read it back later, only to revoke it and issue a new one.

Terminal window
curl https://vroxy.ai/api/v1/docs \
-H "Authorization: Bearer $VROXY_TOKEN"

Two scopes matter:

Scope What it allows
tenant:read Every GET. The minimum for any call.
tenant:write Creating, updating and deleting; anything that spends LLM budget.

A token without tenant:read is rejected with 401. A read-only token attempting a write gets 403 with a message naming the scope it needs.

This is not the public key from your embed snippet. The public key identifies your widget to browsers and grants nothing; an API token carries real authority and belongs on your server only.

Failures return JSON with an error key and a matching status: 401 for a bad token, 403 for insufficient scope, 404 for a record outside your workspace, 422 for a malformed body.

A hashid belonging to another workspace returns 404, never 403 — the API won’t confirm that a record exists somewhere you can’t see it.

List endpoints take ?page= and ?per= (default 25, max 100) and return a meta block:

{
"docs": [ ... ],
"meta": { "current_page": 1, "total_pages": 4, "total_count": 87, "per_page": 25 }
}
GET /api/v1/chats # paginated list
GET /api/v1/chats/:id # one chat with its full transcript

Filter the list with ?status=open|archived and ?ended=yes|no.

Full CRUD over the same documents the bot answers from — the rows you’d otherwise edit at Docs in your workspace. This is the endpoint to reach for if you’d rather your docs live in your own repository and get pushed from CI.

GET /api/v1/docs # ?status=draft|published
GET /api/v1/docs/:id # includes body_md
POST /api/v1/docs # { "doc": { "title": ..., "body_md": ... } }
PATCH /api/v1/docs/:id
DELETE /api/v1/docs/:id
POST /api/v1/docs/:id/publish
POST /api/v1/docs/:id/unpublish
POST /api/v1/docs/scrape # pull a URL in as a doc

Everything except the two GETs needs tenant:write. A doc stays invisible to the bot until it’s published.

POST /api/v1/bot/reply
{ "message": "how does billing work?", "chat_id": "<optional hashid>" }

Runs the same retrieval and tool-use loop the widget runs, and answers synchronously. The response carries the reply plus what it took to get there — which documents were consulted, which tools ran, the model, token counts, cost and duration.

Omit chat_id to start a conversation; pass the one you got back to continue it. Each token gets its own visitor identity, so two tokens on the same workspace never share history.

Useful for a CLI, for evaluating changes to your prompt or corpus against a fixed set of questions, or for putting an answer somewhere that isn’t the widget. It needs tenant:write, because it creates rows and spends real model budget.

These conversations appear in your workspace alongside real visitor traffic, labelled with the token that produced them.

GET /api/v1/feedback # ?status=open|triaged|resolved|dismissed
GET /api/v1/feedback/:id # detail, screenshots, and dispatches
POST /api/v1/feedback/:id/dispatch # { "repo": "<repo hashid>" }

The dispatch endpoint hands a ticket to GitHub Copilot’s coding agent without a browser or a local agent. See Feedback to fix for what it requires — in short, a linked repository and a designated dispatch identity.

Poll GET /api/v1/feedback/:id afterwards and read the dispatches array for progress.

GET /api/v1/repos

The linked, enabled repositories a dispatch can target, each with the base branch it would work from. The response also carries dispatch_ready, which tells you whether a dispatch identity is designated at all — check it before offering dispatch in your own UI, rather than discovering the problem on the failed call.

GET /api/v1/glossary
PUT /api/v1/glossary
{ "entries": [ { "term": "seat", "aliases": ["licence"], "admin_url_template": "..." } ] }

PUT merges by term by default: a term already present is updated, an unknown one is appended, and entries you don’t mention are left alone. That’s deliberate — it means a sync job can run on every deploy without erasing entries someone added by hand. Pass "replace": true to swap the whole list instead.

Writing needs tenant:write.

When you want a person, not an application

Section titled “When you want a person, not an application”

Everything above is a workspace credential: it does what its scopes allow and it isn’t anybody. That’s the right shape for a server, a build step, or an integration you own.

It is the wrong shape when the caller should be a person — when the question is “may Sam do this”, not “may this application do this”. Seats, invitations and anything gated on an individual’s level aren’t on this API at all, deliberately: a workspace token has no level, so the rank rules that stop an admin promoting themselves have nothing to compare against.

For that, use the command line. It signs in as you and every call is checked against your own capabilities, so it can reach members and seats that a workspace token can’t, while being unable to exceed what you could do in the browser.

Issue one token per consumer rather than sharing one, so revoking a compromised token doesn’t take down everything else. Give a token tenant:read unless it genuinely writes. Revoke on the API tokens page the moment a token is no longer needed — revocation takes effect on the next request.

Tokens carry a last-used timestamp, so a token nobody has used in months is safe to revoke and easy to spot.