by Intelliverse X

How Kiosk-X works — the whole picture

This is the "explain it like I'm five" map of the whole system, with every real URL, endpoint, and message kept in so engineers can use it too.

The simple story. A vending machine is like a little shop. The cloud (api.kiosk-x.ai) is the shop's brain — it remembers every machine, every snack, every sale. The operator app is the owner's magic remote: they can see money coming in and refill machines from their phone. And MQTT (mq.kiosk-x.ai) is a walkie-talkie so the brain and the machines can whisper to each other instantly. Everything is ours — no other company's servers are in the loop anymore.

The big map

flowchart LR Customer(["Customer"]) -->|taps + pays| Kiosk subgraph Machine["Vending machine (RK3288 tablet)"] Kiosk["Kiosk X APK
com.ruiye.jd"] VMC["Motor board (VMC)
via serial cable"] Kiosk <-->|dispense| VMC end subgraph Owner["Operator (owner)"] OpApk["Operator APK
(native Android)"] OpWeb["operator.kiosk-x.ai
(same thing, in a browser)"] end subgraph Cloud["Our cloud — api.kiosk-x.ai"] Partner["Partner API
/api/v1/*
(for people)"] Device["Device API
/apk/*
(for machines)"] Docs["Docs
/guides /docs /redoc"] end MQ(["MQTT broker
mq.kiosk-x.ai:1883"]) Nayax(["Nayax
card reader + settlement"]) S3(["S3 downloads / OTA
APK files"]) Kiosk -->|"HTTP: getEquipment, getGoods, createOrder"| Device Kiosk <-->|"presence + remote dispense + OTA"| MQ Kiosk -->|checks for new version| S3 VMC -. card tap .-> Nayax Nayax -->|webhook| Partner OpApk -->|"HTTPS + token"| Partner OpWeb -->|"HTTPS + token"| Partner Partner -->|remote dispense / push OTA| MQ MQ -->|command| Kiosk

Every address (URLs)

What it is URL Who talks to it
Cloud brain (API) https://api.kiosk-x.ai everyone
Partner API (for people/apps) https://api.kiosk-x.ai/api/v1/* operator app, web console, integrations
Device API (for machines) https://api.kiosk-x.ai/apk/* the Kiosk X vending APK
Operator web console https://operator.kiosk-x.ai operators in a browser
MQTT walkie-talkie tcp://mq.kiosk-x.ai:1883 machines ⇄ cloud (instant push)
Live docs https://api.kiosk-x.ai/guides · /docs (Swagger) · /redoc · /openapi.json anyone
Docs mirror (static) http://kiosk-x-docs.s3-website-us-east-1.amazonaws.com anyone
App downloads (APKs) /downloads — ZHZN 1.0.26, Reyeah 1.2.7, content-addressed on media.intelli-verse-x.ai installers / OTA
Readiness self-test https://api.kiosk-x.ai/sandbox/readiness ops / monitoring
Legacy aliases (still work) kiosk-x.intelli-verse-x.ai · hotbox-api.intelli-verse-x.ai old links

One host, two "doors": api.kiosk-x.ai/api/v1/* is the people door and api.kiosk-x.ai/apk/* is the machine door. They share the same brain and data but speak slightly different languages (see below).

What's inside the cloud

The cloud has two API "doors" plus the docs.

Door 1 — Partner API (/api/v1/*) — for people and their apps

Uses normal REST: HTTP status codes (200 OK, 401 unauthorized, 404 not found).

Endpoint What it does
POST /api/v1/auth/login Sign in with email + password (checked against Intelliverse identity), get a fleet-scoped bearer token
GET /api/v1/machines List the operator's machines (status, location, reported APK version)
POST /api/v1/machines/register Add a new machine — or claim one that auto-registered itself
GET /api/v1/machines/{no} One machine's detail
GET /api/v1/inventory?machineNos=… Aisle-level stock across machines
GET /api/v1/inventory/machines/{no} Aisle-level stock for one machine
POST /api/v1/inventory/machines/{no}/restock Refill aisles after a route stop
GET /api/v1/orders · /orders/{id} Sales history and detail
GET /api/v1/orders/summary Revenue + order totals for the dashboard
GET /api/v1/pnl Operator P&L (gross − tax − FIFO − host − …)
POST /api/v1/crm/venues Host venue + commission deal — name machineNos at create if the cabinet has already sold
POST /api/v1/purchases Receive stock; this is what creates the FIFO lots the P&L consumes
GET /api/v1/tax/schedules Sales-tax rate schedule
POST /api/v1/machines/{no}/commands/dispense Remote "drop this aisle" (cloud → machine)
GET /api/v1/accounts (admin) Operator accounts
POST /api/v1/rollouts Start a staged OTA rollout (see OTA section)
POST /api/v1/payments/nayax/webhook Nayax posts card transactions here (HMAC-signed)

Door 2 — Device API (/apk/*) — for the vending machines

This mirrors the original kiosk firmware's protocol. It uses a small envelope, not HTTP status codes:

{ "code": 0, "message": "ok", "data": { } }

code: 0 = success, code: 1 = failure. The machine's software checks code.

Endpoint When the machine calls it
GET /apk/getEquipment On boot — "who am I, what's my grid?" (auto-provisions unknown boards)
GET /apk/getStyle On boot — screen theme/layout
GET /apk/getGoods On boot — the product list with prices + stock
GET /apk/getAd Idle screen ads
POST /apk/createOrder · createOrderCart A sale just happened — record it
POST /apk/offlineCreateOrder* Flush sales made while the internet was down
GET /apk/getUpgradeVersion "Is there a newer APK for me?" (OTA)
POST /apk/apkUp "I'm now running version X" (adoption tracking)
POST /apk/ordersUpdate, submitFault, uploadLog, … Status + telemetry (accepted and acknowledged)

Every other /apk/* call the firmware might make is answered with a friendly success so the machine never freezes on an unknown request.

Signing in (auth)

The vending app (kiosk) — end to end

The current Reyeah build (reyeah-vending-kioskx-1.2.7-aaa3d485cbaa.apk on /downloads) runs on the machine's Android tablet (Rockchip RK3288, Android 7.1.2). Here's one full customer purchase:

sequenceDiagram autonumber participant C as Customer participant K as Kiosk X APK participant V as Motor board (VMC) participant N as Nayax reader participant D as Cloud /apk/* participant M as MQTT mq.kiosk-x.ai Note over K,D: On boot / power-on K->>D: GET /apk/getEquipment (equipmentNo header) D-->>K: code 0 — machine config + aisle grid K->>D: GET /apk/getStyle, /apk/getGoods, /apk/getAd D-->>K: theme, product list, ads K->>M: connect + publish presence {equipmentNo, state:1} Note over C,V: A sale C->>K: taps a snack on screen K->>N: ask for payment N-->>K: card approved C->>N: taps card K->>V: dispense aisle over serial cable V-->>K: motor turned, product dropped K->>D: POST /apk/createOrder {aisleNo, payType, price} D-->>K: code 0 — sale recorded N->>D: POST /api/v1/payments/nayax/webhook (settlement to operator's account) Note over K,D: Every few minutes K->>D: GET /apk/getUpgradeVersion D-->>K: newer APK? (see OTA)

Offline is fine. If the internet drops, the machine still sells (payment + motor are local) and queues the sales, then sends them later via /apk/offlineCreateOrder.

The operator app — end to end

The kiosk-x-operator.apk is a native Android app (same thing lives at operator.kiosk-x.ai in a browser). It only ever talks to the Partner API.

sequenceDiagram autonumber participant O as Operator participant A as Operator APK participant P as Cloud /api/v1/* participant M as MQTT participant K as A kiosk O->>A: open app, enter email + password A->>P: POST /api/v1/auth/login P-->>A: bearer token (valid ~30 days) A->>P: GET /api/v1/orders/summary P-->>A: revenue + order totals (dashboard) A->>P: GET /api/v1/machines P-->>A: fleet: online/offline, location, APK version O->>A: open a machine A->>P: GET /api/v1/inventory?machineNos=NO P-->>A: aisle stock (out / low / ok) O->>A: tap "Fill low aisles" A->>P: POST /api/v1/inventory/machines/NO/restock P-->>A: stock updated O->>A: "test dispense aisle 3" A->>P: POST /api/v1/machines/NO/commands/dispense P->>M: publish command to topic NO M->>K: {type:"1", aisleNo:"3"} — drop it

MQTT — the instant walkie-talkie

HTTP polling is the reliable backbone; MQTT is the fast extra channel for things that shouldn't wait for the next poll. It runs entirely on our broker, mq.kiosk-x.ai:1883. If the broker is unreachable, nothing breaks — the machine keeps vending over HTTP.

flowchart LR subgraph Kiosk["Kiosk X APK"] direction TB P1["publishes presence"] S1["subscribes to topic = its own equipmentNo"] end subgraph Bridge["Cloud MQTT bridge"] direction TB B1["subscribes to presence topic"] B2["publishes commands to a machine's topic"] end P1 -->|"device → cloud
{equipmentNo, state}"| B1 B2 -->|"cloud → device"| S1
Direction Topic Message Meaning
device → cloud presence topic {equipmentNo, state} machine online; a Last-Will marks it offline if it drops
cloud → device <equipmentNo> {type:"1", orderNumber, aisleNo} remote dispense
cloud → device <equipmentNo> {type:"2", url, version} push an APK install (OTA)
cloud → device <equipmentNo> {type:"3", …} install/update the VMC firmware

Payments & Nayax — where the money goes

flowchart LR Tap(["Customer taps card"]) --> Reader["Nayax reader on the machine"] Reader -->|approve| Kiosk["Kiosk X APK vends"] Reader -->|settlement| NayaxAcct["Operator's own Nayax account"] Reader -->|"POST /api/v1/payments/nayax/webhook (HMAC-signed)"| Cloud["Partner API"] Cloud --> Order["Order gets a payment block:
method, amount, terminal, txn id"] Order --> OpApp["Operator app shows the sale"]

Each operator has their own Nayax merchant account, and each machine is bound to a terminal, so money always lands in the right place and the matching sale shows up in that operator's app.

OTA rollouts — updating machines safely

New APKs are built + signed in CI, stored on S3, and rolled out gradually so a bad build can't brick the whole fleet at once.

sequenceDiagram autonumber participant CI as CI build (signed) participant S3 as S3 (APK file) participant P as Partner API participant K as Kiosk CI->>S3: upload reyeah-vending-kioskx-1.2.7-aaa3d485cbaa.apk P->>P: POST /api/v1/rollouts (canary %, target group) K->>P: GET /apk/getUpgradeVersion P-->>K: {version, url} if this machine is in the wave K->>S3: download the APK K->>K: install (same signing key → updates in place) K->>P: POST /apk/apkUp {version} — "I upgraded"

One-time migration note: a machine already running the old vendor build must be uninstalled first (adb uninstall com.ruiye.jd) because our signing key is different. After that, all future updates apply in place automatically.

The full lifecycle, in one line each

  1. Install — flash the current Reyeah or ZHZN build from /downloads (today Reyeah 1.2.7 / ZHZN 1.0.26, content-addressed; never the mutable reyeah-vending-kioskx.apk key) on the tablet (armeabi-v7a, API 25+).
  2. First bootgetEquipment auto-provisions the board into an "unclaimed" pool, fully stocked.
  3. Claim — operator runs POST /api/v1/machines/register to take ownership (orders + inventory move to them).
  4. Sell — customer taps + pays (Nayax), motor drops via serial, createOrder records it.
  5. Refill — operator sees low aisles in the app and calls restock after a route stop.
  6. Update — CI ships a new signed APK; staged OTA rolls it out and tracks adoption.
  7. Watch — MQTT presence + /sandbox/readiness show what's online and healthy.

Everything above is our own stack end to end — one cloud (api.kiosk-x.ai), one broker (mq.kiosk-x.ai), our own signed apps.