* feat(starlark,on-demand): the third-party fixes worth taking, plus an MQTT bridge Analysis of ant456/ledmatrix-fixes-repo, a third-party collection of patches and services built while running this project on Starlark apps under MQTT control. Its patches are whole-file copies taken against an older tree, so applying them as written would revert #523's frame pacing, #534's display() bool returns and the GitHub token masking in plugins_manager.js. Three of its claimed fixes are already in main, and its api_v3 Starlark routes are #535's. What follows is the rest -- verified against current code, and reimplemented where the patch's approach did not hold up. **On-demand display.** `pinned` reached the controller from the API, was stored on it and republished in the status payload, but never narrowed the rotation -- a pinned request still cycled every mode its plugin owns. Right for a sports plugin, whose modes are views of one subject; wrong for a plugin whose modes are unrelated, which is every Starlark app. Now honoured, and it survives a restart. Restarting while on-demand was active loaded *only* the on-demand plugin, so normal rotation had nothing to return to for the life of the process -- and a restart mid-session is routine, since that is how an update is applied. The panel came back cycling one plugin's modes with no way out but clearing the cache by hand. Every enabled plugin loads now; on-demand still resumes on its saved mode. Stop requests are exempt from the duplicate guards on purpose, so that a second click stops a mode a race left running -- which means consuming the mailbox is the only thing that ends one. It was never consumed, so the same stop was re-read and re-processed on every poll, forever. Both paths now share one compare-before-delete helper. **Starlark rendering.** `extract_schema` parsed the source with a regex, which can only see option lists written out literally: an app whose dropdown is filled from a live API call inside `get_schema()` came back empty, and the config form offered nothing to pick. Now runs `pixlet schema`, which executes the app, and falls back to the parser when Pixlet is absent, too old for the subcommand, or the app fails to run. The third-party patch replaced the parser outright and hardcoded /usr/local/bin/pixlet; this keeps the fallback and the binary search. A `|` in a config value was dropped by a shell-metacharacter filter, though the command is a list with no shell involved -- and apps do use it as a separator inside one value. The key went missing silently and the app rendered its own "not configured" screen with nothing to say why. And a 0-byte render was reported as success: Pixlet exits 0 and writes nothing when an app has no content, which read downstream as a working app drawing a black panel. **Starlark display.** `display()` ignored the mode it was called with, so a specific app could not be addressed. It now accepts `display_mode` -- which is the whole mechanism, since the controller inspects the signature before passing it. Found while there: `_select_next_app` ran only while `current_app` was unset, so with several apps installed the first was picked once and shown forever while the rest were rendered on schedule and never displayed. And `enable_scrolling` was missing, so multi-frame apps were called once per rotation slot and never advanced past frame one. **GET /api/v3/display/modes.** Every mode that can be requested on-demand, with the plugin that owns it. Nothing exposed this, so anything driving the display from outside the web UI read each plugin's manifest.json off disk and reimplemented PluginManager's fallbacks. It also triggers discovery, which is otherwise lazy and normally happens because a person opened the dashboard. **integrations/mqtt_bridge.** Home Assistant control over MQTT Discovery: a mode select, a stop button, power, brightness. Rewritten against the API rather than the filesystem, so it needs no read access to config.json and cannot drift from the web UI. paho-mqtt 2.x VERSION2, TLS, an availability topic that is also the last will, and secrets from the environment. **Two opt-in extras.** A DNS single-request unit, for glibc's parallel A/AAAA lookup stalling ~5s per name on routers that answer only the A query -- which makes any plugin calling an external API slow and Starlark apps, which have a render timeout, fail outright. And a Pixlet config editor: a script you run and Ctrl+C rather than the third-party version's always-on unauthenticated Flask service, since it stops the display for the length of a session. Neither is installed by default. Long Starlark app names now wrap instead of overflowing their card. 115 new tests across 5 files. Also unblocked test_starlark_display_contract.py, which was silently skipping wherever fcntl is absent. Whole suite: no new failures against main. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(mqtt_bridge): the five issues Codacy flagged on this branch All in the new bridge, all real: * requests floor was 2.31.0, which carries CVE-2024-35195, CVE-2024-47081 and CVE-2026-25645. Raised to >=2.33.0,<3.0.0, which is what the project's own requirements.txt already pins. * `import time` was never used. * `"mqtt_password": None` in DEFAULTS read as a hardcoded credential. It is the "no password configured" default; marked nosec B105, the convention used elsewhere in the repo. Also dropped an unused `build_app` from the display-modes test imports. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: the review findings on this PR Nine of CodeRabbit's ten, plus the CodeQL alert. The tenth is wrong and is answered below. **One bad config section blanked the whole mode list.** `/display/modes` read `full_config.get(plugin_id, {}).get('enabled')`, so a non-dict under a plugin id -- a shape DisplayController already guards, so it happens -- raised AttributeError mid-loop and answered 500 with no modes at all. Every MQTT bridge entity is built from that list. Now skipped with a warning. **The DNS scripts reported success they had not earned.** Three separate paths: `resolvconf -u` failing was swallowed by `|| true`; the systemd-resolved branch exited 0 without applying anything, so the oneshot unit recorded success while the workaround was inactive; and the installer's `|| echo` turned a failed start into "installation complete." with exit 0. All three now fail loudly. `single-request` is a glibc resolv.conf option with no resolved.conf equivalent, so on those hosts the honest answer is that it cannot be applied. A NetworkManager-generated resolv.conf is regenerated on connection changes, not only at boot, and the unit is oneshot with RemainAfterExit -- so the option can vanish mid-boot with nothing to put it back. Now detected and stated plainly rather than implied to be permanent. **`Before=` does not order a manual restart.** It only orders units already in the same transaction, so `systemctl restart ledmatrix` could bypass the fix. install_dns_fix.sh now writes a ledmatrix.service drop-in with Wants= and After=. Wants=, not Requires=: a DNS workaround failing should not stop the display. **The Pixlet editor's `--lan` is gone.** `pixlet serve` has no authentication, and a printed warning is not access control. Loopback only, with the SSH port-forward in the header where the flag used to be documented -- SSH does the authenticating and nothing is left listening. **The MQTT example config now defaults to TLS** on 8883. The installer copies it verbatim, and without TLS the broker password and every command cross the network in cleartext. A plaintext broker is still supported and documented, and the bridge warns once at startup when a password is configured without TLS. **Not taken: "the upstream Pixlet CLI has no `schema` subcommand."** Upstream tidbyt/pixlet has none, but `scripts/download_pixlet.sh` installs `tronbyt/pixlet`, whose `cmd/schema.go` is `schema [PATH]` -> JSON on stdout, built on `runtime.NewAppletFromPath`, so it does execute `get_schema()`. That is exactly what extract_schema_via_pixlet calls. A binary without the subcommand exits non-zero and falls back to the source parser, which is already covered by a test. **CodeQL stack-trace exposure: not taken either.** I removed `details` first and that broke test_web_error_detail.py::test_no_api_v3_handler_discards_its_exception, which enforces `describe_exception` across all ~75 handlers -- written because a device with failing storage answered "see logs for details" from the log viewer itself. describe_exception redacts credentials; the trade-off is the project's and is already made. Restored, with the reasoning in a comment. 11 new tests. Whole suite: no new failures against main, 4127 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
28 KiB
LEDMatrix REST API Reference
Complete reference for all REST API endpoints available in the LEDMatrix web interface.
Base URL: http://your-pi-ip:5000/api/v3
All endpoints return JSON responses with a standard format:
{
"status": "success" | "error",
"data": { ... },
"message": "Optional message"
}
Table of Contents
- Configuration
- Display Control
- Plugins
- Plugin Store
- System
- Fonts
- Cache
- WiFi
- Streams
- Logs
- Error tracking
- Health
- Schedule (dim/power)
- Plugin-specific endpoints
- Starlark Apps
The API blueprint is mounted at
/api/v3(web_interface/app.py:199). SSE stream endpoints (/api/v3/stream/*) are defined directly on the Flask app atapp.py:799-809. There are 111 routes total — seeweb_interface/blueprints/api_v3.pyfor the canonical list.
Configuration
Get Main Configuration
GET /api/v3/config/main
Retrieve the complete main configuration file.
Response:
{
"status": "success",
"data": {
"timezone": "America/New_York",
"location": {
"city": "New York",
"state": "NY",
"country": "US"
},
"display": { ... },
"plugin_system": { ... }
}
}
Save Main Configuration
POST /api/v3/config/main
Update the main configuration. Accepts both JSON and form data.
Request Body (JSON):
{
"timezone": "America/New_York",
"city": "New York",
"state": "NY",
"country": "US",
"web_display_autostart": true,
"rows": 32,
"cols": 64,
"chain_length": 2,
"brightness": 90
}
Response:
{
"status": "success",
"message": "Configuration saved successfully"
}
Get Schedule Configuration
GET /api/v3/config/schedule
Retrieve the current schedule configuration.
Response:
{
"status": "success",
"data": {
"enabled": true,
"mode": "global",
"start_time": "07:00",
"end_time": "23:00"
}
}
Per-day mode response:
{
"status": "success",
"data": {
"enabled": true,
"mode": "per-day",
"days": {
"monday": {
"enabled": true,
"start_time": "07:00",
"end_time": "23:00"
},
"tuesday": { ... }
}
}
}
Save Schedule Configuration
POST /api/v3/config/schedule
Update the schedule configuration.
Request Body (Global mode):
{
"enabled": true,
"mode": "global",
"start_time": "07:00",
"end_time": "23:00"
}
Request Body (Per-day mode):
{
"enabled": true,
"mode": "per-day",
"monday_enabled": true,
"monday_start": "07:00",
"monday_end": "23:00",
"tuesday_enabled": true,
"tuesday_start": "08:00",
"tuesday_end": "22:00"
}
Response:
{
"status": "success",
"message": "Schedule configuration saved successfully"
}
Get Secrets Configuration
GET /api/v3/config/secrets
Retrieve the secrets configuration (API keys, tokens, etc.). Secret values are masked for security.
Response:
{
"status": "success",
"data": {
"weather": {
"api_key": "***"
},
"spotify": {
"client_id": "***",
"client_secret": "***"
}
}
}
Save Raw Configuration
POST /api/v3/config/raw/main
Save raw JSON configuration (advanced use only).
POST /api/v3/config/raw/secrets
Save raw secrets configuration (advanced use only).
Display Control
Get Current Display
GET /api/v3/display/current
Get the current display state and preview image.
Response:
{
"status": "success",
"data": {
"timestamp": 1234567890.123,
"width": 128,
"height": 32,
"image": "base64_encoded_image_data"
}
}
List Display Modes
GET /api/v3/display/modes
Every display mode that can be requested on-demand, with the plugin that owns it. This is the list the force-display dialog offers.
Send the reported plugin_id alongside mode when starting an on-demand
display: /display/on-demand/start falls back to find_plugin_for_mode when
plugin_id is omitted, and that lookup only sees modes declared in a static
manifest — a plugin whose modes are generated (each installed Starlark app is
one) returns 404 there.
Triggers plugin discovery, which is otherwise lazy — so a caller that never opens the dashboard still gets the full list.
Query Parameters:
include_disabled(optional):1to include modes belonging to disabled plugins. They are still valid on-demand targets — the controller enables the plugin for the duration of the request — and are reported with"enabled": false.
Response:
{
"status": "success",
"data": {
"modes": [
{
"mode": "nfl_live",
"plugin_id": "football-scoreboard",
"plugin_name": "Football Scoreboard",
"name": "nfl_live",
"enabled": true
},
{
"mode": "clock-simple",
"plugin_id": "clock-simple",
"plugin_name": "Simple Clock",
"name": "Simple Clock",
"enabled": true
}
]
}
}
name is a label for a dropdown: a single-mode plugin's own name, or the raw
mode string for a multi-mode plugin, since there is no per-mode name anywhere.
On-Demand Display Status
GET /api/v3/display/on-demand/status
Get the current on-demand display state.
Response:
{
"status": "success",
"data": {
"state": {
"active": true,
"plugin_id": "football-scoreboard",
"mode": "nfl_live",
"duration": 45,
"pinned": true,
"status": "running",
"last_updated": 1234567890.123
},
"service": {
"active": true,
"returncode": 0
}
}
}
Start On-Demand Display
POST /api/v3/display/on-demand/start
Request a specific plugin to display on-demand.
Request Body:
{
"plugin_id": "football-scoreboard",
"mode": "nfl_live",
"duration": 45,
"pinned": true,
"start_service": true
}
Parameters:
plugin_id(string, optional): Plugin identifiermode(string, optional): Display mode name (plugin_id inferred if not provided)duration(number, optional): Duration in seconds (0 = until stopped)pinned(boolean, optional): Pin display (pause rotation)start_service(boolean, optional): Auto-start display service if not running (default: true)
Response:
{
"status": "success",
"data": {
"request_id": "uuid-here",
"plugin_id": "football-scoreboard",
"mode": "nfl_live",
"active": true
}
}
Stop On-Demand Display
POST /api/v3/display/on-demand/stop
Stop the current on-demand display.
Request Body:
{
"stop_service": false
}
Parameters:
stop_service(boolean, optional): Also stop the display service (default: false)
Response:
{
"status": "success",
"message": "On-demand display stopped"
}
Plugins
Get Installed Plugins
GET /api/v3/plugins/installed
List all installed plugins with their status and metadata.
Response:
{
"status": "success",
"data": {
"plugins": [
{
"id": "football-scoreboard",
"name": "Football Scoreboard",
"author": "ChuckBuilds",
"category": "Sports",
"description": "NFL and NCAA Football scores",
"tags": ["sports", "football", "nfl"],
"enabled": true,
"verified": true,
"loaded": true,
"last_updated": "2025-01-15T10:30:00Z",
"last_commit": "abc1234",
"last_commit_message": "feat: Add live game updates",
"branch": "main",
"web_ui_actions": []
}
]
}
}
Get Plugin Configuration
GET /api/v3/plugins/config?plugin_id=<plugin_id>
Get configuration for a specific plugin.
Query Parameters:
plugin_id(required): Plugin identifier
Response:
{
"status": "success",
"data": {
"plugin_id": "football-scoreboard",
"config": {
"enabled": true,
"display_duration": 30,
"favorite_teams": ["TB", "DAL"]
}
}
}
Save Plugin Configuration
POST /api/v3/plugins/config
Update plugin configuration.
Request Body:
{
"plugin_id": "football-scoreboard",
"config": {
"enabled": true,
"display_duration": 30,
"favorite_teams": ["TB", "DAL"]
}
}
Response:
{
"status": "success",
"message": "Plugin configuration saved successfully"
}
Get Plugin Schema
GET /api/v3/plugins/schema?plugin_id=<plugin_id>
Get the JSON schema for a plugin's configuration.
Query Parameters:
plugin_id(required): Plugin identifier
Response:
{
"status": "success",
"data": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": true
},
"display_duration": {
"type": "number",
"minimum": 1,
"maximum": 300
}
}
}
}
Toggle Plugin
POST /api/v3/plugins/toggle
Enable or disable a plugin.
Request Body:
{
"plugin_id": "football-scoreboard",
"enabled": true
}
Response:
{
"status": "success",
"message": "Plugin football-scoreboard enabled"
}
Install Plugin
POST /api/v3/plugins/install
Install a plugin from the plugin store.
Request Body:
{
"plugin_id": "football-scoreboard"
}
Response:
{
"status": "success",
"data": {
"operation_id": "uuid-here",
"plugin_id": "football-scoreboard",
"status": "installing"
}
}
Uninstall Plugin
POST /api/v3/plugins/uninstall
Remove an installed plugin.
Request Body:
{
"plugin_id": "football-scoreboard"
}
Response:
{
"status": "success",
"message": "Plugin football-scoreboard uninstalled"
}
Update Plugin
POST /api/v3/plugins/update
Update a plugin to the latest version.
Request Body:
{
"plugin_id": "football-scoreboard"
}
Response:
{
"status": "success",
"data": {
"operation_id": "uuid-here",
"plugin_id": "football-scoreboard",
"status": "updating"
}
}
Install Plugin from URL
POST /api/v3/plugins/install-from-url
Install a plugin directly from a GitHub repository URL.
Request Body:
{
"url": "https://github.com/user/ledmatrix-my-plugin",
"branch": "main",
"plugin_path": null
}
Parameters:
url(required): GitHub repository URLbranch(optional): Branch name (default: "main")plugin_path(optional): Path within repository for monorepo plugins
Response:
{
"status": "success",
"data": {
"operation_id": "uuid-here",
"plugin_id": "my-plugin",
"status": "installing"
}
}
Load Registry from URL
POST /api/v3/plugins/registry-from-url
Load a plugin registry from a GitHub repository URL.
Request Body:
{
"url": "https://github.com/user/ledmatrix-plugins"
}
Response:
{
"status": "success",
"data": {
"plugins": [
{
"id": "plugin-1",
"name": "Plugin One",
"description": "..."
}
]
}
}
Get Plugin Health
GET /api/v3/plugins/health
Get health metrics for all plugins.
Response:
{
"status": "success",
"data": {
"football-scoreboard": {
"status": "healthy",
"last_update": 1234567890.123,
"error_count": 0,
"last_error": null
}
}
}
Get Plugin Health (Single)
GET /api/v3/plugins/health/<plugin_id>
Get health metrics for a specific plugin.
Response:
{
"status": "success",
"data": {
"status": "healthy",
"last_update": 1234567890.123,
"error_count": 0,
"last_error": null
}
}
Reset Plugin Health
POST /api/v3/plugins/health/<plugin_id>/reset
Reset health state for a plugin (manual recovery).
Response:
{
"status": "success",
"message": "Health state reset for plugin football-scoreboard"
}
Get Plugin Metrics
GET /api/v3/plugins/metrics
Get resource usage metrics for all plugins.
Response:
{
"status": "success",
"data": {
"football-scoreboard": {
"update_count": 150,
"display_count": 500,
"avg_update_time": 0.5,
"avg_display_time": 0.1,
"memory_usage": 1024000
}
}
}
Get Plugin Metrics (Single)
GET /api/v3/plugins/metrics/<plugin_id>
Get resource usage metrics for a specific plugin.
Reset Plugin Metrics
POST /api/v3/plugins/metrics/<plugin_id>/reset
Reset metrics for a plugin.
Get/Set Plugin Limits
GET /api/v3/plugins/limits/<plugin_id>
Get rate limits and resource limits for a plugin.
POST /api/v3/plugins/limits/<plugin_id>
Update rate limits and resource limits for a plugin.
Request Body:
{
"max_update_interval": 60,
"max_display_time": 5.0,
"max_memory_mb": 50
}
Get Plugin State
GET /api/v3/plugins/state
Get the current state of all plugins.
Response:
{
"status": "success",
"data": {
"football-scoreboard": {
"state": "loaded",
"enabled": true,
"last_update": 1234567890.123
}
}
}
Reconcile Plugin State
POST /api/v3/plugins/state/reconcile
Reconcile plugin state with configuration (fix inconsistencies).
Response:
{
"status": "success",
"message": "Plugin state reconciled"
}
Get Plugin Operation
GET /api/v3/plugins/operation/<operation_id>
Get status of an async plugin operation (install, update, etc.).
Response:
{
"status": "success",
"data": {
"operation_id": "uuid-here",
"type": "install",
"plugin_id": "football-scoreboard",
"status": "completed",
"progress": 100,
"message": "Installation completed successfully"
}
}
Get Operation History
GET /api/v3/plugins/operation/history?limit=100
Get history of plugin operations.
Query Parameters:
limit(optional): Maximum number of operations to return (default: 100)
Response:
{
"status": "success",
"data": {
"operations": [
{
"operation_id": "uuid-here",
"type": "install",
"plugin_id": "football-scoreboard",
"status": "completed",
"timestamp": 1234567890.123
}
]
}
}
Execute Plugin Action
POST /api/v3/plugins/action
Execute a custom plugin action (defined in plugin's web_ui_actions).
Request Body:
{
"plugin_id": "football-scoreboard",
"action": "refresh_games",
"parameters": {}
}
Reset Plugin Configuration
POST /api/v3/plugins/config/reset
Reset a plugin's configuration to defaults.
Request Body:
{
"plugin_id": "football-scoreboard"
}
Upload Plugin Assets
POST /api/v3/plugins/assets/upload
Upload assets (images, files) for a plugin.
Request: Multipart form data
plugin_id(required): Plugin identifierfile(required): File to uploadasset_type(optional): Type of asset (logo, image, etc.)
Response:
{
"status": "success",
"data": {
"filename": "logo.png",
"path": "plugins/football-scoreboard/assets/logo.png"
}
}
Delete Plugin Asset
POST /api/v3/plugins/assets/delete
Delete a plugin asset.
Request Body:
{
"plugin_id": "football-scoreboard",
"filename": "logo.png"
}
List Plugin Assets
GET /api/v3/plugins/assets/list?plugin_id=<plugin_id>
List all assets for a plugin.
Query Parameters:
plugin_id(required): Plugin identifier
Response:
{
"status": "success",
"data": {
"assets": [
{
"filename": "logo.png",
"path": "plugins/football-scoreboard/assets/logo.png",
"size": 1024
}
]
}
}
Authenticate Spotify
POST /api/v3/plugins/authenticate/spotify
Initiate Spotify authentication flow for music plugin.
Request Body:
{
"plugin_id": "music"
}
Response:
{
"status": "success",
"data": {
"auth_url": "https://accounts.spotify.com/authorize?..."
}
}
Authenticate YouTube Music
POST /api/v3/plugins/authenticate/ytm
Initiate YouTube Music authentication flow.
Request Body:
{
"plugin_id": "music"
}
Upload Calendar Credentials
POST /api/v3/plugins/calendar/upload-credentials
Upload Google Calendar credentials file.
Request: Multipart form data
file(required): credentials.json file
Plugin Store
List Store Plugins
GET /api/v3/plugins/store/list?fetch_commit_info=true
Get list of available plugins from the plugin store.
Query Parameters:
fetch_commit_info(optional): Include commit information (default: false)
Response:
{
"status": "success",
"data": {
"plugins": [
{
"id": "football-scoreboard",
"name": "Football Scoreboard",
"description": "NFL and NCAA Football scores",
"author": "ChuckBuilds",
"category": "Sports",
"version": "1.2.3",
"repository_url": "https://github.com/ChuckBuilds/ledmatrix-football-scoreboard",
"installed": true,
"update_available": false
}
]
}
}
Get GitHub Status
GET /api/v3/plugins/store/github-status
Get GitHub API rate limit status.
Response:
{
"status": "success",
"data": {
"rate_limit": 5000,
"rate_remaining": 4500,
"rate_reset": 1234567890
}
}
Refresh Plugin Store
POST /api/v3/plugins/store/refresh
Force refresh of the plugin store cache.
Response:
{
"status": "success",
"message": "Plugin store refreshed"
}
Get Saved Repositories
GET /api/v3/plugins/saved-repositories
Get list of saved custom plugin repositories.
Response:
{
"status": "success",
"data": {
"repositories": [
{
"url": "https://github.com/user/ledmatrix-plugins",
"name": "Custom Plugins",
"auto_load": true
}
]
}
}
Save Repository
POST /api/v3/plugins/saved-repositories
Save a custom plugin repository for easy access.
Request Body:
{
"url": "https://github.com/user/ledmatrix-plugins",
"name": "Custom Plugins",
"auto_load": true
}
Delete Saved Repository
DELETE /api/v3/plugins/saved-repositories
Remove a saved repository.
Request Body:
{
"url": "https://github.com/user/ledmatrix-plugins"
}
System
Get System Status
GET /api/v3/system/status
Get system status and metrics.
Response:
{
"status": "success",
"data": {
"timestamp": 1234567890.123,
"uptime": "Running",
"service_active": true,
"cpu_percent": 25.5,
"memory_used_percent": 45.2,
"cpu_temp": 45.0,
"disk_used_percent": 60.0
}
}
Get System Version
GET /api/v3/system/version
Get LEDMatrix repository version.
Response:
{
"status": "success",
"data": {
"version": "v2.4-10-g1234567"
}
}
Execute System Action
POST /api/v3/system/action
Execute system-level actions.
Request Body:
{
"action": "start_display",
"mode": "nfl_live"
}
Available Actions:
start_display: Start the display servicestop_display: Stop the display servicerestart_display_service: Restart the display servicerestart_web_service: Restart the web interface serviceenable_autostart: Enable display service autostartdisable_autostart: Disable display service autostartreboot_system: Reboot the Raspberry Pigit_pull: Update code from git repository
Response:
{
"status": "success",
"message": "Action start_display completed",
"returncode": 0,
"stdout": "...",
"stderr": ""
}
Fonts
Get Font Catalog
GET /api/v3/fonts/catalog
Get list of available fonts.
Response:
{
"status": "success",
"data": {
"fonts": [
{
"family": "Press Start 2P",
"files": ["PressStart2P-Regular.ttf"],
"sizes": [8, 10, 12]
}
]
}
}
Get Font Tokens
GET /api/v3/fonts/tokens
Get font size token definitions.
Response:
{
"status": "success",
"data": {
"tokens": {
"xs": 6,
"sm": 8,
"md": 10,
"lg": 12,
"xl": 16
}
}
}
Get Font Overrides
GET /api/v3/fonts/overrides
Get current font overrides.
Response:
{
"status": "success",
"data": {
"overrides": {
"plugin.football-scoreboard.title": {
"family": "Arial",
"size_px": 12
}
}
}
}
Set Font Override
POST /api/v3/fonts/overrides
Set a font override for a specific element.
Request Body:
{
"element_key": "plugin.football-scoreboard.title",
"family": "Arial",
"size_px": 12
}
Delete Font Override
DELETE /api/v3/fonts/overrides/<element_key>
Remove a font override.
Upload Font
POST /api/v3/fonts/upload
Upload a custom font file.
Request: Multipart form data
file(required): Font file (.ttf, .otf, etc.)
Response:
{
"status": "success",
"data": {
"family": "Custom Font",
"filename": "custom-font.ttf"
}
}
Delete Font
DELETE /api/v3/fonts/<font_family>
Delete an uploaded font.
Font Preview
GET /api/v3/fonts/preview?family=<font_family>&text=<sample>
Render a small preview image of a font for use in the web UI font picker.
Cache
List Cache Entries
GET /api/v3/cache/list
List all cache entries.
Response:
{
"status": "success",
"data": {
"entries": [
{
"key": "weather_current_12345",
"age": 300,
"size": 1024
}
]
}
}
Delete Cache Entry
POST /api/v3/cache/delete
Delete a cache entry or clear all cache.
Request Body:
{
"key": "weather_current_12345"
}
Or clear all:
{
"clear_all": true
}
WiFi
Get WiFi Status
GET /api/v3/wifi/status
Get current WiFi connection status.
Response:
{
"status": "success",
"data": {
"connected": true,
"ssid": "MyNetwork",
"ip_address": "192.168.1.100",
"signal_strength": -50
}
}
Scan WiFi Networks
GET /api/v3/wifi/scan
Scan for available WiFi networks.
Response:
{
"status": "success",
"data": {
"networks": [
{
"ssid": "MyNetwork",
"signal_strength": -50,
"encryption": "WPA2",
"connected": true
}
]
}
}
Connect to WiFi
POST /api/v3/wifi/connect
Connect to a WiFi network.
Request Body:
{
"ssid": "MyNetwork",
"password": "mypassword"
}
Response:
{
"status": "success",
"message": "Connecting to MyNetwork..."
}
Disconnect from WiFi
POST /api/v3/wifi/disconnect
Disconnect from current WiFi network.
Enable Access Point Mode
POST /api/v3/wifi/ap/enable
Enable WiFi access point mode.
Disable Access Point Mode
POST /api/v3/wifi/ap/disable
Disable WiFi access point mode.
Get Auto-Enable AP Status
GET /api/v3/wifi/ap/auto-enable
Get access point auto-enable configuration.
Response:
{
"status": "success",
"data": {
"auto_enable": true,
"timeout_seconds": 300
}
}
Set Auto-Enable AP
POST /api/v3/wifi/ap/auto-enable
Configure access point auto-enable settings.
Request Body:
{
"auto_enable": true,
"timeout_seconds": 300
}
Streams
System Statistics Stream
GET /api/v3/stream/stats
Server-Sent Events (SSE) stream for real-time system statistics.
Response: SSE stream
data: {"cpu_percent": 25.5, "memory_used_percent": 45.2, ...}
data: {"cpu_percent": 26.0, "memory_used_percent": 45.3, ...}
Display Preview Stream
GET /api/v3/stream/display
Server-Sent Events (SSE) stream for real-time display preview images.
Response: SSE stream with base64-encoded images
data: {"image": "base64_data_here", "timestamp": 1234567890.123}
Service Logs Stream
GET /api/v3/stream/logs
Server-Sent Events (SSE) stream for real-time service logs.
Response: SSE stream
data: {"level": "INFO", "message": "Plugin loaded", "timestamp": 1234567890.123}
Logs
Get Logs
GET /api/v3/logs?limit=100&level=INFO
Get recent log entries.
Query Parameters:
limit(optional): Maximum number of log entries (default: 100)level(optional): Filter by log level (DEBUG, INFO, WARNING, ERROR)
Response:
{
"status": "success",
"data": {
"logs": [
{
"level": "INFO",
"message": "Plugin loaded: football-scoreboard",
"timestamp": 1234567890.123
}
]
}
}
Error tracking
Get Error Summary
GET /api/v3/errors/summary
Aggregated counts of recent errors across all plugins and core components, used by the web UI's error indicator.
Get Plugin Errors
GET /api/v3/errors/plugin/<plugin_id>
Recent errors for a specific plugin.
Clear Errors
POST /api/v3/errors/clear
Clear the in-memory error aggregator.
Health
Health Check
GET /api/v3/health
Lightweight liveness check used by the WiFi monitor and external monitoring tools.
Schedule (dim/power)
Get Dim Schedule
GET /api/v3/config/dim-schedule
Read the dim/power schedule that automatically reduces brightness or turns the display off at configured times.
Update Dim Schedule
POST /api/v3/config/dim-schedule
Update the dim schedule. Body matches the structure returned by GET.
Plugin-specific endpoints
A handful of endpoints belong to individual built-in or shipped plugins.
Calendar
GET /api/v3/plugins/calendar/list-calendars
List the calendars available on the authenticated Google account. Used by the calendar plugin's config UI.
Of The Day
POST /api/v3/plugins/of-the-day/json/upload
Upload a JSON data file for the Of-The-Day plugin's category data.
POST /api/v3/plugins/of-the-day/json/delete
Delete a previously uploaded Of-The-Day data file.
Plugin Static Assets
GET /api/v3/plugins/<plugin_id>/static/<path:file_path>
Serve a static asset (image, font, etc.) from a plugin's directory. Used internally by the web UI to render plugin previews and icons.
Starlark Apps
The Starlark plugin lets you run Tronbyt Starlark apps on the matrix. These endpoints expose its UI.
Status
GET /api/v3/starlark/status
Returns whether the Pixlet binary is installed and the Starlark plugin is operational.
Install Pixlet
POST /api/v3/starlark/install-pixlet
Download and install the Pixlet binary on the Pi.
Apps
GET /api/v3/starlark/apps — list installed Starlark apps
GET /api/v3/starlark/apps/<app_id> — get app details
DELETE /api/v3/starlark/apps/<app_id> — uninstall an app
GET /api/v3/starlark/apps/<app_id>/config — get app config schema
PUT /api/v3/starlark/apps/<app_id>/config — update app config
POST /api/v3/starlark/apps/<app_id>/render — render app to a frame
POST /api/v3/starlark/apps/<app_id>/toggle — enable/disable app
Repository (Tronbyt community apps)
GET /api/v3/starlark/repository/categories — browse categories
GET /api/v3/starlark/repository/browse?category=<cat> — browse apps
POST /api/v3/starlark/repository/install — install an app from the
community repository
Upload custom app
POST /api/v3/starlark/upload
Upload a custom Starlark .star file as a new app.
Error Responses
All endpoints may return error responses in the following format:
{
"status": "error",
"message": "Error description",
"error_code": "ERROR_CODE",
"details": "Additional error details (optional)"
}
Common HTTP Status Codes:
200: Success400: Bad Request (invalid parameters)404: Not Found (resource doesn't exist)500: Internal Server Error503: Service Unavailable (feature not available)
See Also
- Plugin API Reference - API for plugin developers
- Plugin Development Guide - Complete plugin development guide
- Web Interface README - Web interface documentation