Skip to main content

Building a Kiosk App

Build a dedicated scanning and check-in/check-out application using the Stockaj Kiosk API.

Overview

A kiosk app is a dedicated station — typically a tablet or desktop at a warehouse entrance — where users scan QR codes to quickly identify items, check rental status, and perform actions.

Prerequisites

  • Stockaj account with Starter plan or above (API access required)
  • API token with kiosk:scan ability
  • USB barcode/QR reader in HID (keyboard) mode, or a device with camera

Architecture

┌──────────────────────────────┐
│ Kiosk Device │
│ ┌────────────────────────┐ │
│ │ Camera / Scanner │ │
│ │ (reads QR code) │ │
│ └──────────┬─────────────┘ │
│ │ │
│ ┌──────────▼─────────────┐ │
│ │ Kiosk App │ │
│ │ (web or native) │ │
│ └──────────┬─────────────┘ │
└─────────────┼────────────────┘
│ HTTPS

┌──────────────────────────────┐
│ Stockaj API │
│ POST /api/v2/kiosk/scan │
│ GET /api/v2/kiosk/search │
│ PATCH /api/v2/rentals/{id} │
└──────────────────────────────┘

Step 1: Set Up Authentication

Create a dedicated API token for your kiosk:

  1. Go to Settings → API Tokens.
  2. Create a token named kiosk-warehouse-main (or similar).
  3. Select abilities:
    • kiosk:scan — Required for scan and search
    • rents:read — View rental details
    • rents:update — Update rental status (for check-in/check-out)
    • items:read — View item details

Step 2: Implement QR/Barcode Scanning

The recommended approach is a USB barcode/QR reader in HID (keyboard) mode. These devices send scanned codes as rapid keystrokes ending with Enter, which you can capture with a keyboard listener:

let buffer = '';
let timeout = null;
const SCAN_TIMEOUT_MS = 100; // Keystrokes from a scanner arrive within ~100ms

document.addEventListener('keydown', (event) => {
if (event.key === 'Enter' && buffer.length > 0) {
// Complete scan received
const code = buffer;
buffer = '';
clearTimeout(timeout);
handleScan(code);
return;
}

// Accumulate characters, auto-clear if typing is too slow (human input)
buffer += event.key;
clearTimeout(timeout);
timeout = setTimeout(() => { buffer = ''; }, SCAN_TIMEOUT_MS);
});

async function handleScan(code) {
const result = await scanCode(code);
displayResult(result);
}

Alternatively, for camera-based scanning, use a library like html5-qrcode.

Step 3: Call the Scan API

The scan endpoint accepts several code formats:

  • STK format (Stockaj QR codes): STK:1:i:<uuid> (item) or STK:1:v:<uuid> (variant)
  • Raw UUID: 019c77b8-c625-716e-8dd8-3a3a13ae2a1c — matches items or variants by UUID
  • Raw code: ITM-ABC123 or VAR-XYZ789 — matches items or variants by their code field
const API_BASE = 'https://app.stockaj.io/api/v2';
const TOKEN = process.env.STOCKAJ_KIOSK_TOKEN;

async function scanCode(code) {
const response = await fetch(`${API_BASE}/kiosk/scan`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ code }),
});

return response.json();
}

Step 4: Display Results

function displayResult(result) {
if (!result.found) {
showError(`Item not found: ${result.message}`);
return;
}

// Show item info
showItemCard({
name: result.item.name,
code: result.item.code,
category: result.item.category,
available: result.item.available_quantity,
});

// Show variant info (if variant was scanned)
if (result.variant) {
showVariantCard({
serial: result.variant.serial_number,
code: result.variant.code,
available: result.variant.is_available,
});
}

// Show rental info (if item is currently rented)
if (result.rental) {
showRentalCard({
status: result.rental.status,
renter: result.renter.name,
returnDate: result.rental.expected_return_date,
});
}
}

Add a search bar for manual item lookup:

async function searchItems(query) {
const response = await fetch(
`${API_BASE}/kiosk/search?q=${encodeURIComponent(query)}&limit=10`,
{
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Accept': 'application/json',
},
}
);

const { results } = await response.json();
displaySearchResults(results);
}

Step 6: Add Check-Out and Check-In

Check-Out (Create a Rental)

To check items out, create a new rental via POST /rentals:

async function checkout(renterId, items) {
const response = await fetch(`${API_BASE}/rentals`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
renter_id: renterId,
items: items.map(i => ({ item_id: i.id, quantity: i.quantity })),
}),
});

return response.json();
}

Check-In (Return Items)

To check items back in, update the rental with returned quantities and set the status to FINISHED:

async function checkin(rentalUuid, items, status = 'FINISHED') {
const response = await fetch(`${API_BASE}/rentals/${rentalUuid}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
status,
items: items.map(i => ({
item_id: i.id,
returned_quantity: i.returnedQuantity,
lost_quantity: i.lostQuantity ?? 0,
})),
}),
});

return response.json();
}

// Full return
await checkin('019c77b8-d000-716e-8dd8-3a3a13ae2a1c', [
{ id: 1, returnedQuantity: 3, lostQuantity: 0 },
]);

// Partial return — keep status as ONGOING
await checkin('019c77b8-d000-716e-8dd8-3a3a13ae2a1c', [
{ id: 1, returnedQuantity: 1, lostQuantity: 0 },
], 'ONGOING');

Rental Statuses

StatusDescription
IN_TREATMENTRental just created, being prepared
ONGOINGItems dispatched / partially returned
FINISHEDAll items fully returned

UI Recommendations

For kiosk applications:

  • Large touch targets — Buttons should be at least 48px for touch screens
  • High contrast — Use clear colors and large fonts
  • Auto-reset — Clear the screen after a period of inactivity
  • Sound feedback — Play audio cues for successful/failed scans
  • Continuous scanning — Auto-restart the scanner after each result
  • Offline queue — Queue actions when network is unavailable

Security Considerations

  • Use a dedicated API token with minimal abilities
  • Lock down the kiosk device — Use kiosk mode or guided access (the Stockaj Kiosk app has a built-in kiosk mode with PIN lock)
  • HTTPS only — Ensure all API calls use HTTPS
  • Store the token securely — in a system keychain (e.g. tauri-plugin-keyring) or environment variables. Avoid plain localStorage in production
  • For web-based kiosks, consider a backend proxy to keep the token server-side. For native apps (e.g. Tauri/Electron), use the native HTTP client so the token isn't exposed in browser DevTools