by Intelliverse X

Machines

List, register, and inspect your vending machines. Machines are identified by their machineNo — the hardware serial printed on the device (e.g. 866903013700011).

Machine acquisition costs and depreciation are recorded separately from the location and operating details on this page.

Use the address review and installation workflow for a reviewed numbered address and an explicit cabinet-location record. It applies equally to ZHZN, Reyeah and VC EL fleet records. Legacy labels, GPS pins and geocodeStatus: resolved do not establish a reviewed address, postal deliverability or physical installation.

Register a machine

POST /api/v1/machines/register — scope machines:write

The cloud half of physical installation — and installation order doesn't matter:

Body field Type Notes
machineNo string required — board serial, 8–32 alphanumeric chars
name string optional display name
location string optional
lat / lng string optional
operatorEmail string admin keys only: which fleet to register into
curl -X POST "https://api.kiosk-x.ai/api/v1/machines/register" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"machineNo":"866903013700099","name":"Gym Lobby","location":"24h Fitness, Queens"}'

409 is returned only if the serial already belongs to another operator. The response includes the machine's Nayax terminal binding, created automatically under your merchant account.

List machines

GET /api/v1/machines — scope machines:read

Query param Type Notes
operatorEmail string Admin keys only: filter by operator
status string online or offline
page int 1-based, default 1
size int default 20, max 100
curl "https://api.kiosk-x.ai/api/v1/machines?status=online" \
  -H "X-API-Key: vtm_e08725ea09876af65f431c6a3742d7c0"
{
  "code": 200, "message": "success",
  "data": {
    "page": 1, "size": 20, "total": 1,
    "records": [{
      "id": "8b7df143d91c716ecfa5fc1730022f6b",
      "machineNo": "866903013700011",
      "name": "Ryan Test",
      "status": "online",
      "lastSeen": "2026-08-10T11:49:00.000+0000",
      "location": "Warehouse A",
      "lat": "40.7128", "lng": "-74.0060",
      "geo": {"lat": 40.7128, "lng": -74.0060, "address": "Warehouse A", "placeId": null},
      "notes": null,
      "expireDate": "2027-08-10T12:00:00.000+0000",
      "rentState": 1
    }]
  }
}

geo is the structured map pin for fleet-map clients (Operator X): parsed float coordinates, the location label as address, and an OSM-derived placeId (osm:<type><id>) when the pin was set by server-side geocoding. The flat location/lat/lng strings stay for existing clients.

Admin keys see every operator's machines and each record includes operatorEmail.

Get machine by number

GET /api/v1/machines/{machineNo} — scope machines:read

Returns a single machine including last heartbeat (lastSeen). A machine that doesn't exist or is not owned by your account returns 404 Machine not found.

Update machine

PATCH /api/v1/machines/{machineNo} — scope machines:write (PUT is an alias with identical semantics)

Partial update: only the fields you provide change.

Body field Type Constraint
name string ≤ 100 chars
notes string ≤ 255 chars
location string ≤ 255 chars — display label (venue name / address)
lat, lng string decimal degrees, validated ±90/±180
address string ≤ 255 chars — geocoded server-side into lat/lng + placeId
placeId string ≤ 128 chars — place identifier (the geocoder sets osm:<type><id>)
timeZone string ≤ 64 chars — IANA zone of the venue, e.g. America/Chicago. Refused with 400 if the tz database does not know it
curl -X PATCH https://api.kiosk-x.ai/api/v1/machines/866903013700011 \
  -H "X-API-Key: vtm_e08725ea09876af65f431c6a3742d7c0" \
  -H "Content-Type: application/json" \
  -d '{"name": "Lobby Machine", "location": "Building 4 lobby"}'

Setting the fleet-map pin

Two paths, both through this endpoint:

Both coordinates are required together; nonfinite/out-of-range values and 0,0 are refused. Sending address and coordinates together is refused. Changing a location label clears the previous pin. Changing GPS coordinates clears the previous address label and geocoder evidence. Both invalidate address and installation confirmation. A delayed geocoder result cannot overwrite a newer placement or a different owner's record.

The address path needs the deployment's geocoder URL — KIOSKX_GEOCODER_URL (the in-cluster kiosk-x-photon Service in production). Explicit lat/lng always works with no geocoder. See the Geo platform guide for the full self-hosted stack (reverse geocoding, POI search, tiles).

An address that did not resolve still returns 200. Read geo.geocodeStatus, not the status code. The write is never failed over a geocoding problem: the operator has the cabinet in the van either way, and a venue that cannot be recorded at all is worse than one recorded without coordinates. So the machine is saved with the address as typed, lat/lng/placeId set to null — never 0,0 and never a stale pin from wherever it used to be — and the outcome stamped on the record (app/machine_geocode.py:120-170).

geo.geocodeStatus What happened Whose problem
resolved The address is on the map, with canonical coordinates and a placeId Nobody's
not_found The geocoder answered and does not know this address The operator's — fix the address, then POST /api/v1/machines/{machineNo}/geocode/retry with a corrected address
provider_error The geocoder was unreachable, timed out, or replied with something unusable Ours. The address may be fine; retryable unattended
skipped This deployment has no geocoder configured at all. geocodeDetail reads geocoding is not configured on this deployment Deployment config — nothing about the address is wrong, and nothing on site can fix it
absent Never geocoded: a machine pinned from device GPS, or one predating this field. Provenance is not guessed from the mere presence of coordinates Nobody's

geo.geocodeRetryable is true for provider_error and skipped only — both mean the address was never actually judged, so a later attempt can still succeed. Re-asking the same geocoder the same question after a not_found cannot produce a different answer, so that retry is refused with 409 unless you pass a corrected address (app/machine_geocode.py:36-49).

The consequence worth knowing before you automate against this: a 200 with geocodeStatus: "skipped" is a machine that was not placed on the map. If your client treats the status code as the verdict, an unconfigured geocoder looks exactly like a successful placement, and a whole fleet lands with no pins and no error anywhere. Assert on geocodeStatus == "resolved" when the pin matters.

Setting the venue's time zone

timeZone is only needed where the placement cannot settle it on its own — a state that straddles a boundary, or a country with more than one zone. It outranks every inference the backend makes, so a typo is the one way to put a cabinet's on-screen clock confidently in the wrong hour; unknown names are refused rather than resolved to UTC. See the cabinet clock guide for how the zone is otherwise derived and why a cabinet sometimes shows no clock at all.

Every write is recorded in the audit log (GET /sandbox/audit, admin only).