Skip to content

Kiosks

The Kiosk API enables self-service ordering kiosks to initialize, authenticate, and maintain connectivity.

Kiosk Lifecycle

┌─────────────────────────────────────────────────────────────┐
│                     KIOSK LIFECYCLE                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌──────────┐    ┌──────────┐    ┌──────────────────────┐ │
│   │  Setup   │───>│Bootstrap │───>│  Operational Mode    │ │
│   │  (PIN)   │    │  (API)   │    │  (Heartbeat + Orders)│ │
│   └──────────┘    └──────────┘    └──────────────────────┘ │
│                                             │               │
│                                             ▼               │
│                                    ┌──────────────┐        │
│                                    │   Offline    │        │
│                                    │  (Reconnect) │        │
│                                    └──────────────┘        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Bootstrap Kiosk

Initialize a kiosk using its provisioning PIN. This endpoint authenticates the kiosk and returns its configuration.

POST /v1/ecommerce/kiosk/bootstrap

Authentication: OAuth Token

Scope: kitchenclick:kiosks.bootstrap

Rate Limit: 200/min per client

Request Body

json
{
  "pin": "ABC123",
  "device_info": {
    "hardware_id": "kiosk-001-abc123",
    "model": "KitchenClick K1",
    "os_version": "1.2.0",
    "app_version": "2.1.0",
    "screen_resolution": "1920x1080"
  }
}

Request Fields

FieldTypeRequiredDescription
pinstringYes6-character provisioning PIN
device_infoobjectYesHardware and software details
device_info.hardware_idstringYesUnique hardware identifier
device_info.modelstringNoKiosk hardware model
device_info.os_versionstringNoOperating system version
device_info.app_versionstringNoApplication version
device_info.screen_resolutionstringNoDisplay resolution

Response

json
{
  "status": "success",
  "data": {
    "kiosk": {
      "hashkey": "ksk_00000a1B2c3D4e5",
      "name": "Kiosk 1 - Lobby",
      "location_hashkey": "loc_00000k1L2m3N4o5",
      "status": "active"
    },
    "location": {
      "hashkey": "loc_00000k1L2m3N4o5",
      "name": "Downtown Austin",
      "timezone": "America/Chicago"
    },
    "concepts": [
      {
        "hashkey": "con_00000f5G6h7I8j9",
        "name": "Bistro Kitchen",
        "logo_url": "https://cdn.example.com/logos/bistro.png"
      }
    ],
    "config": {
      "idle_timeout_seconds": 120,
      "order_types": ["dine-in", "takeout"],
      "payment_methods": ["credit_card", "cash"],
      "receipt_options": ["print", "email", "sms"],
      "ui_theme": "light",
      "language": "en",
      "accessibility_mode": false
    },
    "session_token": "kst_xxxxx",
    "heartbeat_interval_seconds": 60
  }
}

Response Fields

FieldTypeDescription
kioskobjectKiosk identification
kiosk.hashkeystringUnique kiosk identifier
kiosk.namestringDisplay name
kiosk.location_hashkeystringAssociated location
kiosk.statusstringactive, inactive, maintenance
locationobjectLocation details
conceptsarrayAvailable restaurant concepts
configobjectKiosk configuration
session_tokenstringSession token for this bootstrap
heartbeat_interval_secondsintegerHow often to send heartbeats

Configuration Fields

FieldTypeDescription
idle_timeout_secondsintegerReturn to attract screen after inactivity
order_typesarrayAllowed order types on this kiosk
payment_methodsarrayAvailable payment methods
receipt_optionsarrayReceipt delivery options
ui_themestringlight or dark
languagestringDefault language code
accessibility_modebooleanEnable accessibility features

Send Heartbeat

Maintain kiosk connectivity and report status.

POST /v1/ecommerce/kiosk/{kiosk}/heartbeat

Authentication: OAuth Token

Scope: kitchenclick:kiosks.heartbeat

Rate Limit: 60/min per kiosk

Path Parameters

ParameterTypeDescription
kioskstringKiosk hashkey

Request Body

json
{
  "session_token": "kst_xxxxx",
  "status": "operational",
  "metrics": {
    "orders_since_last_heartbeat": 5,
    "average_order_time_seconds": 180,
    "error_count": 0,
    "uptime_seconds": 28800
  },
  "hardware_status": {
    "printer": "ready",
    "card_reader": "ready",
    "touchscreen": "ready"
  }
}

Request Fields

FieldTypeRequiredDescription
session_tokenstringYesToken from bootstrap
statusstringYesoperational, degraded, error
metricsobjectNoPerformance metrics
hardware_statusobjectNoPeripheral device status

Hardware Status Values

ValueDescription
readyDevice operational
warningDevice has issues but functional
errorDevice not functioning
disconnectedDevice not connected

Response

json
{
  "status": "success",
  "data": {
    "acknowledged": true,
    "server_time": "2024-01-15T14:30:00Z",
    "commands": [],
    "config_updated": false
  }
}

Server Commands

The heartbeat response may include commands for the kiosk to execute:

