Aller au contenu principal
Cette page vous a-t-elle été utile ?

Screens API

Screens & Gadgets

Design wallboard layouts and pair physical displays

Screens are user-owned canvases of Gadgets, optionally shared as reusable Templates, and shown read-only on paired Devices. This page documents the full surface directly from the controllers and DTOs.

JWT (Screens/Templates/Devices)Device bearer token (Device Content)One public endpoint (pairing codes)

Authentication and Permissions​

  • /api/screens, /api/screen-templates, and most of /api/devices require a normal user JWT and the screens plan feature (@RequireFeature('screens')).
  • Sharing a Screen as a Template (POST /api/screen-templates/from-screen/:screenId with a groupId) additionally requires the screen_sharing feature (Pro+).
  • /api/device-content/* routes authenticate via a Device bearer token (Authorization: Bearer dvc_...), never a user JWT. A Device token can only ever reach its own assigned Screen's rendered content — never arbitrary user data.
  • POST /api/devices/pairing-codes is the one deliberately public, unauthenticated endpoint — the kiosk has no session yet when it requests a pairing code. It's rate-limited per IP.
  • POST /api/device-content/gadgets/:gadgetId/quick-trigger additionally requires the target automation rule to have allowQuickTrigger: true set by its owner, and is rate-limited per-rule-per-device (default: 1 fire per 5 minutes).

Endpoint Reference​

MethodPathPurposeAuth
GET/api/screensList Screens owned by, or shared with, the caller.JWT
POST/api/screensCreate a Screen. Enforces the plan's max_screens limit.JWT
GET/api/screens/:idGet a Screen with its gadgets.JWT
PATCH/api/screens/:idUpdate a Screen's settings.JWT
DELETE/api/screens/:idDelete a Screen. Assigned devices become unassigned, not deleted.JWT
POST/api/screens/:id/gadgetsAdd a Gadget to a Screen.JWT
PATCH/api/screens/:id/gadgets/:gadgetIdMove, resize, or reconfigure a Gadget.JWT
DELETE/api/screens/:id/gadgets/:gadgetIdRemove a Gadget.JWT
PUT/api/screens/:id/gadgets/bulk-layoutBatch-save gadget positions/sizes after a drag session.JWT
POST/api/screens/:id/wallpaperUpload a wallpaper image (multipart, 200KB-20MB, jpeg/png/webp).JWT
DELETE/api/screens/:id/wallpaperRemove a Screen's wallpaper.JWT
GET/api/screen-templatesList Templates: owned, system, and group-shared.JWT
GET/api/screen-templates/:idGet one Template.JWT
POST/api/screen-templates/from-screen/:screenIdSave an existing Screen's layout as a new Template.JWT (+screen_sharing if shared)
POST/api/screen-templates/:id/cloneClone a system/shared Template into a personal, editable Template.JWT
POST/api/screen-templates/:id/applyApply a Template's layout onto an existing or new Screen.JWT
PATCH/api/screen-templates/:idRename/redescribe an owned Template.JWT
DELETE/api/screen-templates/:idDelete an owned Template.JWT
POST/api/devices/pairing-codesGenerate a pairing code (called by the kiosk itself).Public, IP rate-limited
GET/api/devicesList Devices owned by the caller.JWT
POST/api/devices/pairRedeem a pairing code from an authenticated session.JWT
GET/api/devices/:idGet a Device.JWT
PATCH/api/devices/:idRename a Device or reassign its Screen.JWT
POST/api/devices/:id/revokeRevoke a Device's credential immediately (no cache delay).JWT
GET/api/device-content/pairing-statusKiosk long-polls this to detect it has just been paired.Pairing code + session secret
GET/api/device-content/screenThe read-only content payload the kiosk renders.Device bearer token
POST/api/device-content/heartbeatLightweight liveness ping.Device bearer token
POST/api/device-content/gadgets/:gadgetId/quick-triggerFire an automation rule from a Quick Trigger gadget.Device bearer token

Request Shapes​

CreateScreenDto​

name (string, ≤120), description?, resolutionPreset?, width/height (200-8000), hideNavChrome? (boolean), displayDensity? (comfortable | minimal_large), wallpaperTheme? ({ colorValue, gradient? }), ownerScopeType? (personal | group | organisation), groupId?, organisationId?.

CreateGadgetDto​

gadgetType (see Gadget Catalog for the full type list), x/y/width/height (int), zIndex?, gadgetConfig? (type-specific — validated by that Gadget's own provider, not a fixed schema).

BulkGadgetLayoutDto​

gadgets: Array<{ id, x, y, width, height, zIndex? }> — up to 60 items.

RedeemPairingCodeDto​

code (exactly 8 chars), deviceName (≤120), screenId?.

Example Calls​

# Create a Screen
curl -X POST https://api.primecal.eu/api/screens \
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
-d '{"name":"Kitchen Wallboard","width":1920,"height":1080}'

# Add a Clock & Date gadget
curl -X POST https://api.primecal.eu/api/screens/42/gadgets \
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
-d '{"gadgetType":"clock_date","x":40,"y":40,"width":360,"height":160}'

# Kiosk: fetch the current Screen content payload
curl https://api.primecal.eu/api/device-content/screen \
-H "Authorization: Bearer $DEVICE_TOKEN"

Response and Behavior Notes​

  • GET /api/device-content/screen returns { state: 'ok', screen, gadgets, pollIntervalSeconds }, { state: 'unassigned' }, or { state: 'unavailable', reason } — never a 404/403 for these expected states, so an unattended kiosk can render a calm message instead of an error screen.
  • Each gadget's data shape is specific to its gadgetType — read the gadgetType of each gadget before interpreting its data.
  • Screen.wallpaperBlobUrl and Screen.wallpaperTheme are mutually exclusive — setting one clears the other.
  • Template gadgetConfig fields that reference user-specific data (e.g. filterLabelIds, ruleIds) are cleared on clone/apply across users — a Template never carries live or user-specific data.

Best Practices​

  • Always call PUT .../gadgets/bulk-layout after a drag session instead of one PATCH per gadget.
  • Re-probe wallpaper uploads server-side rather than trusting Content-Type — this API already does, but if you're proxying uploads, don't strip that validation.
  • Treat a Device bearer token as a long-lived secret with no expiry by default — revoke it (POST /api/devices/:id/revoke) rather than assuming it will lapse on its own.