Assets API
Overview
LedFx provides secure API endpoints for managing image assets from two sources:
User Assets (
GET/POST/DELETE /api/assets) - User-uploaded images stored in the configuration directory under.ledfx/assets/with full read/write access and comprehensive security controls including path traversal protection, content validation, and size limits.Built-in Assets (
GET /api/assets_fixed) - Pre-installed GIFs and images bundled with LedFx inledfx_assets/gifs/. These are read-only and provide default assets for effects.
Asset Storage
User Assets are stored in:
{config_dir}/assets/
Where {config_dir} is the LedFx configuration directory (e.g., ~/.ledfx on Linux/macOS or %USERPROFILE%\.ledfx on Windows).
Built-in Assets are bundled with LedFx installation:
{ledfx_assets}/gifs/
Where {ledfx_assets} is the LedFx installation assets directory containing pre-installed GIFs and images.
Example structure:
The directory structure under assets/ is arbitrary and determined by the frontend or application using the API. Assets can be organized in any folder hierarchy as needed.
.ledfx/
assets/ # User-uploaded assets (read/write)
icon.png
backgrounds/
galaxy.jpg
nebula.webp
effects/
fire/
texture.png
ledfx_assets/
gifs/ # Built-in assets (read-only)
skull.gif
catfixed.gif
bumble.gif
pixelart/
animation.gif
Asset Source Selection
LedFx provides clear separation between user assets and built-in assets using an explicit prefix system:
Path Syntax
User assets: Use path without prefix
Example:
"backgrounds/galaxy.jpg"→{config_dir}/assets/backgrounds/galaxy.jpgExample:
"skull.gif"→{config_dir}/assets/skull.gif
Built-in assets: Use
builtin://prefixExample:
"builtin://skull.gif"→{ledfx_assets}/gifs/skull.gifExample:
"builtin://pixelart/dj_bird.gif"→{ledfx_assets}/gifs/pixelart/dj_bird.gif
Endpoint Support
Endpoint |
User Assets |
Built-in Assets |
|---|---|---|
|
✅ List only |
❌ |
|
❌ |
✅ List only |
|
✅ (no prefix) |
✅ ( |
|
✅ (no prefix) |
✅ ( |
|
✅ (no prefix) |
✅ ( |
|
✅ (no prefix) |
✅ ( |
|
✅ |
❌ Read-only |
|
✅ |
❌ Read-only |
Supported Image Formats
PNG (
.png)JPEG (
.jpg,.jpeg)GIF (
.gif)WebP (
.webp)BMP (
.bmp)TIFF (
.tiff,.tif)ICO (
.ico)
Security Features
Path Traversal Protection
All paths are validated to prevent directory traversal attacks:
Rejected patterns:
../../../etc/passwd/absolute/pathC:\Windows\system32\\network\sharefile:///etc/passwdNull bytes (
\0)Unicode normalization attacks
Content Validation
Files are validated using Pillow to ensure they contain actual image data:
Extension alone is not sufficient
MIME type must be in allowed list (image/png, image/jpeg, image/gif, etc.)
File content must parse as valid image with Pillow
PIL format must match allowed formats
Extension must match actual file content
Corrupted or fake images are rejected
Size Limits
Default maximum file size is 10MB. This limit accounts for animated GIFs and WebP files which can be larger than static images.
Automatic Cleanup
When deleting assets, empty parent directories are automatically removed.
API Endpoints
List User Assets
Get a list of all user-uploaded assets with metadata from {config_dir}/assets/.
Endpoint: GET /api/assets
Success Response (bare response - no status wrapper):
{
"assets": [
{
"path": "icon.png",
"size": 15234,
"modified": "2025-11-30T10:30:00+00:00",
"width": 512,
"height": 512,
"format": "PNG",
"n_frames": 1,
"is_animated": false
},
{
"path": "backgrounds/galaxy.jpg",
"size": 234567,
"modified": "2025-11-28T14:22:15+00:00",
"width": 1920,
"height": 1080,
"format": "JPEG",
"n_frames": 1,
"is_animated": false
}
]
}
Metadata Fields:
path(string) - Relative path to the assetsize(integer) - File size in bytesmodified(string) - ISO 8601 timestamp of last modification (UTC)width(integer) - Image width in pixels (0 if unreadable)height(integer) - Image height in pixels (0 if unreadable)format(string|null) - Image format detected by PIL (e.g., “PNG”, “JPEG”, “GIF”, “WEBP”)n_frames(integer) - Number of frames (1 for static images, >1 for animated GIF/WebP)is_animated(boolean) - True if the image contains multiple frames (animated GIF or WebP)
Entries sorted by: Path name (alphabetical)
List Built-in Assets
Get a list of all built-in assets with metadata from {ledfx_assets}/gifs/.
Endpoint: GET /api/assets_fixed
Success Response (bare response - no status wrapper):
{
"assets": [
{
"path": "skull.gif",
"size": 123456,
"modified": "2025-10-15T08:30:00+00:00",
"width": 64,
"height": 64,
"format": "GIF",
"n_frames": 12,
"is_animated": true
},
{
"path": "catfixed.gif",
"size": 234567,
"modified": "2025-10-15T08:30:00+00:00",
"width": 48,
"height": 48,
"format": "GIF",
"n_frames": 8,
"is_animated": true
},
{
"path": "pixelart/dj_bird.gif",
"size": 345678,
"modified": "2025-10-15T08:30:00+00:00",
"width": 128,
"height": 128,
"format": "GIF",
"n_frames": 24,
"is_animated": true
}
]
}
Metadata Fields:
path(string) - Relative path to the built-in asset fromledfx_assets/gifs/Root-level:
"skull.gif"(refers toledfx_assets/gifs/skull.gif)Subdirectory:
"pixelart/dj_bird.gif"(refers toledfx_assets/gifs/pixelart/dj_bird.gif)
size(integer) - File size in bytesmodified(string) - ISO 8601 timestamp of last modification (UTC)width(integer) - Image width in pixels (0 if unreadable)height(integer) - Image height in pixels (0 if unreadable)format(string|null) - Image format detected by PIL (e.g., “PNG”, “JPEG”, “GIF”, “WEBP”)n_frames(integer) - Number of frames (1 for static images, >1 for animated GIF/WebP)is_animated(boolean) - True if the image contains multiple frames (animated GIF or WebP)
Entries sorted by: Path name (alphabetical)
Notes:
Built-in assets are read-only - they can be used in effects but cannot be modified or deleted via the API
This endpoint lists only built-in assets - use
GET /api/assetsto list user-uploaded assetsTo guarantee accessing a built-in asset (not a user override), check this list first and verify the user hasn’t uploaded a file with the same path
Upload Asset
Upload a new image asset. Requires multipart/form-data encoding.
Endpoint: POST /api/assets
Request Parameters:
file(file) - The image file to uploadpath(string) - Relative path where asset should be stored (e.g.,icons/led.png)
Success Response:
{
"status": "success",
"data": {
"path": "icons/led.png"
}
}
Error Response:
{
"status": "failed",
"payload": {
"type": "error",
"reason": "Extension .txt is not allowed"
}
}
Validation Rules:
File must be a valid image (content-verified)
Extension must be in allowed list
Path must not contain traversal sequences (
.., absolute paths, etc.)File size must not exceed 10MB
Uploading to an existing path will replace the file
Automatic Cache Invalidation: When an asset is uploaded (new or replacing existing), any cached thumbnails for that asset path are automatically cleared. This ensures that subsequent thumbnail requests will generate fresh thumbnails from the new image data, preventing stale cached thumbnails from being served.
Example:
curl -X POST http://localhost:8888/api/assets -F "file=@/path/to/image.png" -F "path=icons/led.png"
Download Asset
Download a specific asset file (user, built-in, or remote URL).
Methods: Both GET and POST supported
GET - Browser-friendly, query parameters
POST - Programmatic, JSON body
Asset Sources:
User assets: No prefix →
{config_dir}/assets/{path}Built-in assets:
builtin://prefix →{ledfx_assets}/gifs/{path}Remote URLs:
http://orhttps://→ Automatically fetched and cached with security validation (SSRF protection, size limits, content validation)
GET /api/assets/download
Browser-friendly method using query parameters.
Query Parameters:
path(string, required) - Asset pathUser:
"icons/led.png"Built-in:
"builtin://skull.gif"Cached URL:
"https://example.com/image.gif"
Response:
Success: Binary image data with
Content-Typeheader (HTTP 200)Error: JSON error message (HTTP 200)
Examples:
# User asset
curl "http://localhost:8888/api/assets/download?path=icons/led.png" --output led.png
# Built-in asset
curl "http://localhost:8888/api/assets/download?path=builtin://skull.gif" --output skull.gif
# Cached URL
curl "http://localhost:8888/api/assets/download?path=https://example.com/image.gif" --output image.gif
Browser Usage:
<!-- User asset -->
<img src="http://localhost:8888/api/assets/download?path=icons/led.png" alt="LED Icon">
<!-- Built-in asset -->
<img src="http://localhost:8888/api/assets/download?path=builtin://skull.gif" alt="Skull">
<!-- Download link -->
<a href="http://localhost:8888/api/assets/download?path=galaxy.jpg" download>Download</a>
POST /api/assets/download
Programmatic method using JSON body.
Request Body:
{
"path": "icons/led.png" // User asset
}
{
"path": "builtin://skull.gif" // Built-in asset
}
{
"path": "https://example.com/image.gif" // Cached URL
}
Response:
Success: Binary image data with
Content-Typeheader (HTTP 200)Error: JSON error message (HTTP 200)
Examples:
# User asset
curl -X POST http://localhost:8888/api/assets/download \
-H "Content-Type: application/json" \
-d '{"path": "icons/led.png"}' \
--output led.png
# Built-in asset
curl -X POST http://localhost:8888/api/assets/download \
-H "Content-Type: application/json" \
-d '{"path": "builtin://skull.gif"}' \
--output skull.gif
# Cached URL
curl -X POST http://localhost:8888/api/assets/download \
-H "Content-Type: application/json" \
-d '{"path": "https://example.com/image.gif"}' \
--output image.gif
Error Response:
{
"status": "failed",
"payload": {
"type": "error",
"reason": "Asset not found: icons/led.png"
}
}
Download Asset Thumbnail
Generate a thumbnail for an asset (user, built-in, or remote URL).
Thumbnail Caching: Generated thumbnails are automatically cached for improved performance. The cache key includes the asset path and all parameters (size, dimension, animated), so different thumbnail variations are cached separately. Cached thumbnails follow the same LRU eviction policy as remote images and are subject to the same cache size/count limits.
Methods:
GET - Browser-friendly, query parameters
POST - Programmatic, JSON body
Asset Sources:
User assets: No prefix →
{config_dir}/assets/{path}Built-in assets:
builtin://prefix →{ledfx_assets}/gifs/{path}Remote URLs:
http://orhttps://→ Automatically fetched and cached with security validation
GET /api/assets/thumbnail
Browser-friendly method using query parameters.
Query Parameters:
path(string, required) - Asset pathUser:
"backgrounds/galaxy.jpg"Built-in:
"builtin://skull.gif"Cached URL:
"https://example.com/image.gif"
size(integer, optional) - Dimension in pixels (16-512, default: 128)dimension(string, optional) - Sizing mode (default: “max”)"max"- Size to longest axis (maintains aspect ratio)"width"- Set width to size, scale height proportionally"height"- Set height to size, scale width proportionally
animated(boolean, optional) - Preserve animation (default: true)"true"- Return animated WebP for GIF/WebP"false"- Return static PNG of first frame
force_refresh(boolean, optional) - Force regeneration bypassing cache (default: false)"true"- Clear cache and regenerate thumbnail"false"- Use cached thumbnail if available
Response:
Success: Thumbnail image (HTTP 200)
Static: PNG format (
image/png)Animated: WebP format (
image/webp)
Error: JSON error message (HTTP 200)
Examples:
# Default 128px thumbnail (user asset)
curl "http://localhost:8888/api/assets/thumbnail?path=backgrounds/galaxy.jpg" --output thumb.png
# 64px animated thumbnail (built-in asset)
curl "http://localhost:8888/api/assets/thumbnail?path=builtin://skull.gif&size=64" --output skull-thumb.webp
# 256px width-constrained thumbnail
curl "http://localhost:8888/api/assets/thumbnail?path=image.png&size=256&dimension=width" --output thumb-256w.png
# Static PNG of first frame
curl "http://localhost:8888/api/assets/thumbnail?path=builtin://skull.gif&animated=false" --output skull-static.png
# Force regenerate thumbnail (bypass cache)
curl "http://localhost:8888/api/assets/thumbnail?path=backgrounds/galaxy.jpg&force_refresh=true" --output fresh-thumb.png
# Cached URL with custom size
curl "http://localhost:8888/api/assets/thumbnail?path=https://example.com/image.gif&size=200" --output remote-thumb.webp
Browser Usage:
<!-- Default 128px thumbnail -->
<img src="http://localhost:8888/api/assets/thumbnail?path=backgrounds/galaxy.jpg" alt="Galaxy">
<!-- 64px animated thumbnail -->
<img src="http://localhost:8888/api/assets/thumbnail?path=builtin://skull.gif&size=64" alt="Skull">
<!-- Static first frame -->
<img src="http://localhost:8888/api/assets/thumbnail?path=animation.gif&animated=false" alt="Static">
<!-- Width-constrained thumbnail -->
<img src="http://localhost:8888/api/assets/thumbnail?path=icon.png&size=200&dimension=width" alt="Icon">
<!-- Thumbnail grid with different sizes -->
<div class="grid">
<img src="http://localhost:8888/api/assets/thumbnail?path=image1.jpg&size=128" alt="Image 1">
<img src="http://localhost:8888/api/assets/thumbnail?path=image2.jpg&size=128" alt="Image 2">
<img src="http://localhost:8888/api/assets/thumbnail?path=builtin://catfixed.gif&size=128" alt="Cat">
</div>
POST /api/assets/thumbnail
Programmatic method using JSON body.
Request Body:
{
"path": "backgrounds/galaxy.jpg", // Required: asset path
"size": 256, // Optional: 16-512, default: 128
"dimension": "width", // Optional: "max"|"width"|"height", default: "max"
"animated": true, // Optional: preserve animation, default: true
"force_refresh": false // Optional: force regeneration, default: false
}
Parameters:
path(string, required) - Asset pathUser:
"backgrounds/galaxy.jpg"Built-in:
"builtin://skull.gif"Cached URL:
"https://example.com/image.gif"
size(integer, optional) - Dimension in pixels (16-512, default: 128)dimension(string, optional) - Sizing mode (default: “max”)"max"- Size to longest axis (maintains aspect ratio)"width"- Set width to size, scale height proportionally"height"- Set height to size, scale width proportionally
animated(boolean, optional) - Preserve animation (default: true)true- Return animated WebP for GIF/WebPfalse- Return static PNG of first frame
force_refresh(boolean, optional) - Force regeneration bypassing cache (default: false)true- Clear cache and regenerate thumbnailfalse- Use cached thumbnail if available
Response:
Success: Thumbnail image (HTTP 200)
Static: PNG format (
image/png)Animated: WebP format (
image/webp)
Error: JSON error message (HTTP 200)
Examples:
# Default 128px thumbnail (user asset)
curl -X POST http://localhost:8888/api/assets/thumbnail \
-H "Content-Type: application/json" \
-d '{"path": "backgrounds/galaxy.jpg"}' \
--output thumb.png
# 64px animated thumbnail (built-in asset)
curl -X POST http://localhost:8888/api/assets/thumbnail \
-H "Content-Type: application/json" \
-d '{"path": "builtin://skull.gif", "size": 64}' \
--output skull-thumb.webp
# 256px width-constrained thumbnail
curl -X POST http://localhost:8888/api/assets/thumbnail \
-H "Content-Type: application/json" \
-d '{"path": "image.png", "size": 256, "dimension": "width"}' \
--output thumb-256w.png
# Static PNG of first frame
curl -X POST http://localhost:8888/api/assets/thumbnail \
-H "Content-Type: application/json" \
-d '{"path": "builtin://skull.gif", "animated": false}' \
--output skull-static.png
# Force regenerate thumbnail (bypass cache)
curl -X POST http://localhost:8888/api/assets/thumbnail \
-H "Content-Type: application/json" \
-d '{"path": "backgrounds/galaxy.jpg", "force_refresh": true}' \
--output fresh-thumb.png
Error Response:
{
"status": "failed",
"payload": {
"type": "error",
"reason": "Asset not found: backgrounds/galaxy.jpg"
}
}
Delete Asset
Delete a user asset and clean up empty directories.
Endpoint: DELETE /api/assets
Query Parameters (recommended):
DELETE /api/assets?path=icons/led.png
OR Request Body (alternative):
{
"path": "icons/led.png"
}
Response:
Success: Confirmation with deleted path (HTTP 200)
Error: JSON error message (HTTP 200)
Examples:
# Query parameter (recommended)
curl -X DELETE "http://localhost:8888/api/assets?path=icons/led.png"
# JSON body (alternative)
curl -X DELETE http://localhost:8888/api/assets \
-H "Content-Type: application/json" \
-d '{"path": "icons/led.png"}'
Success Response:
{
"status": "success",
"data": {
"deleted": true,
"path": "icons/led.png"
}
}
Error Response:
{
"status": "failed",
"payload": {
"type": "error",
"reason": "Asset not found: icons/missing.png"
}
}
Note: Empty parent directories are automatically removed after deleting an asset.
Error Handling
All endpoints return HTTP 200 with status information in the JSON payload to support frontend notifications:
Success:
{
"status": "success",
"data": { ... }
}
Failure:
{
"status": "failed",
"payload": {
"type": "error",
"reason": "Detailed error message"
}
}
Note: The download endpoint returns binary image data on success, or the standard JSON error response on failure (both with HTTP 200 status).