Ownware
Home›Tokora›API
Tokora · API

Tokora API, as shipped in the download

Tokora REST API, MCP & Webhooks (v3.0)

Tokora ships a JSON REST API and signed webhooks so the queue can drive the rest of your room: a self-service kiosk, a second display, a Slack channel, an SMS gateway, or your own scripts.

Tokora sends no email, on purpose. The customer is standing in front of you and the display board is the notification — there is no event worth an inbox. Rather than ship an SMTP settings block nobody would ever fill in, there is no mailer at all. Webhooks are the integration surface.

Authentication

Create a key in API & Webhooks (in the sidebar). Send it on every call:

Authorization: Bearer apk_...

The key is shown once at creation and stored as a SHA-256 hash. Revoke any time.

Endpoints

Method & pathWhat it does
GET /api/pingVerify a key
POST /api/tokensIssue a ticket
POST /api/counters/{id}/call-nextCall the next waiting customer to a counter
GET /api/boardThe display-board state — the same payload the wall screen renders
GET /api/report?date=YYYY-MM-DDThe daily report
GET /api/services · GET /api/countersReference data
GET /api/openapi.jsonOpenAPI 3 spec (no auth)

Issue a ticket

curl -X POST https://your-install/api/tokens \
  -H "Authorization: Bearer apk_..." -H "Content-Type: application/json" \
  -d '{"service_id": 1, "note": "wheelchair access"}'

This is the endpoint a custom kiosk calls, and it uses the app's own numbering, not a re-implementation of it:

  • Numbers restart every service day, with no cron and no nightly job — the day is part of the ticket's identity, so "today's number 1" is simply the first ticket whose service_day is today.
  • A UNIQUE index across (service, day, number) is what makes two kiosks safe. If two requests land on the same number at the same instant, one loses the race and is retried, not handed a duplicate. Two customers can never be given the same ticket.
  • The label in the response comes from the same formatter the paper ticket, the display board and the counter console use, so nobody can be shown two different versions of their number.

Call the next customer

curl -X POST https://your-install/api/counters/3/call-next \
  -H "Authorization: Bearer apk_..."

Mirrors the counter console exactly: it completes whoever that counter was already serving, then takes the head of the FIFO queue, honouring the counter's service filter if it has one. When nobody is waiting it returns 200 with "called": null and a plain-English detail — an empty queue is a normal state, not an error.

MCP (Model Context Protocol) — new in 3.0

Tokora speaks MCP over stateless streamable HTTP at POST /mcp. Authentication is the same revocable API key as the REST API (Authorization: Bearer <key>), and a key carries its owner's role, so an agent can never do more than the person whose key it holds.

GET /mcp answers 405 with Allow: POST — deliberately without the key check, so a probing client is told "right endpoint, wrong verb" rather than a bare 401 that reads as "no MCP here".

POST /mcp   {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}
POST /mcp   {"jsonrpc":"2.0","id":2,"method":"tools/list"}
POST /mcp   {"jsonrpc":"2.0","id":3,"method":"tools/call",
             "params":{"name":"call_next","arguments":{"counter_id":1}}}
ToolWrites?What it does
list_servicesnoThe lines a visitor can join, with their token prefix
queue_statusnoWhat the wall board shows: now serving per counter, line lengths, next number
issue_ticketyesTakes a ticket for a visitor — the kiosk path, so an agent can be the receptionist
call_nextyesCalls the next waiting ticket to a counter, through the atomic claim
ticket_statusnoWhere a ticket stands, and how many people are ahead
daily_statsnoTickets issued and served, average wait and service time, per service and counter

The claim, and why it matters through MCP. call_next does not read-then-write. The write carries its own precondition, so if two counters — or a counter and an agent — call at the same instant, exactly one wins the ticket and the other is handed the next waiter. This is the same function the staff console and the REST API call; there is no second code path where the guard could be missing.

Argument hygiene. Tool arguments take the same scalar guard as the REST body: a value may not be an array or object unless the tool allowlists it, and unknown arguments are refused by name.

One issue path. issue_ticket delegates to the writer the kiosk itself uses, which carries the numbering retry against the unique (service, day, number) index and mints the visitor's private follow link. A ticket cannot come into existence any other way.

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://your-install/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 tokora https://your-install/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": "tokora",
  "server_url": "https://your-install/mcp",
  "authorization": "apk_xxxx",
  "require_approval": "never"
}

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

{
  "mcpServers": [
    { "id": "tokora", "name": "Tokora", "url": "https://your-install/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://your-install/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: Tokora implements the protocol, not an integration with a particular vendor.

Webhooks

Add receiver URLs in API & Webhooks. Events:

  • token.issued — a ticket is handed out (kiosk, desk or API)
  • token.called — a customer is called, including which counter
  • token.served — a customer's turn is completed

Each delivery is an HTTP POST with a JSON body and:

X-Tokora-Event: token.called
X-Tokora-Signature: sha256=<hex HMAC-SHA256 of the raw body, keyed with the endpoint's secret>

Verify the signature before trusting a payload. Delivery is one attempt with a short timeout — deliberately simple; the Recent deliveries log shows every attempt and its response code. Design receivers to be idempotent.

This is how you bolt on the things Tokora deliberately does not do: pipe token.called into a text-to-speech announcer, a second screen, a Slack channel, or an SMS gateway of your choosing.

CSV import

Two entities, both under Import:

  • Services — name and prefix, both required. Prefixes are normalised the way the app normalises them and must be unique: two services sharing a prefix would print two different customers the same ticket label.
  • Counters — name required; service optional. Leave the service blank for a counter that serves any queue.

Both run a mandatory dry-run whose report is produced by the same code that performs the import — including duplicate detection between two rows of the same file — so the number it promises is the number you get. A row that cannot be applied in full writes nothing.

Limits, honestly

  • The API acts at user level; there is no separate scope system.
  • One delivery attempt per webhook event (log + idempotent receivers, not a retry queue).
  • Tokens can be issued and called through the API; the recall / no-show / requeue actions stay in the counter console, where the person making the judgement call is standing.
  • No email, by design (see above).

← Back to Tokora · 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 →