Skip to main content

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.

CategoryKey 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:

ParameterTypeDefaultDescription
typestring(required)Sensor type identifier (e.g. reed_switch, pir, vibration)
gpionumbersensor defaultBCM GPIO pin number (0-27)
enabledbooleantrueWhether the sensor is active
hold_secondsnumber10Seconds to keep recording after sensor releases
invert_logicbooleanfalseSwap trigger/release - e.g. trigger on LOW instead of HIGH
calibrationobject{}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:

ParameterTypeDefaultDescription
timeoutnumber90Seconds 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.

MetricWarningCritical
CPU temperature\u2265 65\u00b0C\u2265 80\u00b0C
Throttle\u2014Any active flag
Storage\u2265 85%\u2265 95%
SD card healthlife_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"}'
ParameterTypeDefaultDescription
enabledbooleanfalseStart/stop timelapse capture
interval_minutesnumber5Minutes between frame captures (1-60)
fpsnumber24Playback framerate of the stitched video
resolutionstring"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.

ParameterTypeDefaultDescription
limitnumber500Max entries to return (capped at 2000)
levelstring(all)Filter by level: ERROR, WARNING, INFO, DEBUG
searchstring(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
}
}