The Provisioning Domain handles device lifecycle management for Digital Signage displays (LG WebOS, Android, etc.). It manages device pairing via OTP codes, real-time status monitoring via heartbeats, remote playback control via MQTT, and IP-based geolocation enrichment.
Lookup geo via ip-api.com using device IP; updates geo fields
PATCH
/devices/:deviceId/location
HandleAssignDeviceLocation
Assign device to a spatial location (space)
Key Creation Flows
1. Device Pairing (OTP Flow)
# Step 1: Screen requests a pairing code (called from the display hardware)
POST /devices/generate-code
→ Generates 6-digit numeric OTP
→ Stores in provisioning_codes with 10-minute TTL
→ Returns: { "provisioningId": "uuid", "code": "483921", "expiresAt": "..." }
# Step 2: Operator enters the code on the web platform
POST /devices/claim-code
Headers: X-Organization-ID
Body: { "code": "483921", "name": "Lobby Screen A" }
→ Validates code is not expired and not yet claimed
→ Creates device (serialNumber = "SN-483921", volume=100, orientation=landscape)
→ Links provisioning_codes.device_id = new device id
→ Returns: { "id": "device-uuid", "name": "Lobby Screen A", "status": "Online" }
# Step 3: Screen polls until claimed
GET /devices/check-claim/:provisioningId
→ Returns { "status": "pending" } (while waiting)
→ Returns { "status": "claimed", "deviceId": "...", "deviceToken": "JWT" }
→ Returns { "status": "expired" } (after 10 min)
2. Heartbeat (Device → Platform)
POST /devices/heartbeat-public
Body: { "deviceId": "device-uuid" }
→ No auth required (called directly from device)
→ Updates devices.last_seen_at = NOW()
→ Device is considered 'Online' for 5 minutes after last heartbeat
3. Push Playlist to Device
POST /devices/:deviceId/play
Headers: X-Organization-ID
Body: {
"id": "playlist-uuid",
"playlistName": "Morning Show",
"items": [
{ "mediaId": "...", "name": "...", "mediaType": "video", "duration": 30, "url": "...", "storageKey": "..." }
]
}
→ Normalizes items (supports both "id"/"mediaId" and "type"/"mediaType" field names)
→ Also supports "zones": [{ "items": [...] }] for multi-zone layouts
→ Updates devices.current_playlist_id
→ Publishes to MQTT topic: tellyboard/devices/{deviceId}/play
→ Returns: { "success": true, "message": "Playlist sent and saved" }
POST /devices/:deviceId/stop
→ Clears current_playlist_id
→ Publishes "STOP" to MQTT topic: tellyboard/devices/{deviceId}/player/command
→ Returns: { "status": "stopped" }
4. Geolocation Enrichment
POST /devices/:deviceId/geo
Headers: X-Organization-ID
→ Uses device's stored ip_address, or falls back to request IP / X-Forwarded-For
→ Private IPs (127.x, 192.168.x, 10.x) query ip-api.com without IP (uses server IP)
→ Calls: http://ip-api.com/json/{ip}
→ Updates: continent, country, countryCode, regionName, city, isp, ip_address
→ Returns geo fields on success
5. Relink Device to New Code
POST /devices/relink
Headers: X-Organization-ID
Body: { "code": "new-otp", "deviceId": "existing-device-uuid" }
→ Validates OTP is valid and unclaimed
→ Validates device belongs to org
→ Links provisioning_codes.device_id = existing device
→ Sets devices.status = 'Online', last_seen_at = NOW()
→ Use case: device was factory reset or swapped hardware
Changelog
Date
Author
Change
2026-06-17
—
Added description, architecture, data model, creation flows