Files
LEDMatrix/docs/REST_API_REFERENCE.md
T
ChuckandClaude Opus 5 e23f1f45d3 feat(starlark,on-demand): the third-party fixes worth taking, plus a Home Assistant MQTT bridge (#538)
* 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>
2026-09-08 08:34:52 -04:00

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

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 at app.py:799-809. There are 111 routes total — see web_interface/blueprints/api_v3.py for 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): 1 to 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 identifier
  • mode (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 URL
  • branch (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 identifier
  • file (required): File to upload
  • asset_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 service
  • stop_display: Stop the display service
  • restart_display_service: Restart the display service
  • restart_web_service: Restart the web interface service
  • enable_autostart: Enable display service autostart
  • disable_autostart: Disable display service autostart
  • reboot_system: Reboot the Raspberry Pi
  • git_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: Success
  • 400: Bad Request (invalid parameters)
  • 404: Not Found (resource doesn't exist)
  • 500: Internal Server Error
  • 503: Service Unavailable (feature not available)

See Also