by Intelliverse X

Client apps & downloads

Kiosk X is a gamified vending network for any category — snacks, drinks, tech, beauty, or age-restricted goods. One cloud connects two roles: the on-device Vending Machine apps (one per hardware family — Reyeah "JD" and ZHZN/CSM) run on the kiosk hardware; the Operator app is the owner's fleet console. Both speak only to the Intelliverse cloud (api.kiosk-x.ai) — the whole lifecycle, from physical install to refills and OTA upgrades, runs on our own stack. New here? See how it all works (pictures of the whole system).

Choose by the cabinet's controller

Reyeah: use the JD machine APK below on a validated Reyeah cabinet. ZHZN: use the ZHZN / CSM machine APK on a validated controller profile. Neither package is certified for every machine sold under a brand name.

DFY / Vending Concepts VC EL Series: DFY describes the service package, not a controller protocol. The cabinet identified as XT-202303207146 still requires controller and installed-software identification. No verified VC EL / Silkron machine APK is available here. Stabilize the flickering screen before installing a replacement machine app.

Start here: all machine types · Reyeah manual · ZHZN installation & handover · ZHZN newcomer course (first day, in order) · ZHZN release verification · VC EL screen repair & identification · Nayax, MoMa & revenue · Silkron / Vendron integration status

Need help reaching a cabinet? Follow Android remote-access setup & onsite handoff and day-to-day machine care. Viewing, touch control and recovery after reboot must be qualified on the actual Android/OEM configuration; initial permission approval may need an onsite helper.

Vending machine app — Reyeah "JD" hardware

Kiosk X — vending app Intelliverse cloud

