by Intelliverse X

Free-product program (QuestX)

Turn foot traffic into engagement: the kiosk's attract loop advertises "finish tasks / win the game → free product", the shopper scans the QR on the machine, earns a one-time code on their phone, and the code vends a free product from that exact machine — booked as a $0 promo order the operator can account for.

sequenceDiagram participant S as Shopper (phone) participant K as Kiosk-X cloud participant Q as QuestX participant M as Machine (Reyeah APK) M->>M: attract loop shows house creative
(from /apk/getAd) S->>K: scan QR → GET /play?machine=NNN K-->>S: offer page (eligible products, quest list) S->>Q: complete quests / win game Q->>K: POST /play/api/questx/completion (x-kiosk-secret) K-->>S: one-time code KX-XXXXXXXX (machine-bound) S->>M: enters code at the discount screen M->>K: POST /apk/validDiscountCode K-->>M: data "0" → zero-price vend M->>K: POST /apk/createOrder K-->>M: $0 promo order (stock −1, retail value kept)

Shopper flow

  1. QR on the kiosk encodes https://api.kiosk-x.ai/play?machine=<machineNo>.
  2. The /play page shows the offer, the machine's eligible reward products (in-stock aisles priced within the program's maxPrice cap), and two ways to earn:
  3. Real QuestX quests — hand-off to the QuestX offerwall (https://quest-x.ai/quests/offerwall?iv=1&machine=<machineNo>). When QuestX is reachable the page lists the machine's live quest feed (server-side proxy of GET /api/kiosk/{kioskId}/cache with x-kiosk-secret). QuestX-claimed vouchers work at the kiosk exactly as before (the original bridge).
  4. Demo task set / demo game — a clearly-labeled sandbox path behind the same interface (KIOSKX_PLAY_DEMO_TASKS, default on). Task progression is tracked server-side; the credit only mints when every task is done.
  5. Completion mints a one-time code (KX-XXXXXXXX), bound to that machine, valid for KIOSKX_PLAY_CREDIT_TTL (24 h default).

Redemption (device protocol)

The shopper enters the code in the vending UI's discount screen (Reyeah DiscountPOP). The firmware calls POST /apk/validDiscountCode:

Either success returns data: "0" (zero payable) and arms a promo entitlement on the machine: the next createOrder books ONE unit of the first cap-eligible item as a $0 promo sale — stock decremented, revenue 0, retail value and attribution (code, source, program) kept on the order. The age gate still applies to age-restricted vends.

Operator control & accounting

What How
Enable/disable per machine PUT /api/v1/machines/{machineNo}/free-product-program {"enabled": true, "maxPrice": 10, "fundingSourceId": "<fs-uuid>"} (app/routes/play.py:393-434)
Inspect program GET /api/v1/machines/{machineNo}/free-product-program (app/routes/play.py:383-390)

Enabling requires a funding source

A program can only be active while it references a funding source. STORE.set_free_product_program raises ValueError("A funding source is required to activate the free-product program (spec §4: sponsor, operator budget, or platform)") when enabled: true is requested with no fundingSourceId — either omitted on a machine that never had one, or explicitly cleared while enabled (app/store.py:5290-5300). The route turns that into HTTP 400 with the same message (app/routes/play.py:421-426).

fundingSourceId references a FundingSource in the qr-studio registry (sponsor_brand | operator_budget | platform; request model ProgramUpdateRequest, app/routes/play.py:367-373). Create one there (POST /v1/funding-sources) and pass its id. When QR_STUDIO_BASE_URL is unset (sandbox/dev) the id is still required but the remote availability check is skipped (app/config.py:527-543, app/funding.py). The seeded demo machine uses KIOSKX_PLAY_DEMO_FUNDING_SOURCE_ID (default fs-demo-operator-promo). Optional campaignId rides alongside for attribution.

Enabling auto-registers the machine in QuestX

When the PUT leaves the program enabled, the route calls questx_link.ensure_kiosk_registered(machineNo) (app/routes/play.py:429-430), which POSTs QuestX POST /api/kiosk/register with x-kiosk-secret (app/questx_link.py:83-125). QuestX upserts on hardwareId = machine number, so re-enabling refreshes the row rather than duplicating it. Before this, the kiosk row had to be created by hand in QuestX admin, and a freshly enabled machine showed "This machine isn't linked to QuestX yet" (seen on 866903013700033, 2026-09-03).

Failure model — enrichment, never a blocker (questx_link.py:17-22): the program still enables when QuestX is unconfigured or down. The one refusal: a machine with no real lat/lng is not registered (QuestX geo-fences quest check-ins), reported as machine-has-no-coordinates (registration_payload, :55-61, 94-99). Set the machine's coordinates in Operator X first, then PUT again.

Response shape

GET returns the normalized program (STORE.free_product_program, app/store.py:5254-5267):

{"machineNo": "…", "enabled": true, "maxPrice": 10.0,
 "fundingSourceId": "fs-…", "campaignId": null}

PUT returns the same object plus a questx block when the program is enabled (app/routes/play.py:427-434; shape from ensure_kiosk_registered, questx_link.py:86-88):

{"…": "…",
 "questx": {"linked": true, "kioskId": "qx_…", "reason": null}}

reason on failure is one of questx-unconfigured, machine-has-no-coordinates, questx-unreachable, questx-refused:<status>, questx-refused:no-id (questx_link.py:36-39, 111-122). A disabled program carries no questx block. | Giveaway report | GET /api/v1/play/report — redemption counts, retail value given away, per-machine breakdown, credit funnel | | Orders summary | GET /api/v1/orders/summary now carries a promoGiveaways block; giveaways are excluded from paid-revenue buckets |

Enabling a machine also injects two house creatives ("Play & win" / "Finish tasks", 1080×1920, S3-hosted) into its /apk/getAd attract loop — after sold inventory (direct-sold + programmatic), ahead of the generic fixture screensaver.

QuestX integration status

Env

Variable Default Purpose
KIOSKX_PLAY_CREDIT_TTL 86400 Credit code lifetime (seconds)
KIOSKX_PLAY_ENTITLEMENT_TTL 600 validated-code → createOrder window
KIOSKX_PLAY_MAX_PRICE 20.0 default reward price cap
KIOSKX_PLAY_DEMO_TASKS true demo task set on the /play page
KIOSKX_PLAY_DEMO_FUNDING_SOURCE_ID fs-demo-operator-promo funding source the seeded demo machine's program references (app/config.py:541-543)
QUESTX_WEB_BASE https://quest-x.ai shopper hand-off links
QUESTX_API_BASE (unset → link reports questx-unconfigured) QuestX API for voucher consume, quest feed, and POST /api/kiosk/register (app/config.py:489)
QUESTX_KIOSK_DEVICE_SECRET x-kiosk-secret for all of the above (app/config.py:490-491)
QR_STUDIO_BASE_URL / QR_STUDIO_INTERNAL_TOKEN (unset → local funding rules only) FundingSource registry + redemption cost ledger (app/config.py:533-535)

See also: Redemption protocol (the voucher path), Screen ads & sponsorships (attract-loop economics).