Content bundles¶
A content bundle is a self-contained web app (HTML/CSS/JS/images/fonts — any static web content) that you upload to the device and the device serves and displays locally, with no dependency on network reachability. It's the recommended way to show your own content, as opposed to navigating to a remote URL.
Authoring a bundle¶
A bundle is a plain zip archive with these rules:
index.htmlMUST exist at the root of the archive — this is the entry point that gets served.- Reference other files (CSS, JS, images) with paths relative to the archive root, e.g.
<script src="app.js">,<link rel="stylesheet" href="css/style.css">. - No absolute paths and no
..traversal in entry names — the device rejects archives that contain them. - No symlink entries — rejected too.
- The archive must be a genuine zip (checked by content, not by file extension) and every entry must decompress cleanly (CRC-32 verified).
- If you need HTTP range requests to work on a file (e.g. seeking in a large video), store that entry uncompressed ("Store" method) in the zip rather than "Deflate" — the device can only serve byte ranges out of uncompressed entries. Most zip tools compress by default; check your tool's flag for "store"/"no compression" per file, or zip the whole archive with compression level 0 if range support matters more than upload size.
- Bundle names (the
{name}in the URLs below) should be short filesystem/URL-safe identifiers (letters, digits,-,_) — they become part of the path the bundle is served at on the device.
The page channel script (display-client.js, providing window.mcd for
message-passing with the host — see REST and WebSocket API) is
injected automatically into every page the kiosk shows, bundle or remote URL alike. You don't
need to include it yourself, though you may — it self-deduplicates.
A minimal bundle is just:
Upload and activate a bundle¶
Upload (or replace) a bundle by name:
curl -X PUT http://10.99.0.1/api/v1/content/promo \
--data-binary @promo.zip \
-H 'Content-Type: application/zip'
This returns 202 Accepted with an operation resource while the device transfers and validates
the archive (see Long-running operations for how to poll or subscribe
to its completion). Uploading to an existing name replaces it atomically — the old bundle
stays untouched and servable until the new one fully validates.
Once the upload operation completes successfully, display it:
curl -X POST http://10.99.0.1/api/v1/display/bundle \
-H 'Content-Type: application/json' \
-d '{"name": "promo"}'
Web UI: Content page → Upload (file picker) → once listed, Display.
List bundles and check free space¶
Returns every stored bundle (name, size, sha256, created, whether it's currently active
on screen) plus disk: {free_bytes, total_bytes} for the data partition — check this before a
large upload. The device also rejects an upload outright if it won't leave at least 64 MB of
headroom on the data partition.
Web UI: Content page — the bundle list and the free-space indicator.
Re-validate a stored bundle¶
Returns 200 {"ok": true|false, "message": "…"} — this re-runs the same integrity checks as
upload time against the archive already on the device (useful after suspecting SD-card wear or
just to sanity-check before a scheduled display change). It's synchronous and may take a few
seconds on a large archive; a 404 content_not_found means no bundle by that name exists.
Delete a bundle¶
Fails with 409 bundle_in_use if the bundle is currently displayed — switch to another target
first (POST /display/waiting or display a different bundle).
Web UI: Content page → bundle row → Delete.
Set the boot-time bundle¶
To have a bundle shown automatically at boot (instead of the waiting page), set it in configuration — see Configuration → startup target for the full boot-target resolution order:
curl -X PATCH http://10.99.0.1/api/v1/config \
-H 'Content-Type: application/json' \
-d '{"content": {"startup_bundle": "promo"}}'
This only takes effect at the next boot — it does not navigate the currently running
display. If the named bundle is missing or fails validation at boot time, the device falls
through to the next configured target (ultimately the waiting page) rather than getting stuck, and
reports the failure via a display_state event.
How bundles are served¶
Bundles are stored on the device exactly as uploaded (the zip archive itself, plus a small metadata sidecar) and served directly out of the archive — they are never extracted to disk. This is transparent to you as an integrator except for two practical consequences worth knowing:
- Range requests only work on entries stored uncompressed in the zip (see "Authoring a bundle" above); compressed entries are always served whole.
- Bundles are served from a local origin on the device (
http://127.0.0.1:8081/bundles/{name}/) — you never address that origin directly from the host; you only ever reference bundles bynamethrough the API above.
See also¶
- Displaying content — navigating, reloading, screenshots, crash recovery.
- Configuration — the startup-bundle field and boot target resolution.
- Boot and shutdown splash — for branding shown before any bundle, at boot.