json
{
  "commands": [
    {
      "type": "reload_menus",
      "priority": "normal"
    },
    {
      "type": "update_config",
      "priority": "high",
      "config": {
        "idle_timeout_seconds": 90
      }
    },
    {
      "type": "restart",
      "priority": "low",
      "scheduled_at": "2024-01-16T02:00:00Z"
    }
  ]
}

Command Types

CommandDescription
reload_menusFetch fresh menu data
update_configApply new configuration
restartRestart the kiosk application
shutdownShut down for maintenance
display_messageShow message to customers

Error Responses

Invalid PIN

json
{
  "status": "error",
  "message": "Invalid PIN",
  "errors": {
    "pin": ["The provided PIN is invalid or has expired"]
  }
}

Kiosk Disabled

json
{
  "status": "error",
  "message": "Kiosk is disabled",
  "errors": {
    "kiosk": ["This kiosk has been disabled by an administrator"]
  }
}

Session Expired

json
{
  "status": "error",
  "message": "Session expired",
  "errors": {
    "session_token": ["Session has expired. Please re-bootstrap the kiosk."]
  }
}

Implementation Example

javascript
class KioskManager {
  constructor(apiClient) {
    this.apiClient = apiClient;
    this.config = null;
    this.sessionToken = null;
    this.heartbeatInterval = null;
  }

  async bootstrap(pin) {
    const deviceInfo = await this.getDeviceInfo();

    const response = await this.apiClient.post('/v1/ecommerce/kiosk/bootstrap', {
      pin,
      device_info: deviceInfo,
    });

    if (response.status !== 'success') {
      throw new Error(response.message);
    }

    const { data } = response;
    this.config = data.config;
    this.sessionToken = data.session_token;
    this.kioskHashkey = data.kiosk.hashkey;

    // Start heartbeat
    this.startHeartbeat(data.heartbeat_interval_seconds);

    return data;
  }

  startHeartbeat(intervalSeconds) {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
    }

    this.heartbeatInterval = setInterval(
      () => this.sendHeartbeat(),
      intervalSeconds * 1000
    );

    // Send initial heartbeat
    this.sendHeartbeat();
  }

  async sendHeartbeat() {
    try {
      const response = await this.apiClient.post(
        `/v1/ecommerce/kiosk/${this.kioskHashkey}/heartbeat`,
        {
          session_token: this.sessionToken,
          status: this.getOperationalStatus(),
          metrics: this.collectMetrics(),
          hardware_status: await this.checkHardware(),
        }
      );

      if (response.data.commands?.length > 0) {
        await this.processCommands(response.data.commands);
      }

      if (response.data.config_updated) {
        await this.reloadConfig();
      }
    } catch (error) {
      console.error('Heartbeat failed:', error);

      if (error.status === 401) {
        // Session expired, need to re-bootstrap
        this.handleSessionExpired();
      }
    }
  }

  async processCommands(commands) {
    for (const command of commands) {
      switch (command.type) {
        case 'reload_menus':
          await this.menuManager.reload();
          break;
        case 'update_config':
          this.config = { ...this.config, ...command.config };
          break;
        case 'restart':
          if (command.scheduled_at) {
            this.scheduleRestart(command.scheduled_at);
          } else {
            this.restart();
          }
          break;
        case 'display_message':
          this.showCustomerMessage(command.message);
          break;
      }
    }
  }

  getDeviceInfo() {
    return {
      hardware_id: this.getHardwareId(),
      model: 'KitchenClick K1',
      os_version: this.getOsVersion(),
      app_version: APP_VERSION,
      screen_resolution: `${screen.width}x${screen.height}`,
    };
  }

  getOperationalStatus() {
    const hardware = this.checkHardwareSync();
    const hasErrors = Object.values(hardware).some(s => s === 'error');
    const hasWarnings = Object.values(hardware).some(s => s === 'warning');

    if (hasErrors) return 'error';
    if (hasWarnings) return 'degraded';
    return 'operational';
  }

  collectMetrics() {
    return {
      orders_since_last_heartbeat: this.orderCount,
      average_order_time_seconds: this.avgOrderTime,
      error_count: this.errorCount,
      uptime_seconds: this.getUptime(),
    };
  }

  async checkHardware() {
    return {
      printer: await this.printerService.getStatus(),
      card_reader: await this.cardReaderService.getStatus(),
      touchscreen: 'ready',
    };
  }

  handleSessionExpired() {
    // Stop heartbeat
    clearInterval(this.heartbeatInterval);

    // Show re-initialization screen
    this.showBootstrapScreen();
  }
}

// Usage
const kioskManager = new KioskManager(apiClient);

// On kiosk startup, show PIN entry
const pin = await promptForPin();
const config = await kioskManager.bootstrap(pin);

// Kiosk is now operational
console.log(`Kiosk "${config.kiosk.name}" ready at ${config.location.name}`);

Security Considerations

  1. PIN Security - PINs are single-use and expire after successful bootstrap
  2. Session Tokens - Rotate session tokens periodically
  3. Hardware ID - Use tamper-resistant hardware identifiers
  4. Network Security - Use HTTPS and certificate pinning
  5. Physical Security - Secure kiosks in locked enclosures

Changelog
DateChange
2026-01-15Initial publication.

ShopHero CommerceCore Platform