Installs as "Kiosk X" · package com.ruiye.jd · baseUrl → kiosk-x · armeabi-v7a (real RK3288 board)
  • Speaks the /apk/* device protocol against api.kiosk-x.ai — provisioning, product grid, orders, faults, and OTA all on our cloud.
  • A supported, correctly configured board is auto-provisioned on first contact with an empty inventory — no "Device non-existed". Claim the serial in the operator app (or POST /api/v1/machines/register), configure its lanes and reader, and complete a supervised sale before customer operation.
  • Built with the genuine 32-bit libs (real libserialport.so), so it installs on the RK3288 (Android 7.1.2) and drives the real VMC. On-device flow: Machine client guide.
  • v1.2.8 — remote-control cleanup: removes the bundled gotoagent integration. The signed APK advances to versionCode 128; use only the supported Kiosk-X service controls for this cabinet. Physical installation and acceptance remain machine-specific.
  • v1.2.7 — offline card report retry: the offline-order drain now requests with NO_CACHE, so a queued card sale can no longer be discarded against a stale cached success — every report is answered by the cloud or retried. Also carries the v1.2.6 buy-path legibility pass: each state of the purchase flow is drawn distinctly, so a shopper can tell paying from paid from dispensing.
⬇ Kiosk X vending APK
verify before installing: shasum -a 256 reyeah-vending-kioskx-1.2.8-a428bd2a020f.apka428bd2a020f1985a02e7b08b39d84e6f01b64c5626f0b929e8543552e9181cd
Download: v1.2.8 · versionCode 128 · sha256 a428bd2a020f…
Latest recorded rollout: v1.2.5 · versionCode 125 · sha256 f65d2d144bd7…
⚠ this build is staged to 1 named machine, not the whole fleet — the fleet-wide wave is still v1.2.1
⚠ offered to 1 cabinet and adopted by none — nothing has confirmed this build installs
⚠ the latest recorded rollout uses a different artifact than this link — check the rollout status and machine eligibility
saves as reyeah-vending-kioskx-1.2.8-a428bd2a020f.apk

Operator — Android & iPhone Flutter

Your fleet console · installs on an operator's phone or tablet

Android 3.2.6 (47) · Android 7.0 or later · ARM32, ARM64 and x86_64. Signed release and public download verified September 7, 2026; this link pins the verified bytes.

⬇ Operator Android APK (Flutter)
verify before installing: shasum -a 256 operator-x-3.2.6-bd30968aa207.apkbd30968aa2070eb66512fe1d2cb53dbc6ff3ed0b3bfe305ea10e59880db70f3b

Android and Apple 3.2.6 add Screen experiences. Open a machine to stage, publish or disable a hosted app/game and optional phone controller. Requires a compatible ZHZN 1.0.30+ player on Android 9+. Follow the publishing guide.

iPhone / iPad: signed 3.2.6 (44), iOS 15 or later. Apple processing and distribution to the existing internal TestFlight testers completed September 7, 2026. Install through your assigned TestFlight access; this does not create a new tester invitation or establish access for every account. A downloaded App Store IPA cannot be installed directly like an Android APK. No public invitation link is available here.

Use Operator for fleet, product/price editing, refills, sales, alerts and supported machine controls. Since 3.2.2: private screen viewing and acknowledged remote input: a new command waits for its device result and matching fresh screenshot. Update the cabinet agent and provision its credentials first. Use 3.2.2 or later for the private frame format; older phone builds cannot display it. The app retains Edit product & price under each machine aisle, preserves stock during edits, keeps the machine's currency with the form, and checks sign-in persistence. Nayax binding labels now describe transaction attribution accurately.

Android and Apple releases since 3.2.3 include address review and separate installation confirmation. New setup requires both steps before Go live; existing live cabinets keep operating. Apple build 44 and Android build 47 include explicit cash-receipt and captured-payment reviews, alongside the earlier payment/fleet/setup audit repairs. Hardware actions still depend on the cabinet agent and controller. Use the supported web or cabinet service workflow for supervised test vends; Flutter has no test-vend button. See the feature and release matrix before installation.

For every cabinet family, follow address review and installation confirmation. Review the entered address against a map candidate, then separately record the cabinet's observed placement. A map match does not verify postal delivery or prove that the cabinet is installed there. Client availability is recorded in the platform guide.

Android / Apple installation & features Open Operator in browser ↗
Native Android Operator — 3.2.1 (7)

The separate Kotlin / Compose client includes the operator audit repairs. Use its Full console link for expanded setup, MFA and refund actions.

⬇ Native Operator APK
verify before installing: shasum -a 256 kiosk-x-operator-3.2.1-99fc2f7e043a.apk99fc2f7e043a3bb60cc60593df138b3c9569749316b501856c5cd5c66822afc2

Vending machine app — ZHZN / CSM hardware

ZHZN KioskX — vending app Intelliverse cloud

Installs as "ZHZN KioskX" · package ai.intelliverse.zhzn.kioskx · arm64 + armv7 (ZHZN Android boards)
  • For the newer ZHZN / CSM vending machines (AA…BB serial protocol, Y-lift and spring modules). Speaks the /zhzn/* device gateway on api.kiosk-x.ai — zero-touch registration, remote config, dispatch, dispense reporting, and OTA.
  • Factory setup: provision the cabinet identity, have the authorized administrator arm the intended device, and configure the ZHZN kiosk Home app. Confirm its credential exchange and subsequent cloud contact. Complete manufacturer tests and sign-off, then claim it into the intended operator account, configure and physically fill its slots, verify payments and run first-sale acceptance. An optional companion needs its exact role and private credentials before first launch. The durable spool retries reports and suppresses duplicate dispense IDs; a lost physical board reply still needs tray and payment reconciliation.
  • Kiosk WebView storefront is pushed from the cloud config; until then the built-in status screen shows serial, registration state, and board health.
  • (v1.0.35) / versionCode 36 — manufacturer walkthrough: first installation asks for cabinet identity, shelf and coil counts, slot labels and capacity, fitted hardware, network and physical checks. Setup drafts survive restart. Factory sign-off is required before handover; the receiving operator confirms the physical layout before the first fill. Refill photos and placement review remain available with each refill event in Operator Web.
  • Retained from v1.0.34 — offline custom-product requests: privately save merch, sticker and figurine requests, then retry the same request until the configured commerce service confirms receipt. These are unpaid requests; payment, supplier acceptance and server activation are separate.
  • Retained from v1.0.33: the touchscreen navigation and admin gesture fixes verified in 1.0.32.
  • Retained from v1.0.32 — Back buttons receive taps: the native admin shortcut no longer intercepts shopper touches in the upper-left corner. Authenticated service access remains available.
  • Retained from v1.0.31 — touchscreen navigation: visible Back, Done, Retake and retry controls across native and offline screens. The hosted storefront adds QR exits and retry, product Back, and safe checkout cancellation, including short landscape screens.
  • Retained from v1.0.30 — apps beyond the phone: on Android 9 or later, open an operator-published HTTPS app or game in a separate player with a native Back to kiosk button, a timed session and an optional phone-controller QR. Publish the screen mapping from Operator X 3.2.6 or the Intelliverse SDK publishing preview. A saved mapping is not proof of playback. Validate the physical cabinet before enabling it; vending and payment controls are not exposed to the app. Publish a screen experience.
  • Retained from v1.0.29 — installation and payment recovery: combines the approved remote controls with durable card-session recovery, stock reservation before motor dispatch, payment amount/currency/quantity guards, explicit offline stock reconciliation, stable identity and truthful factory evidence. Public bytes, signer and offline Android launch are verified; physical cabinet, payment, delivery and OTA acceptance still need the real machine. Verified release and exact evidence · Visual installation steps.
  • Remote input safety, retained from v1.0.28: normalized taps work with resized frames; attempted inputs expire and are not replayed. The optional Reyeah companion requires provisioned credentials and working root/service permissions, reports actual input readiness and refuses credential redirects. ZHZN controls its own app; companion device control is conditional. Use Operator 3.2.2 or later and the matching gateway for private images and acknowledged input. Setup and acceptance.
  • Update eligibility, retained from v1.0.27: earlier 1.0.26 builds reused code 27, so a cabinet already on code 27 could ignore the newer binary during OTA checks. This release advances the code and adds a publishing check against the full retained APK history. It contains the current agent behavior from the preceding 1.0.26 build. Android 7.0+; ARM32/ARM64. Exact release evidence and upgrade requirements. Version advancement fixes eligibility; controller compatibility, installer privileges and physical acceptance must still be checked.
  • Historical release notes and migration details

    These are dated engineering notes, not a current fleet-status report. Check the download identity and recorded rollout below.

    • Before 1.0.14. v1.0.2 – v1.0.8 brought up the agent itself: locked kiosk mode, remote reboot and health telemetry, remote screen, per-machine feature flags, self-rescue, the unified Reyeah JD driver, and the photobooth with its bilingual factory test. None of those builds is offered here and none of them changes what to do at a cabinet today, so their detail belongs in the zhzn-vending-kiosk changelog rather than on an install page. v1.0.9 – v1.0.13 are the diagnostic arc that the three builds below conclude, kept to a line each because the question they were each chasing is now answered:
      v1.0.9 – v1.0.13 — the arc that got us here
      • (v1.0.9) the spring board has no init command, so asking it one and hearing nothing was read as a dead cabinet; the driver listens for its unsolicited heartbeat instead, and the factory test records the proven port map.
      • (v1.0.10) OTA was silently dropped on any cabinet that is not device owner — the install now survives pending user action instead of killing the worker thread, checks every 15 min, and keeps a ledger.
      • (v1.0.11) added a USB serial transport for adapter-wired cabinets. It was built for 1A8B08520A50, whose four /dev/ttyUSB* nodes turned out to be one Quectel EC25 LTE modem rather than four adapters — that board is on a native UART, so this transport could never have found it and remains unproven on hardware.
      • (v1.0.12) swept every port at 9600 / 19200 / 38400 / 115200 / 57600 rather than only the configured rate, and excluded the modem from board probing.
      • (v1.0.13) kept a bounded hex sample per probe with the UART's own framing and parity counters, and widened the matrix to 8E1 / 8O1 / 7E1 / 8N2 and 2400 / 4800 — which is what produced the evidence 1.0.14 acted on.
    • The board's length byte was never its length (v1.0.14): the manufacturer's protocol document and SDK arrived, and they contradict what every build so far assumed. The spring board pushes its heartbeat as fifteen bytes — AA 06 03 01, ten lane bytes, BB — carrying a length byte of 06. Our parser read 06 as the total, took six bytes, found a lane byte where the terminator should be, threw the frame away and resynchronised one byte at a time through the rest of it. The heartbeat is the only thing this board ever sends unprompted, so a port could only ever report bytes arriving and nothing framing — identically at every baud rate, because a protocol-logic error does not care how fast it is fed. The vendor's own SDK never trusted that byte either: it overrides it to a hardcoded 15 for this frame type, and emits a seven-byte deliver command labelled 06. Length now comes from the frame's type and symbol, as the manufacturer does it.
      Three more corrections from the same documents. The deliver command goes out byte-for-byte as the vendor writes it. The aisle byte uses the board's fixed stride of ten, not the planogram's column count — on a four-shelf, two-channel cabinet the old packing sent shelf 2 channel 1 as byte 3, which this board reads as shelf 1 channel 3, a different spiral; only shelf 1 agreed, by coincidence. And the wait for the first heartbeat rises to the eight seconds the vendor's own initialisation allows, since a shorter window can only mistake a live board for a dead one.
      The manufacturer's return and warn codes now travel upward in their own field, and "the motor did not turn" is kept apart from "no item was detected": the same refund, but different site visits.
      Read this part carefully, because a claim on this page was wrong. The count in the uploaded sweep note called every port that opened "silent", so a port that was transmitting was counted among the quiet ones — this cabinet's own record reads 6 silent. No cloud record for 1A8B08520A50 has ever confirmed that bytes arrived; the field that would have proved it was not being sent. From 1.0.14 the count means what it says and a transmitting node is named in the headline. Two things follow. This release does not promise a vend — it removes a defect that would have blocked dispensing on a perfectly wired cabinet, which is not the same as proving the cabinet is wired. And /dev/ttyS6 has still never been probed on any run: it refuses to open with errno 13, which is a permission, not a missing device, and the T3568 guide publishes no map from /dev/ttySN to the COM headers — so the hand-written "COM4" label does not license the assumption that the board is on ttyS4. Run the factory test on this build and photograph the board section: it will now say either BYTES ARRIVED but never framed with the hex, or zero bytes from every node. Everything else waits on that one answer.
    • The board step stops concluding a board is absent from what its sensors report (v1.0.15): the heartbeat check required every one of the ten drop-sensor bytes to be 0 or 1. That describes a cabinet with all ten optical eyes fitted; it is not a rule about which bytes are legal, and used as one it threw away real heartbeats. A cabinet with eight eyes unfitted sends AA 06 03 01 01 00 FF FF FF FF FF FF FF FF BB — right type, right direction, right lane count, structurally perfect — and the old clause refused it. This is a deliberate change to a stated contract: a test asserted the old behaviour was correct, on the belief that a text-emitting device could otherwise pass as a board. That belief was measured and found wrong, so the assertion was reversed rather than deleted and the original reasoning kept beside it with the measurement that overturned it. Discrimination is now taken from a field the vendor tabulates — the declared length must be the vendor's 06 or the frame's true total — which scores the same flat zero against noise.
      The board step now says four different things instead of one. A single message used to cover four unrelated situations. It now separates success (the board answered), no_ack (ports opened, the board stayed silent), port_unopenable (no port could be opened, so the board was never asked — not evidence it is dead), and port_held_by_agentthe main vending service is using this port, so the scan declined to run. That is not a fault. Probing it anyway would have changed the line rate under the running service and then reported the silence it had itself caused.
      Running the diagnostic can no longer leave the cabinet's board link misconfigured afterwards. The sweep used to reconfigure the port while the vending service held it and hand it back at the wrong line rate, leaving the board deaf until the app was restarted. It now releases that port for the duration and rebuilds it unconditionally on every exit path, including failure.
      The serial diagnostics now reach telemetry. Time to first byte, the span the bytes arrived over, and the UART's own BREAK and framing counters were being measured and dropped on the floor. They now reach both the factory screen and the report the cloud receives, which is what turns the remaining wiring question into something answerable from a desk instead of a site visit. Two new fields carry the rest: how many messages actually decoded, and — if one decoded and was then refused — which field refused it. Capture retention was also inverted: it kept the largest sample, and a board at the wrong baud produces more bytes than at the right one, so a 15-byte window holding a whole parsed heartbeat lost to 33 oversampled zeroes. Every report showed the one capture that could not contain a frame and hid the one that did. The window that framed a message now wins.
      Spring aisle addressing is corrected on the device, using the control board's numbering stride rather than the planogram's column count. Be clear about what that does not fix: shelves 2–5 will still not dispense after this install. The remaining half is the shelf width configured cloud-side, which is still set to a value this cabinet is not built to. Nobody can set it correctly from here, because it is a physical fact about the cabinet: someone has to open it and count the coils on a single shelf. Shelf 1 will work either way, which is exactly how this stayed hidden.
      1.0.15 is superseded by 1.0.16 below, which is what the button serves. Nothing here was reverted — everything above is still in the build — but the counters this release began reporting were being computed wrongly, which is what 1.0.16 repairs.
    • The sweep stops miscounting what it heard (v1.0.16): superseded by v1.0.17 below This build changes what the cabinet tells you, not what the cabinet does. Read that first, because every heading below is a measurement fix and none of them is a repair. 1.0.15 left the board step able to report four outcomes; this one makes the numbers behind those outcomes mean what a reader assumes they mean.
      Since-boot counters are no longer reported as if they belonged to one listening window. The UART's framing, parity, overrun and BREAK counters are cumulative from boot. They were being read once and printed beside a probe, so a counter that had been climbing for three days read as damage done during a 2.5-second listen — which makes an idle port look violently broken and makes a genuinely noisy one indistinguishable from it. Each probe now reports the difference across its own window.
      A count taken at four times the board's clock is refused rather than averaged in. Oversampling a silent line produces more bytes than a correct rate produces real ones, so the wrong setting could out-score the right one on volume alone. A reading whose implied rate is a multiple of the configured one is now discarded with its reason, instead of being folded into a total that then looks authoritative.
      The configured rate gets the time it deserves. The old sweep spent 2.5 seconds per header on every setting including the configured one — so 9600, the one rate this board can be decoded at, got the least total attention of anything in the matrix, and /dev/ttyS4 had no 9600 row at all. Dwell on the configured rate rises to 7.5 seconds per header, and every port gets a 9600 row.
      One ranked verdict instead of a table to interpret. The report used to hand a technician raw per-port counters and leave the conclusion to them. It now states a single ranked verdict — which port and line setting is most likely carrying the board, and what the evidence for that is — so two people reading the same report reach the same next step.
      /dev/ttyS6's refusal is reported with its mechanism. It has never been probed on any run and it is not probed by this one either. It returns errno 13 because it is the debug console Android holds open, which is a permission and not a missing device; the report now says that, rather than letting a denied node sit in a list of failures that look like absence.
      What this build does not do, stated plainly so nobody installs it expecting a working cabinet. It does not open /dev/ttyS6 — no in-app change can, and none is attempted. It cannot make an electrically wrong link work: if the T3568's RS485-only header is wired to a VMC speaking RS232, every rate and every framing will keep opening cleanly and hearing nothing, and no release will ever change that. A board that does not answer still needs investigation of wiring, firmware, controller configuration and software. This historical diagnostic change does not establish the cause or certify a cabinet.
      For the current APK, use the verified version and signer below. Check the installed package, signer, Android version and controller profile before attempting an in-place update.
      Delete any older ZHZN download before you start. Every build this page has offered since 1.0.16 carries its own per-release name, but the unversioned zhzn-kioskx.apk was linked here until then and has held more than one build, so a copy under that name may still be sitting in Downloads on a cabinet or a laptop. Android will not overwrite an existing download — it saves the new build beside it as zhzn-kioskx-1.apk and leaves the older build under the recognisable name, which is the obvious one to tap. Verify the bytes against the digest printed under the button rather than trusting the filename: the same name has held more than one build today. After installing, the factory screen's apk: line is read from the running build — if it does not say 1.0.35 the install did not take, and a same-version reinstall reports success while changing nothing.
      What to do once it is installed, in order. Restart the main vending service first, so the diagnostic is not merely told the port is busy. Then run the board diagnostic once and read the result three ways.
      Board acknowledged at 9600 on the configured port — the acceptance rule was the whole problem and the hardware is fine, and 9600 is now actually swept on every port, for 7.5s per header rather than 2.5s.
      Still no acknowledgement, but the report names one ranked verdict and the field that refused a decoded message — the heartbeat was always arriving, and we know exactly what rejected it.
      No messages decoded at any setting, with the counters now scoped to each listening window and oversampled readings discarded — the software explanation is exhausted. It needs someone at the cabinet with a multimeter.
    • The cabinet keeps selling when the cloud goes away (v1.0.26): this is the build above Until now a ZHZN cabinet with no network was a dark screen: the storefront, the planogram and the payment path all lived on the other end of a link that had just failed. This build makes offline a mode instead of an outage.
      The storefront and planogram are bundled on the device. An offline store ships inside the APK with a cached planogram, so the shopper still sees products and prices with the cloud unreachable, not a spinner.
      Card sales complete offline over MDB. The vend is run against the cabinet's own MDB cashless reader and the sale is spooled durably on the device; every offline order is minted a local id and reported for reconciliation the moment the cloud is back, exactly once.
      Recovery is automatic and fast. The agent detects the cloud's return on a fast-reconnect edge rather than waiting out a long poll interval, drains the spool, and hands the screen back to the hosted storefront.
      MDB cashless is on by default for Reyeah boards, so a hybrid cabinet takes card payments without a per-machine config push, and the Reyeah flat-aisle numbering is packed correctly — shelf-and-column addressing no longer skips lanes on flat layouts.
      Also carries 1.0.25 (never offered on this page): when the ROM's speech recogniser cannot transcribe, the utterance is recorded and transcribed in the cloud instead of being dropped.
      versionCode 27, sha256 4d96a74d806cc95fc21da9a00a4d9174c3986305dc2a3adce5af1399081c8e96; built and published by CI from main commit dd97fd7d (PR #37) and fetched anonymously from the exact download URL above.
    • The microphone stays open through Google's recognizer handoff (v1.0.24): superseded by v1.0.35 above The Android speech service requests transient-exclusive audio focus when listening starts. Earlier builds held the app's TTS playback focus across that handoff, interpreted the recognizer's expected request as an interruption, and cancelled the microphone before the shopper could speak.
      This release drops playback focus before recognition and lets the recognizer own focus while the microphone is active. The hosted storefront also retries native voice readiness for up to ten seconds on a cold Android boot, so the Talk to shop button does not disappear merely because SpeechRecognizer bound after the page loaded.
      versionCode 25, sha256 2b8276bec1b5b26703b0eb2272c43d1ab51030bbd0989879e74cf46189fa47f2; published and fetched anonymously from the exact download URL above.
    • Every cashless port is opened before Credit card is greyed (v1.0.21): superseded by v1.0.24 above A reader on the second node used to read as no reader at all. The sweep stopped at the first MDB bridge that answered, so a coin-only UART — or a Quectel modem on /dev/ttyUSB0 — ended the search before the node the Nayax was actually on was ever opened. A pinned port was worse: it replaced the candidate list outright, so a cabinet configured to the wrong node looked at exactly one port and reported an empty machine.
      What changed. A bridge with no cashless peripheral is now kept as a fallback and the sweep continues; it returns early only when a card reader answers at MDB 0x10 or 0x60. A pinned port is tried first rather than exclusively. And the candidate list no longer depends on the OS listing being complete: the thirteen nodes a cashless bridge or USB-MDB dongle can sit on are swept regardless, since nodes that do not exist fail the open immediately and cost nothing. The vend board's own port is still excluded outright, never merely deprioritised — a payment probe must not make a dispense fault harder to find.
      Why this build is what makes the greyed tile honest. The cabinet now reports tried (every node it opened) and sweepComplete, and the cloud greys Credit card only on a finished sweep — a partial look leaves the tile offered rather than removing it. Before this build the cabinet could not say whether it had finished, so an absent reader and an unlooked-at port were the same report.
      versionCode 22, built from 0c1a322 on origin/main; the payload is d8cfb4c. Its later fleet waves completed and v1.0.24 above carries this behaviour forward with the microphone fix.
    • An Inactive Nayax reader is asked instead of mistaken for an empty socket (v1.0.20): superseded by v1.0.21 above A card-only MDB reader cannot announce itself before the VMC sends its setup data. Earlier builds only listened, so the new cabinet's present-but-Inactive reader looked exactly like an empty port. This build sends the bounded SETUP CONFIGURATION DATA probe and waits for READER CONFIG DATA without enabling card acceptance.
      The next heartbeat reports the per-port probe array whenever no reader is adopted. asked: true with a non-negative answeredAt proves the reader answered; all ports at answeredAt: -1 prove they were asked and stayed silent; open-error points to access/configuration, and foreign points to the wrong device or protocol.
      Historical diagnostic and recovery release. It originally shipped as a one-cabinet diagnostic canary. That historical wave does not identify what a cabinet runs today. Built from merge commit 1fd0973; versionCode 21; 1022 tests, 0 failures.
    • A card tap can pay for a vend, and is charged the marked price (v1.0.19): historical release; verify recovery eligibility This is the first build in which a shopper can pay at the cabinet with a card. The agent negotiates the MDB cashless session with the Nayax reader itself, asks it to authorise the price on the label, dispenses, and only then captures.
      Capture follows the reported vend result. The source passes dispensing into the sale as a callback. A reported failure releases the authorization. Confirm sensor behavior, actual delivery, capture and failure recovery on the installed controller/reader; software sequencing alone is not evidence of a physical drop.
      The amount charged is the price on the label, by construction. This matters because Default Credit on the reader has to clear the dearest SKU — on cabinet 1A8B08520A50 the range is $5.99–$8.99, so the reader is armed with $10.00, a number larger than anything in the machine. MDB VEND SUCCESS is 13 02 <item> and carries no amount field, so once the vend request has named the price there is no byte left in the sequence that could substitute the reader's default. An approval above the marked price is handed back rather than captured, and never reaches the motor. Three byte-level tests pin exactly this.
      A cabinet that cannot dispense does not get a second way to be paid. The reader is shut when the vend board goes down, including mid-shift — and the shutdown is deferred while a sale is on the bus rather than pushed into the gap between the approval and the capture, which would leave an authorisation standing on a shopper's card.
      Installing this build does not turn card payments on anywhere, and cannot. The shopper-facing tile is off by default on all 74 cabinets and needs four separate things to be true: the cabinet must have reported a live card reader on its MDB bus, an operator must have opted that specific cabinet in, no earlier withdrawal may be standing against its reader, and its board must be up. This is deliberate and it is about real money: Scan & Pay runs on a test Stripe key, while a Nayax tap moves real funds to the operator's merchant account and this cloud cannot reverse a capture the reader makes at the cabinet. A tile that appeared on its own would start taking real cards because a heartbeat changed shape.
      This remains the rollback artifact. versionCode 20, signed with the same certificate as 1.0.15 through 1.0.18, so a cabinet on any of them takes it as an in-place upgrade with no uninstall. Built from 7db3e1a. 1017 unit tests, 0 failures.
      Silent updates require the actual installation privileges. A cabinet without device-owner or privileged-installer access can require Android confirmation on screen. Check the target cabinet's current report; this historical release note cannot establish its permissions today.
    • A paid vend waits as long as the board is allowed to take (v1.0.18): superseded by v1.0.19 above Every build before this one abandoned a sale after 500 ms. The control board's own firmware allows itself twelve seconds to answer a deliver and offers no way to shorten it, so a cabinet turned the coil, gave up a twenty-fourth of the way through the board's permitted time, voided the payment, and told the shopper "this machine could not hand your item over" — while the product landed in the tray. On cabinet 1A8B08520A50 one order was voided three seconds after payment and dispensed anyway, and the machine read 0-of-10 on delivery while answering every board probe cleanly. The probe was answered precisely because the probe path never had the 500 ms cut-off; only the paid vend did.
      What changed. A committed vend now runs to the result timeout and can never be cut off below the board's qualified twelve seconds, whatever configuration says; the probe keeps its own short window so a sweep of dead ports is still quick. A board that stays silent no longer tells a shopper the item did not come out — it sends them to check the collection tray first, because silence from the board is not a denial and the goods may already be there. The two board timings are now remote-configurable, so the next timing question costs a configuration poll rather than a release and a visit. And the frames the board sent, the frame we sent it, and how long we waited now ride the heartbeat, because the last two investigations of this fault had neither.
      If a cabinet is on 1.0.16 or 1.0.17, it has this defect. It is worth the drive.
      versionCode 19, built from d324b90, tagged v1.0.18. Superseded: the button above serves 1.0.35, which contains everything here. Its content-addressed archive stays live for incident reconstruction — zhzn-kioskx-1.0.18-46c373f37b1e.apk. Note that rolling back is a physical act: Android refuses a downgrade over the air, so recovering to it means an uninstall and reinstall at the cabinet.
    • The factory test stops leaving the cabinet mute, and the manufacturer gets a procedure (v1.0.17): superseded by v1.0.18 above The factory test was itself the thing that stopped cabinets selling. Its board sweep took the serial port away from the running agent and handed back a replacement the vend engine never read, so every write afterwards returned errno 9 / EBADF for the rest of the process lifetime — and the cabinet reported boardInit: success the whole time, because a device node had opened. The rebuild succeeded, so the mechanism built to report a failed restore never fired. That test is run on every machine at build time, so this was fleet-wide, and eight failed orders across two days on one cabinet were measuring damage the diagnostic had just caused.
      What changed. The port handle is now permanent and the port behind it replaceable, so the sweep, the boot-time port scan and the self-heal all rebuild something every caller can see. The agent repairs itself on an EBADF instead of beginning an unbounded run of failures that waits for a human — and only on EBADF, which is the one serial fault where retrying unchanged cannot help. boardInit is no longer a boolean: port_unopenable, unframed, no_ack, not_swept and port_held_by_agent are distinct verdicts that travel verbatim, so health can no longer call a board fine on no evidence.
      A missing server endpoint is no longer reported as an unacceptable cabinet. A completed acceptance sign-off used to be answered "this cabinet is not acceptable as it stands" over an HTTP 404 — blaming the cabinet for a server gap — and then the technician's signature was deleted and had to be redrawn. The three cases are now distinct: refused on the merits (400/409/422, the only ending permitted to say the cabinet is at fault, and it always names the failing check), endpoint absent (404/405/501, which clears the cabinet by name and tells the technician to have the server updated), and network or credentials (retryable). The signature is written to the cabinet before the upload is attempted and deleted only on success or a genuine refusal, so it survives a failed attempt — including the unattended retry on the next launch.
      An OTA offer that is not this app is refused. The staged artifact's package name and signing certificate must match the running build, read from PackageManager; an identity that cannot be read is a refusal, not a pass. This matters because GET /apk/getUpgradeVersion is unauthenticated and answers any caller with the Reyeah descriptor — a digest check passes those bytes all the way to the installer, and only a package-name check stops them.
      OTA installs silently where the cabinet is device owner, and every staged build is accounted for. The mechanism was never broken; the install step was waiting for a person at an unattended machine, which from the console is indistinguishable from a wave that never arrived — one cabinet took 1.0.14 eight hours and thirty-nine minutes after the offer. A staged build now either applies or is written down as abandoned with a reason, in a record that outlives both the APK and the process.
      A technician can read the board's own vocabulary, and a shopper never can. A new PIN-gated "Board codes / 主板代码" screen shows a code with the table it came from, the manufacturer's constant name, the meaning, the remedy and the raw frame, bilingual throughout — and distinguishes a live fault from a cleared one, because reading a retracted code as a standing one replaces a working motor. Shoppers get a plain sentence and their money, never a code.
      The cabinet has a voice. A confirmation sound when a product actually lands — fired from the same predicate that decides whether to charge, so it cannot sound for a command merely sent — and an optional ambient bed, off by default, silent 22:00–08:00.
      What this build does not contain, stated so nobody looks for it. There is no add-on fitment declaration and no proximity, lighting, power-bank or ad-screen factory step. That work is written but was still being revised against new hardware evidence — a proximity sensor physically exists and is wired to the J_GPIO1 header, contradicting the vendor documentation that claimed none — and shipping a half-revised version of it in a build a manufacturer signs off on would be worse than shipping without it. Record add-on fitment on paper against the machine serial for now. Whether the edge lighting can be driven by the cabinet at all remains unresolved; do not treat it as either working or absent.
      There is also no dispense, payment or network step in the factory test — the board step proves the board answers, it does not fire a coil.
      Nothing in this release has been exercised on a cabinet. Every claim above rests on 972 passing unit tests, on source, or on a live probe of the gateway. The serial repair in particular is the one most worth confirming on metal, and until it is, the board step still tells the technician to restart the app after the test — and now says why, instead of asserting a restart is required when it should no longer be.

      versionCode 18, built from 0e4f9a0, tagged v1.0.17. Superseded: the button above serves 1.0.35, which contains everything here.
⬇ ZHZN KioskX vending APK
verify before installing: shasum -a 256 zhzn-kioskx-1.0.35-6efcbdecd439.apk6efcbdecd439d1b74e55cd40fcc799910bf9c0453f261a6701d70a3b2fef4b71
Download: v1.0.35 · versionCode 36 · sha256 6efcbdecd439…
Latest recorded rollout: v1.0.35 · versionCode 36 · sha256 6efcbdecd439…
⚠ this build is staged to 2 named machines, not the whole fleet — the fleet-wide wave is still v1.0.21
saves as zhzn-kioskx-1.0.35-6efcbdecd439.apk

Installing on a ZHZN board: adb install -r zhzn-kioskx-1.0.35-6efcbdecd439.apk, set as launcher/home following the guide, then complete configuration and acceptance. Installing from the cabinet's own browser can work too. Separate stale Downloads copies from the selected installer while retaining a verified recovery copy: any file left over from when this page linked the unversioned zhzn-kioskx.apk carries no version in its name, and that one key has held several different builds. Android refuses to overwrite an existing download and saves the new build under a numbered name instead, leaving the older build under the recognisable one. Then verify the bytes rather than the name — shasum -a 256 zhzn-kioskx-1.0.35-6efcbdecd439.apk must print 6efcbdecd439d1b74e55cd40fcc799910bf9c0453f261a6701d70a3b2fef4b71 (v1.0.35, versionCode 36) — because reinstalling a build over itself reports success without changing anything. After installing, the factory-test screen's apk: line is read straight from the running build — if it does not say the version you just downloaded, the install did not take. For a hard kiosk on factory-fresh units also run adb shell dpm set-device-owner ai.intelliverse.zhzn.kioskx/ai.intelliverse.zhzn.admin.KioskDeviceAdmin. Self-updates arrive over the /zhzn/upgrade manifest. Follow the Android installation instructions, manufacturer sign-off and first-install and operator handover.

Installing on a confirmed Reyeah JD cabinet: the hardware build requires 32-bit ARM support. Compare the installed package, version and signer first. For a compatible existing Kiosk-X install, use adb install -r reyeah-vending-kioskx-1.2.8-a428bd2a020f.apk and verify the running version. A factory app signed by a different vendor cannot be replaced in place: preserve configuration and recovery material and follow a planned vendor migration. Do not uninstall it merely to clear Android's signature refusal. Follow the Reyeah installation and operation manual for identity, launcher, service access, payment and acceptance checks.

Migrating a Reyeah JD cabinet from the vendor cloud

Supported Reyeah JD boards pointed at the old vendor backend follow these four stages. The first three are ours and end with the vendor cloud out of your workflow entirely. The fourth is the Nayax card reader, which lives in a different company's account and does not come with the board:

StepWhatHow
1. InstallPut the Kiosk X build on the board Verify the controller and signer, preserve existing configuration, then follow the planned Reyeah migration. The APK installation is followed by claim, configuration and acceptance.
2. ClaimMove the board's serial into your fleet Operator app → Machines → + Register (or POST /api/v1/machines/register)
3. VerifySales appear under your fleet Buy once; the order, payment, and stock decrement show in the operator app
4. Card readerMigrate the Nayax device — a separate account, not part of the board Move the device into your Nayax organisation, re-profile it for this cabinet, confirm the MDB wiring, then run the end-to-end ladder below. Details in the next section

Step 4 in full: migrating the Nayax reader

Steps 1–3 are the vending cloud and they are ours. The reader is not: it is registered in Nayax Core, it authorizes over its own cellular link, and it talks to the cabinet over MDB — three things no APK install or serial claim can touch. On a second-hand cabinet the VPOS is normally still in the previous operator's Nayax organisation, commissioned for their old machine type, which is why a fully migrated board can show a working grid and QR sales while card taps die.

4.xWhatWhere / how
4a. OwnershipGet the device into your Nayax actor Nayax support performs a device move between actors. This is the only step that migrates both profile control and settlement — skip it and taps pay the previous owner
4b. ProfileRe-commission it for this cabinet Machine type off the old category (e.g. Cigarettes); MDB Level 1 Pre-Selection, Transaction Start Method Productnot MDB L3 Always Idle, which ignores the Reyeah VMC's cashless arm; Default Credit above your dearest SKU; then Actions → Update Queue and let the VPOS pull and reboot
4c. WiringProve the reader is on the bus, not just online MDB harness from the VMC to the VPOS on cashless device #1, reader powered from MDB. Cellular "Online" with a green LED proves the modem only — a reader can be online and completely absent from MDB
4d. BindPoint settlement records at this machine POST /api/v1/machines/<serial>/nayax with the reader's Device Number (the long one, leading zero included) — never the short Machine ID
4e. ProveOne real tap, checked at every rung The ladder below. Stop at the first rung that fails; each one has a different owner

End-to-end card verification ladder

Tap the card tile on a real SKU once and walk these in order. The point of the ladder is that "card doesn't work" has six distinct causes with different owners — only rungs 4 and 5 are ours.

#Must happenIf it doesn't
1The VPOS shows the amount while the kiosk still counts down The MDB session never opened — 4b profile or 4c wiring. Kiosk-X reports nayax.cardPath.status = reader_never_authorized
2The tap authorizes on the reader Nayax-side decline / Default Credit below the price (4b), or no cellular
3The VMC reports HavePaid and the product drops MDB reply not reaching the VMC; the app logs Kiosk-X: VMC HavePaid - starting card vend when it does
4The order goes shipped and stock decrements Device /apk/ordersUpdate not landing — ours
5The payment carries a real Nayax transactionId Settlement webhook not arriving or terminal not bound (4d) — ours. A nyx_emu_ id is a cloud-side emulation, not a capture
6The money is in your own Nayax payout report The device is still in someone else's actor — 4a. The account named in the operator app is a mirrored label, not proof of deposit

Complete all six checks. An ok machine status does not prove a bank deposit. Scan & Pay uses a separate payment processor and requires its own verified merchant configuration; it does not depend on a Nayax Core account. See the payment and bank reconciliation manual.

The Nayax card reader does not migrate with the board. The three steps above move provisioning, the grid, orders, faults, OTA and Scan & Pay. The VPOS is registered in Nayax Core, and on a second-hand cabinet it usually stays in the previous operator's Nayax organisation, commissioned for their old machine type. Only that actor can change its MDB profile or where its money lands, so on a fully migrated board card taps can still die (nayax.cardPath.status = reader_never_authorized) — or settle to the previous owner. Fix it by having that actor re-profile the reader, asking Nayax support to move the device into your organisation, fitting a VPOS commissioned under your own account, or skipping Nayax and taking cards through Scan & Pay, which settles to you with no Core dependency. Treat the settlement account shown in the operator app as a label until your first real capture appears in your own Nayax payout report.

The vending link above is an immutable per-build copy (reyeah-vending-kioskx-1.2.8-a428bd2a020f.apk), taken once per release, and every link on this page is now one: the key names the bytes, so no later publish can change what it serves. That is not a refinement — CI overwrites an unversioned path in place, which is what made the v1.2.1 fleet wave undeliverable after a rebuild landed on it.
Current Reyeah CI publishes signed, content-addressed APKs to this CDN. The 1.2.8 release was published by run 33333140314 and anonymously re-fetched and verified before this page selected it. Publishing a file, selecting it on this page and offering it to a cabinet are separate steps. The unversioned aliases can still name older bytes; use the pinned filename and checksum. The Reyeah vending build uses armeabi-v7a. Operator has current Flutter Android/Apple releases and a separate legacy Kotlin/Compose Android client. The operator console is also hosted at operator.kiosk-x.ai.

Every earlier Kiosk-X vending build — for a rollback, or to check what a cabinet ran

The button above is the current release. These are the 9 that came before it, newest first. They share the Kiosk-X signing certificate and target api.kiosk-x.ai. Matching signatures alone do not make an older version installable over a newer one.
Recovery is machine-specific. Android normally rejects a lower versionCode. The -d flag is not a reliable downgrade path for a production release. Do not uninstall to bypass that refusal: it deletes the machine's app data and provisioning. Preserve configuration and use the supplier-approved recovery procedure; prefer a tested forward fix.
Do not point an OTA wave at one of these. They are here for a technician standing at a cabinet. 1.2.0 and older predate the client-side OTA integrity gate, so they will install an update without verifying its digest; 1.2.2 waits for Nayax HavePaid but inverts the branches (charge with no drop); everything below 1.2.2 vends on createOrder instead of waiting. 1.2.3 is the first card-safe build.

Two of these were rebuilt in place under one version number before content-addressed keys existed, so the version alone does not name a binary for 1.1.0 or 1.0.98 — the digest does. Each key above holds the last build of its line, recovered from the workflow run that produced it and re-read from this CDN anonymously before being linked. The per-build provenance, including the source commit, is in the PUBLISHED ledger this page renders from.

These same S3 artifacts drive over-the-air upgrades: publish one to the fleet with POST /api/v1/rollouts and each kiosk installs it on its next getUpgradeVersion poll — full walkthrough in the APK rollouts guide.


Not a release — vendor reference material

Reyeah factory build for a different operatordo not install this on any Kiosk-X machine

Everything above this line is ours. This is not: it is the factory Reyeah "JD" app as provisioned for another company, pointed at their cloud, signed with the Guangzhou manufacturer's key. It is kept here as a reference copy — for comparing against our own build, and for nothing else. It was analysed and found clean, so the danger is not malware, it is mistaken identity: it declares the same package name as the Kiosk-X vending app, com.ruiye.jd.

What happens if it is installed on a Kiosk-X cabinet

Looking for the Kiosk-X vending build?

It is the Kiosk X — vending app card at the top of this page: reyeah-vending-kioskx-1.2.8-a428bd2a020f.apk. That is the one to install on a Kiosk-X cabinet.

It is not a newer build than ours — it is the same one, unmodified

Our Reyeah 1.2.8 is this upstream build, re-pointed at our cloud and re-signed with our key: both carry the identical manufacturer build stamp reyeah-jd-v1.0.98_260626_c13ee8c_beta.apk. So there is nothing in this file to gain and nothing in it we are missing.
1.0.98 is the manufacturer's numbering, not ours. Do not read it as a later release than 1.2.8 because the last digits are larger — the two numbers are counted by different people, and the Kiosk-X build is the newer of the two.

How to tell the two files apart

This vendor reference fileThe Kiosk-X vending build
Packagecom.ruiye.jdidentical, which is the whole problemcom.ruiye.jd
Installs as"Vapetm""Kiosk X"
VersionversionName 1.0.98, versionCode 3 versionName 1.2.8, versionCode 128
Signed byC=CN, ST=guangdong, L=guangzhou, O=ruiye, OU=ruiye, CN=lijinshou — the manufacturer's own self-signed key (d2581b30…) CN=Kiosk-X Vending (d76b4ae5…)
Talks tosapi.vapevendingsoftware.com + mq.vapevendingsoftware.com api.kiosk-x.ai
Filenamereyeah-jd-vapetm-1.0.98-d532bb53c9aa.apk reyeah-vending-kioskx-1.2.8-a428bd2a020f.apk

Full write-up, including the security review done before it was hosted and a field-by-field comparison against our build: README.md in the same folder.

Reference copy, not an install — show the download link

For analysis and comparison off a cabinet. Do not put this on a Kiosk-X machine, and do not hand it to a technician as a build to install. It is kept outside the downloads/ folder the releases live in, in downloads/vendor-reference/, alongside the note that explains why.
reyeah-jd-vapetm-1.0.98-d532bb53c9aa.apk  ·  README.md — read this first

these bytes are d532bb53c9aa52e4e3ab0a8a4aa6fb12283bdb0f8d314082634eaadb487bbc0e (shasum -a 256 reyeah-jd-vapetm-1.0.98-d532bb53c9aa.apk) — to confirm which file you are holding. Not an install check: this build must not be installed on a Kiosk-X machine.