Documentazione completa delle API pubbliche per il software player sui miniPC Linux. Tutte le chiamate usano JSON. Base URL: https://{host}/api/v1
Il software player sul miniPC segue questo ciclo di vita, dall'accensione al funzionamento continuo:
PRIMA ACCENSIONE
1. Provisioning ─── ottieni device_id dal server
(QR code / claim manuale)
2. Pairing ─── reclama il device con hardware ID (MAC)
il device diventa non disponibile per altri
CICLO CONTINUO (dopo pairing)
3. Manifest Poll ─── ogni 30s, scarica la programmazione completa
4. Content Sync ─── scarica/aggiorna i file media
5. Sync Ack ─── conferma download di ogni file
6. Heartbeat ─── ogni 30s, segnala stato al server
7. Command Poll ─── ogni 10s, esegui comandi remoti
8. Render ─── valuta schedule rules, mostra contenutiOgni chiamata API restituisce JSON. In caso di successo il body contiene i dati richiesti. In caso di errore il body contiene { "error": "messaggio" } con HTTP status appropriato.
Il provisioning registra un nuovo device nel sistema. Avviene una sola volta alla prima accensione. Ci sono due modalità: claim da QR code (gestito dal backoffice) oppure creazione diretta.
/provisioningCrea una nuova sessione di provisioning. Genera un codice univoco (32 byte hex) valido 15 minuti. Questa chiamata viene fatta tipicamente dal backoffice, non dal device.
Nessun body richiesto.
Risposta — 201 Created
{
"id": 1,
"provisioning_code": "a1b2c3d4e5f6...",
"status": "open",
"device_id": null,
"expires_at": "2026-03-01T12:15:00.000Z",
"created_at": "2026-03-01T12:00:00.000Z"
}/provisioning?code=xxxIl device può fare polling su questo endpoint per sapere quando la sessione è stata claimed. Utile se il provisioning avviene da cellulare (scansione QR) e il device aspetta.
| Parametro | Tipo | Descrizione |
|---|---|---|
| code | string (query) | Il provisioning_code da verificare |
Risposta — 200 OK (sessione trovata)
{
"id": 1,
"provisioning_code": "a1b2c3d4e5f6...",
"status": "open" | "claimed" | "expired",
"device_id": null | 42,
"expires_at": "...",
"claimed_at": null | "..."
}Errori possibili
| Status | Causa |
|---|---|
| 400 | Parametro code mancante |
| 404 | Sessione non trovata |
/provisioning/claimIl device chiama questo endpoint per "reclamare" una sessione di provisioning aperta. Il server crea il device, i monitor, e restituisce device_id e device_token. Il device deve salvare entrambi in modo persistente.
Body richiesto
{
"provisioning_code": "a1b2c3d4e5f6...", // obbligatorio
"device_name": "Ristorante Roma 1", // obbligatorio
"device_ip": "192.168.1.100", // opzionale
"monitor_count": 2, // opzionale (default: 1, max: 3)
"monitor_names": ["Sala", "Bancone"] // opzionale
}Risposta — 200 OK
{
"device_id": 42,
"device_token": "f8c3a2e1..." // 64 char hex — salvare in modo sicuro!
}Errori possibili
| Status | Causa | Azione device |
|---|---|---|
| 400 | provisioning_code o device_name mancante | Fix parametri e riprova |
| 404 | Codice non valido o sessione non open | Richiedere nuovo codice |
| 410 | Sessione scaduta (> 15 minuti) | Richiedere nuovo codice |
| 500 | Errore server nella creazione device | Riprova dopo 10s |
Nota importante
Dopo il claim, il device_token non viene mai più restituito dal server. Se perso, bisogna creare un nuovo device. Il server salva solo l'hash SHA-256 del token.
Dopo il provisioning, il device deve "reclamare" il proprio record inviando un identificativo hardware univoco (MAC address della scheda di rete primaria). Una volta reclamato, il device non è più disponibile per altre attivazioni.
/devices/{deviceId}Il device invia il proprio MAC address come device_pairing_code. Il server lo salva e da quel momento il device risulta "attivato" nel backoffice.
Body richiesto
{
"device_pairing_code": "A4:83:E7:2F:1B:9C" // MAC address del miniPC
}Risposta — 200 OK
{
"id": 42,
"device_name": "Ristorante Roma 1",
"device_pairing_code": "A4:83:E7:2F:1B:9C",
...
}Errori possibili
| Status | Causa |
|---|---|
| 400 | Nessun campo valido nel body |
| 500 | Errore database |
Come ottenere il MAC address
# Linux — MAC della prima interfaccia di rete fisica
cat /sys/class/net/$(ip route show default | awk '/default/ {print $5}')/address
# Output: a4:83:e7:2f:1b:9cPrima di tentare il pairing, il software deve verificare che il device non sia già reclamato da un altro miniPC. Un device con device_pairing_code != null è già attivato e non può essere reclamato.
/devices/{deviceId}Controllare il campo device_pairing_code nella risposta. Se è null, il device è disponibile. Se contiene un valore, è già attivato su un altro miniPC.
Un operatore può scollegare un device dal miniPC fisico tramite il backoffice. Questo resetta il device_pairing_code a null, rendendo il device nuovamente disponibile per una nuova attivazione.
/devices/{deviceId}Body
{
"device_pairing_code": null
}Questa operazione è tipicamente eseguita dal backoffice, non dal device. Casi d'uso: sostituzione miniPC guasto, riassegnazione device a un altro punto vendita.
Il manifest contiene TUTTA la programmazione del device: monitor configurati, schedule rules, contenuti con URL firmati, e playlist. Il device lo scarica periodicamente (ogni 30s) e lo usa per determinare cosa mostrare.
/manifest/{deviceId}Risposta — 200 OK
{
"device_id": 42,
"device_name": "Ristorante Roma 1",
"generated_at": "2026-03-01T12:00:00.000Z", // timestamp generazione
"monitors": [
{
"id": 101,
"monitor_index": 0, // 0, 1, 2
"monitor_name": "Sala",
"schedule_id": 5,
"schedule_timezone": "Europe/Rome",
"rules": [ // regole della schedule
{
"id": 201,
"priority": 1, // più basso = più prioritario
"rule_type": "weekly", // "weekly" | "date"
"day_of_week": 1, // 1=Lun..7=Dom (null se date)
"date_ymd": null, // "YYYY-MM-DD" (null se weekly)
"start_time": "08:00:00",
"end_time": "22:00:00",
"target_type": "content", // "content" | "playlist"
"target_id": 301,
"is_default": false
},
{
"id": 202,
"priority": 999,
"rule_type": "weekly",
"day_of_week": null,
"date_ymd": null,
"start_time": "00:00:00",
"end_time": "23:59:59",
"target_type": "content",
"target_id": 302,
"is_default": true // regola di fallback
}
],
"fallback_content_id": 302, // contenuto se nessuna regola attiva
"override_target_type": "none", // "none" | "content" | "playlist"
"override_target_id": null,
"override_until": null // ISO datetime o null
}
],
"contents": [
{
"id": 301,
"content_name": "promo-estate.mp4",
"content_kind": "video", // "img" | "video" | "gif"
"checksum_sha256": "a1b2c3...", // per verifica integrità
"file_size_bytes": 15400000,
"mime_type": "video/mp4",
"duration_seconds": 30, // null per immagini
"signed_url": "https://storage.../promo.mp4?token=..."
}
],
"playlists": [
{
"id": 401,
"playlist_name": "Menu pranzo",
"loop_enabled": true,
"items": [
{
"content_id": 301,
"position": 0,
"duration_override_seconds": 15 // null = usa durata default
}
]
}
]
}Errori possibili
| Status | Causa | Azione device |
|---|---|---|
| 404 | Device non trovato o eliminato | Fermare polling, mostrare errore |
| 500 | Errore server | Usare ultimo manifest in cache, riprova al prossimo ciclo |
Strategia di caching
Il device deve confrontare generated_at con il valore precedente. Se identico, il manifest non è cambiato e non serve riscaricare contenuti. Le signed_url scadono dopo 1 ora — il device deve usarle per scaricare i file, poi servire i contenuti dal disco locale.
Dopo aver ricevuto il manifest, il device deve scaricare tutti i contenuti referenziati. I file vanno salvati su disco locale e serviti da lì durante il rendering.
Per ogni content nel manifest.contents:
1. Controlla se il file esiste già in cache locale
(confronta checksum_sha256 con il file su disco)
2. Se NON esiste o checksum diverso → scarica da signed_url
3. Verifica integrità: calcola SHA-256 del file scaricato
e confronta con checksum_sha256 del manifest
4. Salva su disco con naming: {content_id}_{checksum[:8]}.{ext}
5. Invia sync-ack al server (sezione 5)
6. Rimuovi file orfani non più nel manifestLe signed_url nel manifest sono già pronte per il download (GET diretto). Se servisse rigenerare un URL scaduto per un singolo contenuto:
/content/{contentId}/urlRisposta — 200 OK
{
"signed_url": "https://storage.supabase.co/.../file.mp4?token=..."
}Errori possibili
| Status | Causa |
|---|---|
| 404 | Contenuto non trovato o eliminato |
| 500 | Errore generazione URL firmato |
Nota
Le URL firmate scadono dopo 1 ora. Il device non deve salvare le URL ma solo i file scaricati. Alla prossima poll del manifest riceverà URL aggiornate.
Dopo aver scaricato (o tentato di scaricare) un contenuto, il device invia una conferma al server. Questo permette al backoffice di sapere lo stato di sync di ogni device.
/sync-ackBody richiesto
{
"device_id": 42, // obbligatorio
"content_id": 301, // obbligatorio
"download_state": "ready", // vedi tabella sotto
"local_path": "/cache/301_a1b2c3d4.mp4", // opzionale — path su disco
"last_error": null // opzionale — messaggio errore
}Valori validi per download_state
| Stato | Significato | Quando usare |
|---|---|---|
| pending | Download pianificato | File in coda per il download |
| downloading | Download in corso | Download avviato |
| ready | File pronto | Download completato e checksum verificato |
| failed | Download fallito | Errore di rete, checksum non valido, disco pieno |
| deleted | File rimosso | File non più necessario e cancellato dal disco |
Risposta — 200 OK
{ "ok": true }Errori possibili
| Status | Causa |
|---|---|
| 400 | device_id o content_id mancante |
| 500 | Errore database |
Comportamento upsert
Il server usa upsert su (device_id, content_id). Chiamate ripetute per lo stesso contenuto aggiornano lo stato esistente. Ogni sync-ack aggiorna anche last_sync_at su tutti i monitor del device.
Il device invia un heartbeat ogni 30 secondi per segnalare che è vivo e funzionante. Il backoffice usa il timestamp dell'ultimo heartbeat per determinare se il device è online (soglia: 90 secondi).
/heartbeatBody richiesto
{
"device_id": 42, // obbligatorio
"uptime_seconds": 86400, // secondi dall'avvio del player
"storage_free_bytes": 5368709120, // spazio libero su disco
"storage_total_bytes": 53687091200, // spazio totale disco
"player_version": "1.2.0", // versione software player
"internet_ok": true, // connettività internet
"server_ok": true, // raggiungibilità server API
"redis_ok": true, // se applicabile
"sync_ok": true, // ultimo sync riuscito
"error_codes_json": null // array di codici errore o null
}Tutti i campi tranne device_id sono opzionali. Si consiglia comunque di inviare sempre almeno uptime_seconds,player_version e i flag di stato.
Esempio con errori attivi
{
"device_id": 42,
"uptime_seconds": 3600,
"player_version": "1.2.0",
"internet_ok": true,
"server_ok": true,
"sync_ok": false,
"error_codes_json": ["SYNC_FAILED", "STORAGE_LOW"]
}Risposta — 200 OK
{ "ok": true }Errori possibili
| Status | Causa | Azione device |
|---|---|---|
| 400 | device_id mancante | Bug nel player — fix e riavvia |
| 500 | Errore database | Ignorare, riprovare al prossimo ciclo |
Effetti collaterali
Oltre a inserire il record heartbeat, il server aggiorna last_heartbeat_at eplayer_version sulla tabella device.
Il backoffice può inviare comandi al device. Il device li riceve facendo polling ogni 10 secondi, li esegue, e conferma l'esecuzione.
/commands/{deviceId}Restituisce tutti i comandi con status "queued", ordinati per data di richiesta (più vecchi prima).
Risposta — 200 OK
{
"commands": [
{
"id": 501,
"device_id": 42,
"command": "force_sync",
"params_json": null,
"status": "queued",
"requested_by_user_id": null,
"requested_at": "2026-03-01T12:00:00.000Z",
"acked_at": null
}
]
}| Comando | Descrizione | Azione device |
|---|---|---|
| reboot | Riavvia il sistema | Riavviare il miniPC (sudo reboot) |
| restart_player | Riavvia solo il player | Riavviare il processo player senza reboot OS |
| force_sync | Forza riscaricamento contenuti | Riscaricare manifest + tutti i contenuti |
| refresh_config | Aggiorna configurazione | Riscaricare manifest immediatamente |
| set_override | Imposta override su un monitor | Applicare override dai params_json |
Esempio params_json per set_override
{
"monitor_id": 101,
"target_type": "content",
"target_id": 305,
"until": "2026-03-01T18:00:00.000Z"
}/commands/{deviceId}Dopo aver eseguito (o tentato di eseguire) un comando, il device deve confermarlo.
Body richiesto
{
"command_id": 501, // obbligatorio — ID del comando
"status": "ack" // "ack" (successo) o "failed" (fallito)
}Risposta — 200 OK
{
"id": 501,
"device_id": 42,
"command": "force_sync",
"status": "ack",
"acked_at": "2026-03-01T12:00:05.000Z",
...
}Errori possibili
| Status | Causa |
|---|---|
| 400 | command_id o status mancante |
| 500 | Errore database |
Flusso completo di un comando
Backoffice: POST /commands/{deviceId} → status: "queued"
Device: GET /commands/{deviceId} → riceve comando
Device: esegue il comando
Device: PATCH /commands/{deviceId} → status: "ack" o "failed"Il device deve determinare quale contenuto mostrare su ogni monitor in ogni momento. Questa logica gira interamente sul device, usando le regole ricevute nel manifest.
Per ogni monitor, in questo ordine:
1. OVERRIDE ATTIVO
Se override_target_type != "none"
E override_target_id != null
E (override_until è null OPPURE override_until > now)
→ Mostra override_target_id
2. DATE RULES (regole per data specifica)
Filtra rules dove:
rule_type == "date"
date_ymd == oggi (YYYY-MM-DD)
ora corrente è dentro [start_time, end_time)
Ordina per priority (più basso = più prioritario)
→ Usa la prima match
3. WEEKLY RULES (regole settimanali)
Filtra rules dove:
rule_type == "weekly"
day_of_week == giorno ISO corrente (1=Lun..7=Dom)
ora corrente è dentro [start_time, end_time)
Ordina per priority
→ Usa la prima match
4. DEFAULT RULE
Filtra rules dove is_default == true
→ Usa questa
5. FALLBACK CONTENT
Se nessuna regola attiva → mostra fallback_content_id
6. NESSUN CONTENUTO
Se anche fallback è null → schermo nero / logoGli orari sono in formato HH:MM:SS. Il confronto è lessicografico (string-based). Le fasce notturne sono supportate:
Fascia diurna (start <= end): "08:00:00" - "22:00:00" → attiva se ora >= start AND ora < end Fascia notturna (start > end): "22:00:00" - "06:00:00" → attiva se ora >= start OR ora < end
JavaScript: 0=Dom, 1=Lun, 2=Mar, ..., 6=Sab ISO 8601: 1=Lun, 2=Mar, ..., 7=Dom Conversione: iso = (js === 0) ? 7 : js
Quando il target è una playlist, il device deve ciclare gli items in ordine di position:
Per ogni item nella playlist:
1. Mostra il contenuto (content_id)
2. Durata:
- Se duration_override_seconds != null → usa quello
- Se content.duration_seconds != null → usa quello (video)
- Altrimenti → usa DEFAULT_IMAGE_DURATION_SECONDS (10s)
3. Per i video: aspetta l'evento "ended" poi avanza
4. Se loop_enabled == true: ricomincia da capo dopo l'ultimo item
5. Se loop_enabled == false: resta sull'ultimo itemIl device può segnalare errori attivi nell'heartbeat tramite il campo error_codes_json. Questi codici vengono visualizzati nel backoffice per diagnostica.
| Codice | Significato | Quando segnalarlo |
|---|---|---|
| NO_INTERNET | Nessuna connessione internet | Ping a 8.8.8.8 fallisce |
| NO_SERVER | Server API non raggiungibile | Manifest poll fallisce con errore di rete |
| SYNC_FAILED | Sync contenuti fallito | Download di uno o più file fallito |
| STORAGE_LOW | Spazio disco insufficiente | Spazio libero < 500MB (o soglia configurata) |
| REDIS_DOWN | Redis non raggiungibile | Se il device usa Redis locale per cache |
| EMPTY_SCHEDULE | Nessuna regola attiva | Nessun contenuto da mostrare su almeno un monitor |
Visualizzazione errori
Gli errori non devono interrompere la visualizzazione dei contenuti. Il player deve continuare a mostrare l'ultimo contenuto valido in cache. Gli errori vanno mostrati come indicatori discreti (piccole icone semitrasparenti) visibili solo a un tecnico, non al pubblico in sala.
| Costante | Valore | Descrizione |
|---|---|---|
| HEARTBEAT_INTERVAL_MS | 30.000 (30s) | Intervallo invio heartbeat |
| MANIFEST_POLL_INTERVAL_MS | 30.000 (30s) | Intervallo polling manifest |
| COMMAND_POLL_INTERVAL_MS | 10.000 (10s) | Intervallo polling comandi |
| DEVICE_ONLINE_THRESHOLD_SECONDS | 90 | Soglia per considerare device online |
| DEFAULT_IMAGE_DURATION_SECONDS | 10 | Durata default immagini/gif in playlist |
| SIGNED_URL_EXPIRY | 3.600 (1h) | Validità URL firmati per download |
Diagramma completo del comportamento del software player, dalla prima accensione al funzionamento continuo.
┌─────────────────────────────────────────────────────────────┐
│ PRIMA ACCENSIONE │
├─────────────────────────────────────────────────────────────┤
│ │
│ Esiste device_id salvato su disco? │
│ ├─ NO → Avvia provisioning │
│ │ POST /provisioning/claim │
│ │ Salva device_id + device_token │
│ │ │
│ └─ SÌ → Continua │
│ │
│ Device già reclamato (device_pairing_code != null)? │
│ ├─ NO → Reclama con MAC address │
│ │ PATCH /devices/{deviceId} │
│ │ Body: { device_pairing_code: "AA:BB:CC:..." } │
│ │ │
│ └─ SÌ → Vai al ciclo principale │
│ │
├─────────────────────────────────────────────────────────────┤
│ CICLO PRINCIPALE │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──── Ogni 30s ────────────────────────────────────┐ │
│ │ MANIFEST POLL │ │
│ │ GET /manifest/{deviceId} │ │
│ │ ├─ 200 → Confronta generated_at │ │
│ │ │ Se cambiato → Content Sync │ │
│ │ │ Se uguale → skip │ │
│ │ ├─ 404 → Device eliminato, ferma tutto │ │
│ │ └─ 5xx → Usa cache locale, segnala NO_SERVER │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌──── Content Sync (quando manifest cambia) ───────┐ │
│ │ Per ogni content in manifest.contents: │ │
│ │ 1. File in cache con stesso checksum? → skip │ │
│ │ 2. Download da signed_url │ │
│ │ 3. Verifica SHA-256 │ │
│ │ 4. POST /sync-ack (ready / failed) │ │
│ │ 5. Rimuovi file orfani dal disco │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌──── Ogni 30s ────────────────────────────────────┐ │
│ │ HEARTBEAT │ │
│ │ POST /heartbeat │ │
│ │ Body: device_id, uptime, storage, version, │ │
│ │ health flags, error_codes │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌──── Ogni 10s ────────────────────────────────────┐ │
│ │ COMMAND POLL │ │
│ │ GET /commands/{deviceId} │ │
│ │ Per ogni comando: │ │
│ │ 1. Esegui (reboot/sync/restart/override) │ │
│ │ 2. PATCH /commands/{deviceId} → ack/failed │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌──── Ogni secondo ────────────────────────────────┐ │
│ │ RENDER │ │
│ │ Per ogni monitor: │ │
│ │ 1. Override attivo? → mostra override │ │
│ │ 2. Date rule match? → mostra quel contenuto │ │
│ │ 3. Weekly rule match? → mostra quel contenuto │ │
│ │ 4. Default rule? → mostra default │ │
│ │ 5. Fallback content? → mostra fallback │ │
│ │ 6. Niente → schermo nero │ │
│ └───────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────┤
│ GESTIONE ERRORI │
├─────────────────────────────────────────────────────────────┤
│ │
│ • MAI interrompere la visualizzazione per un errore │
│ • Contenuti in cache → continuare a mostrare │
│ • Errori → icone discrete in basso a destra │
│ • Segnalare errori nell'heartbeat │
│ • Server irraggiungibile → usare ultimo manifest valido │
│ • Riprovare automaticamente al prossimo ciclo │
│ │
└─────────────────────────────────────────────────────────────┘Naming file su disco
Convenzione consigliata per i file in cache: {content_id}_{checksum_sha256.slice(0,8)}.{ext}. Esempio: 301_a1b2c3d4.mp4. Questo permette di identificare rapidamente file obsoleti (content_id presente ma checksum diverso).
CHECKMATE API v1 — Ultimo aggiornamento: Marzo 2026