The device's persistent configuration is a single JSON document, read with
GET /api/v1/config and updated with PATCH /api/v1/config (partial updates — send only
the fields you want to change). It is stored at /data/config.json on the device,
schema-versioned (schema: 1), written atomically.
Every PATCH response reports, per field actually present in the patch, whether it was
applied live or is reboot_required (persisted immediately, effective at the
next boot or subsystem restart) in the applied map, keyed by dotted path (e.g.
"display.rotation"). Unknown fields are rejected 400 bad_request with no partial
write — a typo never gets silently ignored. A successful PATCH also emits the
config_changed event (see Events).
Live-apply is incremental
A field's applied value can widen from reboot_required to live as the
corresponding subsystem lands (spec 04). As of this writing, genuinely live fields
are: ui.enabled, device.name, display.rotation (primary screen), and
audio.volume/audio.mute. Everything else currently reports reboot_required,
even though it is persisted immediately — check the live applied map from your own
PATCH response rather than hard-coding this list, since it changes across releases.
Primary screen's rotation applies live (CSS output transform).
display.forced_mode
string, e.g. "1920x1080@60"
null (auto-detect)
reboot_required.
display.resume_on_boot
boolean
false
reboot_required.
display.screens.<id>.rotation
integer enum 0 \| 90 \| 180 \| 270
inherits display.rotation
Keyed by screen id from GET /display/screens. Non-primary screen rotation is reboot_required.
display.screens.<id>.forced_mode
string
inherits display.forced_mode
reboot_required.
display.screens.<id>.url
string (URI)
null (waiting page)
Startup URL for that screen.
display.screens.<id>.browser.backend
string enum wpe
inherits browser.backend
Per-screen entries are board-gated: single-output boards (e.g. Pi Zero 2 W) only
accept the primary screen's id; any other id is 400 screen_not_found. v1 renders
single-screen only — per-screen config is accepted and persisted, but multi-output
rendering is future work; see the board matrix.
Applied live — takes effect on the next HTTP request, no restart. Serves the embedded management SPA at / when true; when false, / returns 404 and only /api/v1 is exposed. The trusted-link model means the SPA is unauthenticated either way (spec 00); this flag only controls whether it's served.
Name of the bundle to display at boot. Consulted only at boot — PATCH persists immediately but always reports reboot_required; it never navigates the running display (use POST /display/bundle for that). Validated for name syntax only — may reference a bundle uploaded later. A missing/invalid bundle at boot falls through to the waiting page.
Policy surface controlling which page origins and which HID/USB/serial peripherals on
the Pi's USB-A ports the displayed page may reach via the navigator.hid/usb/serial
polyfills. Pi 4 / Pi 5 only — accepted but inert on boards without a USB host port (e.g.
Zero 2 W). All domains default disabled; a device is reachable only if it matches an
allow entry and the page origin is in devices.origins. Applied live
(re-evaluated on the next request*/open call from the page).
Field
Type
Default
Validation / notes
devices.origins
array of string
[]
Shared cross-domain allow-list of page origins, e.g. ["https://pos.example.com", "file://"].
devices.hid.enabled
boolean
false
devices.hid.allow[]
array of {vendorId, productId, usagePage?, usage?} (integers)
[]
devices.serial.enabled
boolean
false
devices.serial.allow[]
array of {usbVendorId, usbProductId} (integers)
[]
devices.usb.enabled
boolean
false
devices.usb.allow_driver_detach
boolean
false
If true, displayd may detach a kernel driver to claim an allow-listed interface. Never detaches usbhid/usb-storage or the gadget controller. Dangerous — can break OS function; leave false unless you know you need it.
devices.usb.allow[]
array of {vendorId, productId, classCode?, subclass?, protocol?, serialNumber?} (integers/string)
Policy + binding surface for the optional NFC capability. Accepted on all boards, inert
where no reader is present. When nfc.enabled is false (the default), every
/api/v1/nfc/* endpoint returns 503 nfc_disabled.
Field
Type
Default
Validation / notes
nfc.enabled
boolean
false
A backend/bus change reinitialises the reader.
nfc.backend
string enum auto \| pn532-i2c \| pn532-spi \| acr122u
auto
nfc.origins
array of string
[]
Page origins permitted to use the NFC API/events.
nfc.i2c.bus
integer
1
nfc.i2c.address
integer
36 (0x24)
nfc.spi.bus
integer
0
nfc.spi.cs
integer
0
nfc.poll.interval_ms
integer
250
Minimum 50. Applies live.
nfc.feedback.on_detect
boolean
true
Auto LED/buzzer feedback on tag detection. Applies live.
Accepted on all boards; inert (503-gated) where GET /device's
capabilities.wifi is false (the virt/generic model profiles). All wireless
fields apply live (daemon start/stop/reconfigure).
Field
Type
Default
Validation / notes
wifi.enabled
boolean
false
Master switch: unblocks rfkill and starts wpa_supplicant. Requires wifi.country to be set — enabling without a valid country is rejected 400 bad_request with no partial write.
wifi.mode
string enum station \| ap
station
wifi.country
string, pattern ^[A-Z]{2}$ (ISO 3166-1 alpha-2)
— (no default)
Required (non-null) whenever wifi.enabled is true.
WPA2 passphrase, 8–63 chars, required to start an AP. Returned verbatim on reads.
band
string enum "2.4" \| "5"
"2.4"
"2.4" is valid on every board; "5" is rejected 400 on boards without dual-band radios (e.g. Zero 2 W). AP is WPA2-PSK only; "5" is restricted to non-DFS channels.
channel
integer
0 (auto)
0 = displayd picks a fixed non-DFS channel for the band; otherwise must be a valid channel of that band/regulatory domain.
Accepted on all boards; inert (503-gated) where GET /device's
capabilities.bluetooth is false. enabled: true starts dbus-daemon +
bluetoothd and powers the adapter — applied live.
Field
Type
Default
Validation / notes
bluetooth.enabled
boolean
false
bluetooth.name
string
null (inherits device.name)
Adapter alias.
bluetooth.discoverable
boolean
false
Whether the adapter is discoverable while powered.
See the configuration guide for task-oriented walkthroughs
(rotating the display, setting up a proxy, restricting the device-access bridge) and the
Wireless guide for wifi/bluetooth setup end to end.