Skip to content
Your frame:

API Reference

The Wallie server exposes a REST API over HTTP. All endpoints are available on the port your server is running on (default 9000).

No authentication is required — the API is designed for local network use. Don’t expose it to the internet.

Each frame gets a short ID assigned automatically by the server the first time it checks in. The desktop app doesn’t display this ID anywhere in the UI — the easiest way to find yours is:

Terminal window
curl -s http://192.168.1.42:9000/admin/api/frames | python3 -m json.tool

Look for the "id" field on each frame in the response.


Quick health check. Returns server version and basic counts.

Terminal window
curl http://192.168.1.42:9000/
{
"version": "1.9.1",
"running": true,
"port": 9000,
"sources": 2,
"frames": 3,
"total_images": 847
}

Returns 200 OK with {"healthy": true}. Useful for uptime monitors.

Full server status including image index state and timezone info.

{
"version": "1.9.1",
"running": true,
"frames_count": 3,
"sources_count": 2,
"total_images": 847,
"server_time": "2026-05-06T14:23:00.000Z",
"index": {
"is_scanning": false,
"initial_scan_complete": true,
"image_count": 847
}
}

Returns all registered frames with their current status. Use this to find your frame IDs.

Terminal window
curl http://192.168.1.42:9000/admin/api/frames
{
"frames": [
{
"id": "35506C",
"name": "Frame 35506C",
"battery_percentage": 72,
"usb_power_connected": false,
"firmware_version": "2.4.1",
"next_check_in": 1746540000,
"seconds_until_check_in": 3420,
"current_display": {
"image_name": "vacation-2024-001.jpg",
"served_at": "2026-05-06T10:00:00.000Z"
}
}
]
}
FieldDescription
idUnique identifier assigned by the server when the frame first checks in.
battery_percentage0–100. Only present after the frame has checked in at least once.
usb_power_connectedtrue if the frame is currently charging.
next_check_inUnix timestamp of the frame’s next scheduled check-in.
seconds_until_check_inSeconds until next check-in (convenience field).
current_display.image_nameFilename of the photo currently on the display.

Same as above for a single frame.

Terminal window
curl http://192.168.1.42:9000/admin/api/frames/35506C

Update a frame’s settings, including per-source photo order.

Terminal window
curl -X PUT http://192.168.1.42:9000/admin/api/frames/35506C \
-H "Content-Type: application/json" \
-d '{"refresh_minutes": 120}'

To change the photo rotation order for one or more sources, include a source_configs array. Each entry must have a sourceId matching an existing source and a sort_mode value:

sort_modeBehaviour
lruRotate All — shows every photo before repeating. Default.
randomShuffle — picks randomly each rotation.
newest_firstMost recently added photos first.
oldest_firstOldest photos first.
Terminal window
curl -X PUT http://192.168.1.42:9000/admin/api/frames/35506C \
-H "Content-Type: application/json" \
-d '{
"source_configs": [
{
"sourceId": "family-photos",
"sourceType": "local_folder",
"label": "Family Photos",
"enabled": true,
"weight": 1.0,
"timeHint": "anytime",
"sort_mode": "random",
"config": {}
}
]
}'

To find your sourceId values, check the sources list:

Terminal window
curl -s http://192.168.1.42:9000/admin/api/sources | python3 -m json.tool

Remove a frame from the server.

Add a photo source to a frame. If the frame already has a source with the same source_id, it is replaced.

Terminal window
curl -X POST http://192.168.1.42:9000/admin/api/frames/35506C/sources \
-H "Content-Type: application/json" \
-d '{"source_id": "family-photos", "sort_mode": "newest_first"}'

This endpoint accepts POST only — to see which sources a frame currently has, use GET /admin/api/frames/{id}.

Get, set, or clear a pinned image for a frame. A pinned image is shown instead of the normal rotation until unpinned.

Terminal window
# pin an image (the path must exist on the server, otherwise 404)
curl -X POST http://192.168.1.42:9000/admin/api/frames/35506C/pin \
-H "Content-Type: application/json" \
-d '{"image_path": "/photos/family/vacation-2024-001.jpg"}'
# check the current pin
curl http://192.168.1.42:9000/admin/api/frames/35506C/pin
# → {"pinned": true, "pinned_image_path": "/photos/family/vacation-2024-001.jpg"}
# unpin — the frame returns to normal rotation
curl -X DELETE http://192.168.1.42:9000/admin/api/frames/35506C/pin

