REST API
The Flask backend runs on port 5005 and exposes REST endpoints for sensor control, storage management, and system info. All endpoints accept and return JSON.
Replace <pi-ip> with your Raspberry Pi's IP address in the examples below.
Authentication
When authentication is enabled (see Settings - Security), the dashboard requires a password to log in. For programmatic access (scripts, curl), use the API token shown in Settings.
Pass the API token as a Bearer header:
curl -H "Authorization: Bearer <token>" http://<pi-ip>:5005/settings
Or as a query parameter (useful for SSE connections):
http://<pi-ip>:5005/events?token=<token>
The /auth/status and /auth/validate endpoints are exempt and always accessible. Requests without a valid token receive a 401 Unauthorized response.
| Category | Key endpoints |
|---|---|
| Sensors | /sensor/configure, /sensor/status, /sensor/enable, /sensor/test |
| Bluetooth | /bluetooth/scan, /bluetooth/pair, /bluetooth/discoverable |
| Storage | /storage/status, /storage/configure, /storage/cleanup |
| Timelapse | /timelapse/status, /timelapse/start, /timelapse/stop |
| Logs | /logs, /logs/scripts, /logs/mediamtx |
| System | /system/alert, /system_info, /system/restart, /system/reboot, /system/update/check, /captive-portal |
| Media | /snapshot, /thumbnail/<file>, /sprite/<file> |
| Archive | /archive/count |
| Auth | /auth/status, /auth/validate, /auth/regenerate, /auth/set-password |
| Settings | /settings |
Sensor endpoints
Configure sensor
curl -X POST http://<pi-ip>:5005/sensor/configure \
-H 'Content-Type: application/json' \
-d '{"type": "reed_switch", "gpio": 22, "enabled": true, "hold_seconds": 10}'
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
type | string | (required) | Sensor type identifier (e.g. reed_switch, pir, vibration) |
gpio | number | sensor default | BCM GPIO pin number (0-27) |
enabled | boolean | true | Whether the sensor is active |
hold_seconds | number | 10 | Seconds to keep recording after sensor releases |
invert_logic | boolean | false | Swap trigger/release - e.g. trigger on LOW instead of HIGH |
calibration | object | {} | Sensor-specific calibration parameters |
Examples with calibration:
# Vibration sensor with pulse threshold
curl -X POST http://<pi-ip>:5005/sensor/configure \
-H 'Content-Type: application/json' \
-d '{"type": "vibration", "gpio": 5, "enabled": true, "calibration": {"pulse_count": 3, "pulse_window": 5}}'
# Hall sensor with inverted logic
curl -X POST http://<pi-ip>:5005/sensor/configure \
-H 'Content-Type: application/json' \
-d '{"type": "hall_digital", "gpio": 13, "enabled": true, "invert_logic": true}'
Enable / disable
curl -X POST http://<pi-ip>:5005/sensor/enable
curl -X POST http://<pi-ip>:5005/sensor/disable
Get status
curl http://<pi-ip>:5005/sensor/status
Returns the current sensor state including enabled, armed, triggered, suppressed, recording_from_sensor, and the active sensor's config.
List sensor types
curl http://<pi-ip>:5005/sensor/types
Returns metadata for all registered sensor types including wiring info and calibration schemas.
Test GPIO wiring
Read the raw pin state without interfering with the running sensor:
curl -X POST http://<pi-ip>:5005/sensor/test \
-H 'Content-Type: application/json' \
-d '{"type": "reed_switch", "gpio": 22}'
Mock sensor
Simulate trigger and release events when the mock sensor is active:
curl -X POST http://<pi-ip>:5005/sensor/mock/trigger
curl -X POST http://<pi-ip>:5005/sensor/mock/release
Bluetooth endpoints
Scan for devices
curl -X POST http://<pi-ip>:5005/bt/scan
Returns a list of nearby Bluetooth devices (scans for ~20 seconds).
Make Pi discoverable
curl -X POST http://<pi-ip>:5005/bt/discoverable \
-H 'Content-Type: application/json' \
-d '{"timeout": 90}'
Makes the Pi visible to nearby devices and waits for an incoming pairing request. The device is automatically paired, trusted, and registered for presence detection.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
timeout | number | 90 | Seconds to stay discoverable before giving up |
Returns 200 with {"device": {"address": "...", "name": "..."}} on successful pairing, or 408 if no device paired within the timeout.
Add / remove tracked devices
# Add (pairs and registers)
curl -X POST http://<pi-ip>:5005/devices/bt/add \
-H 'Content-Type: application/json' \
-d '{"address": "AA:BB:CC:DD:EE:FF", "name": "My Phone"}'
# Remove (unpairs and unregisters)
curl -X POST http://<pi-ip>:5005/devices/bt/remove \
-H 'Content-Type: application/json' \
-d '{"address": "AA:BB:CC:DD:EE:FF"}'
Storage endpoints
Get storage status
curl http://<pi-ip>:5005/storage/status
Returns disk usage, threshold, and whether auto-delete is enabled.
Configure auto-delete
curl -X POST http://<pi-ip>:5005/storage/configure \
-H 'Content-Type: application/json' \
-d '{"enabled": true, "max_percent": 85}'
Manual cleanup
Force an immediate cleanup regardless of schedule:
curl -X POST http://<pi-ip>:5005/storage/cleanup
System endpoints
Restart services
curl -X POST http://<pi-ip>:5005/system/restart
Restarts the security-cam systemd service (Flask API + Astro dashboard). The response is sent before the restart begins (1-second delay). Returns {"message": "Restarting services..."}.
Check for updates
curl http://<pi-ip>:5005/system/update/check
Runs git fetch and compares the local HEAD against the remote branch. Returns:
{
"available": true,
"branch": "main",
"local_commit": "93a56b1a",
"remote_commit": "f2e1a990",
"commits_behind": 3,
"summary": "f2e1a99 feat: add update check\n8b3c012 fix: sensor timeout\n..."
}
When no updates are available, available is false and commits_behind is 0. If the fetch times out (no internet), returns {"available": false, "error": "Timed out - check internet connection"}. The fetch has a 20-second timeout.
Reboot device
curl -X POST http://<pi-ip>:5005/system/reboot
Reboots the Raspberry Pi. The response is sent before the reboot begins (2-second delay). Returns {"message": "Rebooting..."}.
Captive portal
# Get status
curl http://<pi-ip>:5005/captive-portal
# Enable or disable
curl -X POST http://<pi-ip>:5005/captive-portal \
-H 'Content-Type: application/json' \
-d '{"enabled": false}'
GET returns {"enabled": true, "active": true} where enabled is the saved setting and active reflects whether the iptables rule is currently present. POST toggles the setting and the iptables PREROUTING rule that redirects port 80 to the dashboard.
Get current alert levels
curl http://<pi-ip>:5005/system_alert_state
Returns the current system health alert state. The overall field is "ok", "warn", or "critical". The alerts object maps individual metrics (cpu_temp, throttle, storage, sd_health) to their current level. The values object contains the raw metric values used for toast messages.
| Metric | Warning | Critical |
|---|---|---|
| CPU temperature | \u2265 65\u00b0C | \u2265 80\u00b0C |
| Throttle | \u2014 | Any active flag |
| Storage | \u2265 85% | \u2265 95% |
| SD card health | life_time_est \u2265 0x03 | \u2265 0x0B |
Alerts are also pushed in real-time via SSE (system_alert event) when a metric transitions between levels.
Timelapse endpoints
Get timelapse status
curl http://<pi-ip>:5005/timelapse/status
Returns the current timelapse capture state: enabled, interval_minutes, fps, resolution, today_frame_count, and last_capture timestamp.
Configure timelapse
curl -X POST http://<pi-ip>:5005/timelapse/configure \
-H 'Content-Type: application/json' \
-d '{"enabled": true, "interval_minutes": 5, "fps": 24, "resolution": "640:480"}'
| Parameter | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Start/stop timelapse capture |
interval_minutes | number | 5 | Minutes between frame captures (1-60) |
fps | number | 24 | Playback framerate of the stitched video |
resolution | string | "640x480" | Frame resolution: "original", "640:480", or "320:240" |
List timelapse videos
curl http://<pi-ip>:5005/timelapse
Returns a JSON array of stitched timelapse MP4s, newest first. Each entry contains path, date (YYYY-MM-DD), size (bytes), and mtime (ISO timestamp of when the file was created). If a thumbnail exists, thumbnail is also included.
Stream timelapse video
curl "http://<pi-ip>:5005/timelapse/video?path=/path/to/timelapse_20260401.mp4"
Delete timelapse video
curl -X POST http://<pi-ip>:5005/timelapse/delete \
-H 'Content-Type: application/json' \
-d '{"path": "/path/to/timelapse_20260401.mp4"}'
Only files named timelapse_*.mp4 inside the timelapse directory can be deleted.
Snapshot endpoint
Capture a still frame
curl -X POST http://<pi-ip>:5005/snapshot
Grabs a single JPEG frame from the live camera feed and saves it to the recordings directory. Returns {"path": "/path/to/snapshot_20260401_143022.jpg"} on success, or 500 if the capture fails (camera not running, ffmpeg error, timeout).
List snapshots
curl http://<pi-ip>:5005/snapshots
Returns a JSON array of all snapshot files in the recordings directory, newest first. Each entry contains path and size (bytes). Only files matching snapshot_*.jpg are included.
Get snapshot image
curl "http://<pi-ip>:5005/snapshot_image?path=/path/to/snapshot_20260401_143022.jpg"
Returns the JPEG image for a snapshot. Response is cached for 24 hours. Returns 404 if the file doesn't exist.
Delete snapshot
curl -X POST http://<pi-ip>:5005/delete_snapshot \
-H 'Content-Type: application/json' \
-d '{"snapshot_path": "/path/to/snapshot_20260401_143022.jpg"}'
Deletes a snapshot file. Only files named snapshot_*.jpg inside the configured recordings directory can be deleted (path traversal is blocked). Returns 200 on success, 400 for invalid paths, 404 if the file doesn't exist.
Thumbnail endpoint
Get recording thumbnail
curl "http://<pi-ip>:5005/thumbnail?video_path=/path/to/output_20260401_143022.mp4"
Returns the JPEG thumbnail captured at the start of recording. The thumbnail is stored as a .thumb.jpg sidecar alongside the video file. Returns 404 if no thumbnail exists for that recording. Response is cached for 24 hours.
Sprite endpoint
Get recording sprite sheet
curl "http://<pi-ip>:5005/sprite?video_path=/path/to/output_20260401_143022.mp4"
Returns the hover-scrub sprite sheet for a recording. The sprite is a single JPEG containing 10 evenly-spaced frames in a horizontal strip (1600\u00d790 pixels). Generated automatically when recording stops. Returns 404 if no sprite exists for that recording (e.g. very short recordings under 2 seconds, or older recordings made before this feature). Response is cached for 24 hours.
Deploy info endpoint
Get deployment info
curl http://<pi-ip>:5005/deploy_info
Returns {"git_branch": "main", "git_commit": "a1b2c3d"} if the project directory is a git repository. Either field may be null if .git is not present or git is not installed. The same fields are also included in the /system_stats response under the platform info. The branch and commit are displayed in the navbar version pill and on the stats page.
Archive badge endpoint
Count new recordings
curl "http://<pi-ip>:5005/archive/new_count?since=2026-04-01T10:00:00%2B00:00"
Returns {"count": N} - the number of recordings, timelapses, and snapshots created after the given ISO timestamp. Used by the dashboard to show notification badges on the Archive nav link. The count updates in real-time via SSE archive_updated events when new content is added. Returns 0 if since is missing or invalid.
Log endpoints
Get API logs
curl "http://<pi-ip>:5005/logs/api?limit=500&level=ERROR&search=presence"
Returns parsed log entries from the backend log file (newest first). All query parameters are optional.
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 500 | Max entries to return (capped at 2000) |
level | string | (all) | Filter by level: ERROR, WARNING, INFO, DEBUG |
search | string | (none) | Case-insensitive full-text search |
Each entry contains ts, level, source, and message fields.
Get MediaMTX logs
curl "http://<pi-ip>:5005/logs/mediamtx?limit=500&level=ERROR&search=RTSP"
Returns parsed MediaMTX log entries (newest first). Same parameters as /logs/api. MediaMTX level codes (INF, WAR, ERR, DBG) are normalized to standard levels (INFO, WARNING, ERROR, DEBUG).
List script logs
curl http://<pi-ip>:5005/logs/install
Returns all script/setup log files from every log subdirectory (newest first). Each entry contains name (e.g. install/2026-04-01_09-51-07.log), size, and category (e.g. install, update, ap-setup, bluetooth-pairing).
Directories api/ and mediamtx/ are excluded (they have dedicated parsed endpoints above). Any other subdirectory under logs/ is automatically discovered.
Get script log content
curl http://<pi-ip>:5005/logs/script/install/2026-04-01_09-51-07.log
curl http://<pi-ip>:5005/logs/script/update/2026-04-01_10-00-00.log
curl http://<pi-ip>:5005/logs/script/ap-setup/2026-03-28_14-30-00.log
Returns the raw text content of a specific script log file. The URL path is logs/script/<category>/<filename>.
Auth endpoints
Check auth status
curl http://<pi-ip>:5005/auth/status
Returns whether authentication is enabled. If a token is provided (via header or query param), also validates it:
{ "enabled": true, "valid": true }
When no token is provided, valid is null. This endpoint is exempt from authentication.
Login (validate password)
curl -X POST http://<pi-ip>:5005/auth/validate \
-H "Content-Type: application/json" \
-d '{"password": "your-password"}'
On success returns {"valid": true, "token": "<api-token>"}. On failure returns {"valid": false}. The returned token is used for all subsequent API requests. This endpoint is exempt from authentication.
Regenerate token
curl -X POST http://<pi-ip>:5005/auth/regenerate \
-H "Authorization: Bearer <current-token>"
Generates a new API token and invalidates the old one. Returns {"token": "<new-token>"}. All active sessions using the old token will need to log in again. This endpoint requires the current valid token.
Change password
curl -X POST http://<pi-ip>:5005/auth/set-password \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"current": "old-password", "new": "new-password"}'
Changes the dashboard login password. Requires a valid API token and the current password. New password must be at least 4 characters. Returns {"message": "Password updated"} on success, 403 if the current password is wrong, or 400 if the new password is too short.
Settings file
The sensor and storage configuration is stored in client/settings/settings.json:
{
"Sensor": {
"type": "reed_switch",
"gpio": 22,
"enabled": false,
"hold_seconds": 10,
"calibration": {}
},
"StorageLimit": {
"enabled": false,
"max_percent": 85
},
"CaptivePortal": {
"enabled": true
}
}