Appearance
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/bootstrapAuthentication: 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
| Field | Type | Required | Description |
|---|---|---|---|
pin | string | Yes | 6-character provisioning PIN |
device_info | object | Yes | Hardware and software details |
device_info.hardware_id | string | Yes | Unique hardware identifier |
device_info.model | string | No | Kiosk hardware model |
device_info.os_version | string | No | Operating system version |
device_info.app_version | string | No | Application version |
device_info.screen_resolution | string | No | Display 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
| Field | Type | Description |
|---|---|---|
kiosk | object | Kiosk identification |
kiosk.hashkey | string | Unique kiosk identifier |
kiosk.name | string | Display name |
kiosk.location_hashkey | string | Associated location |
kiosk.status | string | active, inactive, maintenance |
location | object | Location details |
concepts | array | Available restaurant concepts |
config | object | Kiosk configuration |
session_token | string | Session token for this bootstrap |
heartbeat_interval_seconds | integer | How often to send heartbeats |
Configuration Fields
| Field | Type | Description |
|---|---|---|
idle_timeout_seconds | integer | Return to attract screen after inactivity |
order_types | array | Allowed order types on this kiosk |
payment_methods | array | Available payment methods |
receipt_options | array | Receipt delivery options |
ui_theme | string | light or dark |
language | string | Default language code |
accessibility_mode | boolean | Enable accessibility features |
Send Heartbeat
Maintain kiosk connectivity and report status.
POST /v1/ecommerce/kiosk/{kiosk}/heartbeatAuthentication: OAuth Token
Scope: kitchenclick:kiosks.heartbeat
Rate Limit: 60/min per kiosk
Path Parameters
| Parameter | Type | Description |
|---|---|---|
kiosk | string | Kiosk 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
| Field | Type | Required | Description |
|---|---|---|---|
session_token | string | Yes | Token from bootstrap |
status | string | Yes | operational, degraded, error |
metrics | object | No | Performance metrics |
hardware_status | object | No | Peripheral device status |
Hardware Status Values
| Value | Description |
|---|---|
ready | Device operational |
warning | Device has issues but functional |
error | Device not functioning |
disconnected | Device 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
| Command | Description |
|---|---|
reload_menus | Fetch fresh menu data |
update_config | Apply new configuration |
restart | Restart the kiosk application |
shutdown | Shut down for maintenance |
display_message | Show 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
- PIN Security - PINs are single-use and expire after successful bootstrap
- Session Tokens - Rotate session tokens periodically
- Hardware ID - Use tamper-resistant hardware identifiers
- Network Security - Use HTTPS and certificate pinning
- Physical Security - Secure kiosks in locked enclosures
Changelog
| Date | Change |
|---|---|
| 2026-01-15 | Initial publication. |