The frame picks up the change at its next scheduled check-in.


List all configured photo sources.

{
"sources": [
{
"id": "family-photos",
"type": "local_folder",
"path": "/photos/family",
"enabled": true,
"image_count": 412
}
]
}

Add a new source. The server rescans its folders immediately, so the new source’s photos are available as soon as the request returns. Passing an id that already exists replaces that source.

Terminal window
curl -X POST http://192.168.1.42:9000/admin/api/sources \
-H "Content-Type: application/json" \
-d '{"id": "vacation", "type": "local_folder", "path": "/photos/vacation", "enabled": true}'

If you omit id, the server generates one — capture it from the response if your script needs to reference the source later.

Remove a source.


List indexed images. Supports filtering:

Query paramDescription
source_idOnly return images from this source. Omit to list all sources.
show_alternatestrue to include burst-group alternates that are normally hidden behind the best pick.

The response is not paginated — large libraries return everything in one response.

Upload a photo directly to a source folder.

Terminal window
curl -X POST \
"http://192.168.1.42:9000/admin/api/upload?source_id=family-photos&filename=photo.jpg" \
--data-binary @photo.jpg

Delete an image file from disk and remove it from the library. Takes the full file path, which must be inside a configured source folder (anything else returns 403).

Terminal window
curl -X DELETE \
"http://192.168.1.42:9000/admin/api/delete_image?path=/photos/family/photo.jpg"

Fetch a JPEG thumbnail for an image. Takes the full file path; size is the longest edge in pixels (default 400).

Terminal window
curl "http://192.168.1.42:9000/admin/api/thumbnail?path=/photos/family/photo.jpg&size=200"

If the server is busy generating other thumbnails it returns 503 with a Retry-After header — retry after a couple of seconds.


Returns recent server log output. Useful for debugging frame connectivity.


If you’re generating images with a script (for example an AI pipeline that writes a fresh image for each frame), a few behaviors of the current release are worth knowing:

  • Use a new filename for every generation — don’t overwrite a file in place. The server detects “something new to show” by filename, so overwriting image.png with new content won’t trigger a redraw, even if the image is pinned.
  • Avoid names that differ only by a trailing number (image_1.png, image_2.png) in the same folder. The duplicate detector treats these as copies of the same photo and hides all but one if two of them land on the same byte size.
  • Keep images above 100KB. The scanner skips smaller files as probable thumbnails, silently — a folder whose only file is under 100KB reports zero images.
  • “Newest first” doesn’t hold the newest image. Rotation always excludes the photo currently on the display, so with two or more files in a folder the frame alternates between the newest and second-newest. For a one-image-per-frame setup, keep exactly one file in the folder (write the new file, then delete the old one) or use the pin endpoint instead.

Terminal window
curl -s http://192.168.1.42:9000/admin/api/frames | \
python3 -c "
import json, sys
for f in json.load(sys.stdin)['frames']:
print(f\"{f['name']} → id: {f['id']}\")
"
Terminal window
curl -s http://192.168.1.42:9000/admin/api/frames | \
python3 -c "
import json, sys
for f in json.load(sys.stdin)['frames']:
pct = f.get('battery_percentage', 'unknown')
usb = ' (charging)' if f.get('usb_power_connected') else ''
print(f\"{f['id']}: {pct}%{usb}\")
"
#!/bin/bash
curl -s http://192.168.1.42:9000/admin/api/frames | python3 -c "
import json, sys
for f in json.load(sys.stdin)['frames']:
pct = f.get('battery_percentage')
if pct is not None and pct < 20:
print(f\"LOW BATTERY: {f['id']} is at {pct}%\")
"

Run this from a cron job to get notified before a frame goes dark.


Get or set the timezone used for wake-hour calculations. The desktop app sets this automatically; you only need this if scripting directly against the server.

Terminal window
curl -X POST http://192.168.1.42:9000/admin/api/timezone \
-H "Content-Type: application/json" \
-d '{"offset_seconds": -25200, "name": "America/Los_Angeles"}'