Skip to content

Displaying content

The device always has something on screen: a boot splash, the waiting page, a remote URL, or an uploaded content bundle. This page covers the everyday recipes for putting content on screen and controlling how it's shown, using the control API on http://10.99.0.1/api/v1 (see API overview) and, where available, the management UI.

All examples use curl against the device's fixed USB address 10.99.0.1. Anything shown here also works unchanged over the serial (NDJSON) transport — see Serial API — and through the Go client library.

Display a remote URL

curl -X POST http://10.99.0.1/api/v1/display/navigate \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://example.com/promo"}'

The kiosk browser navigates immediately. This is a full-page navigation to a page fetched over whatever network path the device has (see the wireless guide and dev host guide for connection sharing) — for content you host yourself, an uploaded content bundle served locally is usually a better fit: no network dependency, and no external origin.

Web UI: Display page → Navigate to URL field.

Display an uploaded content bundle

Once a bundle has been uploaded, show it with:

curl -X POST http://10.99.0.1/api/v1/display/bundle \
  -H 'Content-Type: application/json' \
  -d '{"name": "promo"}'

Web UI: Content page → pick a bundle → Display.

Return to the waiting page

curl -X POST http://10.99.0.1/api/v1/display/waiting

This is also what the device falls back to automatically on boot (if nothing else is configured) and after a crash-looping page (see Crash recovery below).

Web UI: Display page → Show waiting page.

Reload the current page

curl -X POST http://10.99.0.1/api/v1/display/reload

Reloads whatever is currently shown (URL or bundle) without changing the target.

Restart the browser

If the page is stuck in a way a reload won't fix:

curl -X POST http://10.99.0.1/api/v1/display/restart-browser

This kills and relaunches the kiosk browser process, then re-applies the configured display target (rotation and startup URL/bundle) — not necessarily the last URL you navigated to by hand. Returns 200 as soon as the relaunch is scheduled; watch the display_state event (below) for completion.

Check what's currently on screen

curl http://10.99.0.1/api/v1/display

Returns the current mode (waiting | url | bundle), the active URL or bundle name, whether the screen is on, the current rotation, and the resolution. The same shape is pushed as a display_state event (see Events) every time the mode or URL changes, or the browser crashes/restarts — subscribe instead of polling if you need to react promptly.

Web UI: Dashboard page (live-updating "Display" card) and Display page.

Turn the screen on/off

curl -X POST http://10.99.0.1/api/v1/display/screen \
  -H 'Content-Type: application/json' \
  -d '{"on": false}'

This uses HDMI-CEC to put the connected display into standby — the kiosk browser and the current page keep running, only the panel goes dark. It's best-effort: it requires a CEC-capable screen and cable. GET /display reflects the commanded state in screen_on; if the screen doesn't support CEC, the call succeeds but nothing visibly happens.

Change rotation

Rotation is a configuration field, applied at the next boot (see Configuration → Display settings for the full field list and the live-vs-reboot-required distinction):

curl -X PATCH http://10.99.0.1/api/v1/config \
  -H 'Content-Type: application/json' \
  -d '{"display": {"rotation": 90}}'

The response's applied map reports "display.rotation": "reboot_required" — reboot the device (POST /api/v1/device/reboot) for it to take effect. The new rotation survives future reboots. For 90°/270°, content must be authored for the swapped logical viewport size (rotation is applied as a CSS transform on the page, not a real display-mode change) — see the configuration guide for details.

Take a screenshot

curl http://10.99.0.1/api/v1/display/screenshot -o screenshot.png

Returns a PNG of exactly what's on screen right now (rotation already applied), captured from the browser's compositor — handy for remote monitoring or automated visual checks.

Web UI: Display page → Screenshot.

Run JavaScript in the current page

curl -X POST http://10.99.0.1/api/v1/display/execute-js \
  -H 'Content-Type: application/json' \
  -d '{"script": "document.title"}'

Returns {"result": <json>} with whatever the script's last expression evaluated to. This is a blunt, one-shot tool for diagnostics or simple pokes; if your page and host need to exchange messages routinely, use the page channel instead (window.mcd in the page, POST /api/v1/page/message / the page_message event on the host side) — see REST and WebSocket API and the display-client.js reference in the client library guide.

Enumerate display outputs

curl http://10.99.0.1/api/v1/display/screens

Returns the list of physical outputs the device knows about: id, connector (e.g. HDMI-A-1), resolution, rotation, connected. In the current release every board renders on a single screen, so this returns exactly one entry even on Pi 4/5 boards with two HDMI ports; the field exists so the same API call keeps working once multi-output rendering ships. Every display/* call above accepts an optional screen query parameter for forward-compatibility — omit it (the device uses the primary screen automatically).

Crash recovery and fail-safe behavior

The device is designed to always converge on something being shown:

  • If the kiosk browser process crashes, displayd restarts it automatically (within a few seconds) on the same content, with display_state events marking the crash and recovery.
  • If the same URL or bundle crashes three times within two minutes, the device gives up on it and falls back to the waiting page, emitting a display_state event with error: "content_crash_loop". This is a strong signal your content has a fatal bug (an infinite synchronous loop, a hard JS exception on load, etc.) — check it before re-displaying the same content.
  • If configured content is missing or invalid at boot (a startup bundle that was deleted, for example), the device falls through to the next configured target and ultimately the waiting page, rather than getting stuck.

There is no API call needed to "recover" from this — just fix the content and re-navigate/re-display it.

See also