Ownware
Home›Visitora›API
Visitora · API

Visitora API, as shipped in the download

Visitora REST API, Webhooks & MCP (1.0)

Visitora ships a JSON REST API, signed webhooks and an MCP endpoint, so your visitor book plugs into Power Automate, Zapier, n8n, AI agents, or your own code.

Authentication

Create a key in Settings → API & Webhooks (admin). A key belongs to a user and can do exactly what that user can do in the app — and it carries its own scope on top of that. Send it on every call:

Authorization: Bearer apk_...

The key is shown once at creation and stored hashed. Revoke any time.

ScopeWhat it can do
Read and writeEverything its owner's role allows
Read onlyGET/HEAD/OPTIONS only. A write is refused with 403 forbidden at the door, and the MCP endpoint hides write tools entirely

Endpoints

Method & pathWhat it does
GET /api/pingVerify a key
GET /api/on-siteEveryone inside the building right now — the evacuation list, with the roll-call tally
GET /api/visitsVisit log, newest first (max 200); ?on_site=1, ?day=YYYY-MM-DD, ?host_id=
GET /api/visits/{id}One visit, with whether the site rules were accepted and their fingerprint
POST /api/visits/{id}/sign-outSign a visitor out. Idempotent
GET /api/hostsActive hosts with live on-site counts
GET /api/openapi.jsonOpenAPI 3 spec (no auth)

Why there is no "sign in" endpoint

The evacuation list is only worth anything if every name on it belongs to a body in the building. A server that could POST "consider this person signed in" would put names on a muster sheet with nobody behind them — and the first time that mattered would be a fire drill where somebody went back inside looking for a person who was never there.

So signing in happens at the kiosk, where a person is standing. Signing out is different and is the one write here: a visitor who forgot to sign out is a false positive on the list, and clearing those at end of day is exactly what an integration is useful for.

What the API will not tell you

A visitor's phone number and the IP address a sign-in came from are never serialized. An integration has no reason to hold either, and a muster list is not a mailing list. The same two fields are redacted out of every JSON backup.

Webhooks

Add an endpoint in Settings → API & Webhooks, choose events, and Visitora POSTs JSON signed with your per-endpoint secret:

X-Visitora-Event: visit.signed_out
X-Visitora-Signature: sha256=<hmac_sha256(body, secret)>
EventFires when
visit.signed_insomebody signs in at the kiosk
visit.signed_outsomebody signs out, however it happened — the kiosk, the desk, a bulk sign-out, the REST API or the AI assistant

Verify the signature before trusting a payload. Delivery is best-effort with a short timeout: a slow receiver must never block the person standing at the kiosk. One attempt per event; the delivery log on the admin page answers "did it fire?".

Limits, honestly

One attempt per webhook, no retry queue. Lists cap at 200 rows. There is no rate limiter on /api/* beyond your own web server's — this is a single-tenant app on your box, and the key is the control.


MCP — the agent door

Visitora speaks the Model Context Protocol at POST /mcp, so Claude, ChatGPT agents, n8n's AI nodes or your own code can use Visitora instead of merely reading it. It is the same product underneath: the same bearer key, the same roles, the same one definition of who is on site.

Transport is streamable HTTP, stateless: one JSON-RPC 2.0 request in, one JSON response out.

curl -s -X POST https://reception.example.com/mcp \
  -H "Authorization: Bearer apk_xxxx" -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"my-agent","version":"1"}}}'

Then tools/list to discover, tools/call to act:

curl -s -X POST https://reception.example.com/mcp \
  -H "Authorization: Bearer apk_xxxx" -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"who_is_on_site","arguments":{}}}'

Connect it to an assistant

Mint the key in the app first: Settings → API keys. Choose Read only when the assistant should answer questions but never change anything — the endpoint then lists only the read tools and refuses the rest by name, so a careless prompt cannot write. Full access behaves as before.

Every client needs the same three facts, and nothing in the handshake is vendor-specific:

The addresshttps://reception.example.com/mcp
The keyheader Authorization: Bearer apk_xxxx
The transportMCP over streamable HTTP, stateless

Claude — one command, or the same URL and header as a custom connector in the desktop and web apps:

claude mcp add --transport http visitora https://reception.example.com/mcp \
  --header "Authorization: Bearer apk_xxxx"

ChatGPT and the OpenAI API — one entry in the Responses API's tools array (in ChatGPT itself, the same URL and key go in as a connector):

{
  "type": "mcp",
  "server_label": "visitora",
  "server_url": "https://reception.example.com/mcp",
  "authorization": "apk_xxxx",
  "require_approval": "never"
}

Own Your AI reads a list of servers in this shape:

{
  "mcpServers": [
    { "id": "visitora", "name": "Visitora", "url": "https://reception.example.com/mcp",
      "token": "apk_xxxx", "enabled": true }
  ]
}

**Every other client spells the same three facts differently — copy the shape from its own documentation, not from here.** VS Code is the clearest example of why: its configuration reference (read 6 September 2026) puts servers in .vscode/mcp.json under a "servers" object — *"an object that maps server names to their configurations"* — not an mcpServers array. Pasted as-is, the block above will not load there. The id, the URL and the token are what travel; the JSON around them belongs to whichever client you are configuring.

A local model, n8n, or your own code — n8n's MCP Client node takes the URL and the same Authorization: Bearer header; a model running on your own machine reaches it through any MCP client, so nothing leaves your network at all. Writing it yourself is one POST of JSON-RPC 2.0:

curl -X POST https://reception.example.com/mcp \
  -H "Authorization: Bearer apk_xxxx" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Any client that speaks MCP's streamable-HTTP transport works, including ones that do not exist yet: Visitora implements the protocol, not an integration with a particular vendor.

Tools

ToolWrites?What it does
who_is_on_sitenoEveryone inside the building now, with hosts, badges and time on site, plus the roll-call tally
list_visitsnoRecent visits (max 100); optional on_site or day
get_visitnoOne visit, and whether the site rules were accepted
list_hostsnoHosts with live counts of their own visitors on site
expected_todaynoPre-registered visitors who have not arrived yet
sign_out_visityesSigns one visitor out by visit id. Idempotent

There is deliberately no sign-in tool, no delete tool and no settings tool. An agent can tell you who is in the building and tidy up the people who forgot to sign out. It cannot invent an arrival, erase a record, or change your retention policy.

Operational endpoints

EndpointGuardWhat it does
GET /backup/scheduled?t=<token>ops token (Backup & restore)Writes a redacted JSON backup into data/backups/, keeps the newest 14
GET /calendar.ics?t=<token>its own feed token (Settings)Read-only iCal feed of expected visitors
GET /visitors/{id}/gdpr.jsonadmin sessionSubject-access export for one person, with every visit they made
GET /healthznoneLiveness, app name and version

cron/purge.php is CLI-only and enforces the retention window. It is not reachable over HTTP — a purge behind a URL is an unauthenticated "delete my records" button.

← Back to Visitora · Manual · Quickstart · Test run

Affiliate program
Recommend tools people own — earn 35% on every sale. 90-day tracking, instant delivery, payouts by Lemon Squeezy.
Become an affiliate →