OwnNode discovers media through Android MediaStore and streams each file to the
existing Pi service. The Pi service, not the Android app, owns filesystem and
optional network-share credentials.
POST /api/media/backup/items?display_name=IMG_0001.jpg&mime_type=image%2Fjpeg&size_bytes=1234&modified_at_seconds=1700000000&captured_at_millis=1699999999000&relative_path=DCIM%2FCamera%2F
Authorization: Bearer <existing OwnNode token>
Idempotency-Key: <device UUID>:external:<MediaStore ID>:<modified seconds>
X-PiStats-Device-Id: <app-generated UUID>
X-PiStats-Media-Key: external:<MediaStore ID>:<modified seconds>
Content-Type: image/jpeg
Content-Length: 1234
<raw file bytes>captured_at_millis and relative_path are optional. The client does not send
latitude, longitude, or a device hardware identifier.
| Status | Meaning |
|---|---|
200, 201, 202, 204 |
Stored successfully |
409 |
This idempotency key was already stored; treated as success |
401, 403 |
Authentication or authorization problem |
404 |
Media backup is not installed on the Pi service |
413 |
File is skipped and the scan continues |
408, 5xx |
Temporary failure; WorkManager retries with backoff |
Other 4xx |
Permanent request failure shown in the app |
- Authenticate before reading the request body.
- Enforce a configured maximum content length and allow only intended image and video MIME types.
- Ignore path separators from
display_name; generate the destination path on the server. Treatrelative_pathas untrusted display metadata, not a path. - Stream to a temporary file on the same filesystem, verify the received byte
count,
fsync, and atomically rename it into the configured library. - Store the idempotency key in a durable database with a unique constraint. A
repeated completed request returns
409without creating a second file. - Keep incomplete temporary files outside the shared library and remove them on a schedule.
- Never expose Samba credentials to the Android client. Bind privately and use the installation's private-network access controls.
One practical destination layout is:
<media-root>/<device-id>/<yyyy>/<MM>/<server-generated-name>
The server should derive the date from captured_at_millis, falling back to
modified_at_seconds, and should preserve the original extension only after
validating it against the MIME type.
- Automatic work runs every six hours with battery-not-low and network constraints.
- Wi-Fi-only mode uses WorkManager's
UNMETEREDnetwork constraint. - Each worker processes at most 20 files and schedules another constrained batch when more remain. This keeps work restartable and avoids a long-running foreground service.
- The scan checkpoint advances after a success,
409, or an explicitly skipped missing/oversized file. Resetting the scan is safe because the server contract is idempotent.
Large single videos still need to finish within the request timeout. A future server revision should add resumable, offset-based upload sessions before this is used for very large video libraries.