This document describes the REST API endpoints provided by the Seen backend server. The API uses JSON for request and response bodies, and follows standard HTTP status codes.
Base URL: The API is typically served on http://localhost:3000 (or as configured)
CORS: The API allows requests from any origin and supports the following HTTP methods: GET, POST, DELETE, OPTIONS
- Health & Status
- Statistics & Metrics
- Assets
- Paths & Scanning
- File Operations
- Facial Recognition (optional feature)
Check server health and version information.
Response: 200 OK
{
"status": "ok",
"version": "0.8.0",
"database": "SQLite"
}Fields:
status: Always"ok"when the server is runningversion: Application version numberdatabase: Database type ("SQLite"or"Postgres")
Get comprehensive statistics about the system, including queue depths, processing rates, and scan status.
Response: 200 OK
{
"uptime_seconds": 3600,
"queues": {
"discover": 0,
"hash": 5,
"metadata": 10,
"db_write": 20,
"thumb": 15
},
"processed": {
"files_total": 10000,
"bytes_total": 5000000000,
"files_per_sec": 25.5,
"bytes_per_sec": 25500000.0,
"mb_per_sec": 25.5
},
"processing": {
"files_committed": 8500,
"bytes_total": 5000000000,
"rate_files_per_sec": 20.0,
"throughput_mb_per_sec": 20.0,
"last_completed_elapsed_seconds": 425.0
},
"scan_running": true,
"processing_active": true,
"current_scan": {
"files_processed": 5000,
"files_per_sec": 25.5,
"elapsed_seconds": 196.0,
"photos_processed": 4000,
"videos_processed": 1000
},
"current_processing": {
"files_committed": 4500,
"processing_rate_files_per_sec": 20.0,
"elapsed_seconds": 225.0
},
"db": {
"assets": 8500
}
}Fields:
uptime_seconds: Server uptime in secondsqueues: Current queue depths for each processing stageprocessed: Overall statistics (lifetime totals and rates)processing: Processing pipeline statistics (files committed, not just discovered)scan_running: Whether any path is currently being scannedprocessing_active: Whether there are items in processing queuescurrent_scan: Statistics for the current scan (if active)current_processing: Statistics for current processing (if active)db.assets: Total number of assets in the database
Reset performance statistics. Cannot be called while a scan is running.
Response: 200 OK on success, 409 Conflict if scan is running
{
"success": true,
"message": "Performance statistics reset"
}Get metrics in Prometheus format.
Response: 200 OK (text/plain)
Returns Prometheus-formatted metrics including:
- Queue depths
- Processing rates
- File counts
- Throughput statistics
Get detailed performance metrics and comparisons with other photo library software.
Response: 200 OK
{
"seen": {
"files_per_sec": 25.5,
"current_rate": 25.5,
"mb_per_sec": 25.5,
"status": "excellent",
"is_active": true
},
"system_info": {
"cpu_cores": 8,
"cpu_brand": "Intel Core i7-9700K",
"accel": "CUDA",
"note": "Estimated performance ranges based on your hardware (CPU cores). Values are static and don't change with Seen's current rate."
},
"gpu_usage": {
"enabled": true,
"accel": "CUDA",
"jobs_gpu": 5,
"jobs_cpu": 0,
"consecutive_failures": 0,
"auto_disabled": false
},
"typical_ranges": {
"digikam": {
"files_per_sec": "2.1-3.9",
"note": "Desktop application, single-threaded indexing (less affected by cores)"
},
"photoprism": {
"files_per_sec": "5.6-10.4",
"note": "Go-based, multi-threaded, includes AI features (scales with cores)"
},
"immich": {
"files_per_sec": "8.4-15.6",
"note": "TypeScript/Node.js, optimized for modern hardware (scales with cores)"
},
"lightroom": {
"files_per_sec": "1.4-2.6",
"note": "Desktop application, includes full RAW processing (CPU-intensive)"
},
"synology_ds220_plus": {
"files_per_sec": "0.5-1.0",
"note": "NAS device (Intel Celeron J4025, 2 cores). Optimized for storage, slower CPU processing."
}
},
"current_scan": {
"files_processed": 5000,
"files_per_sec": 25.5,
"elapsed_seconds": 196.0,
"status": "excellent"
},
"current_rate": 25.5,
"notes": [
"Performance varies significantly based on:",
"- File sizes (larger files = slower processing)",
"- Storage type (SSD vs HDD)",
"- CPU cores and speed",
"- Whether thumbnails are being generated",
"- Network latency (if files are on network storage)"
]
}Get distribution of file types in the database.
Response: 200 OK
{
"image/jpeg": 5000,
"image/png": 2000,
"image/webp": 500,
"video/mp4": 1000,
"video/mov": 500,
"audio": 200,
"other": 100,
"other_extensions": [".pdf", ".txt"],
"other_breakdown": {
".pdf": 80,
".txt": 20
}
}Get FFmpeg diagnostic information including version, hardware acceleration support, and GPU configuration.
Response: 200 OK
{
"ffmpeg_version": "ffmpeg version 6.0",
"hwaccels": ["cuda", "qsv", "d3d11va"],
"filters": ["scale_cuda", "scale_npp"],
"gpu_config": {
"accel": "CUDA",
"enabled": true,
"consecutive_failures": 0,
"auto_disabled": false,
"device_counts": {
"cuda": 1,
"intel_gpu": 0,
"opencl": 0
}
}
}List assets with pagination and sorting.
Query Parameters:
offset(optional, default: 0): Number of assets to skiplimit(optional, default: 200): Maximum number of assets to returnsort(optional, default: "mtime"): Field to sort by (mtime,ctime,size,filename)order(optional, default: "desc"): Sort order (ascordesc)person_id(optional, facial-recognition feature only): Filter assets by person ID
Response: 200 OK
[
{
"id": 1,
"path": "/photos/image.jpg",
"filename": "image.jpg",
"mime": "image/jpeg",
"size": 1024000,
"mtime": 1234567890,
"ctime": 1234567890,
"sha256": "abc123...",
"width": 1920,
"height": 1080,
"camera_make": "Canon",
"camera_model": "EOS 5D",
"date_taken": "2023-01-15T10:30:00Z"
}
]Search assets by text query and optional filters.
Query Parameters:
q(required): Search query stringfrom(optional): Filter by date taken (Unix timestamp)to(optional): Filter by date taken (Unix timestamp)camera_make(optional): Filter by camera makecamera_model(optional): Filter by camera modeloffset(optional, default: 0): Number of assets to skiplimit(optional, default: 200): Maximum number of assets to return
Response: 200 OK
Returns the same format as /assets.
Get detailed information about a specific asset.
Path Parameters:
id: Asset ID
Response: 200 OK or 404 Not Found
{
"id": 1,
"path": "/photos/image.jpg",
"filename": "image.jpg",
"mime": "image/jpeg",
"size": 1024000,
"mtime": 1234567890,
"ctime": 1234567890,
"sha256": "abc123...",
"width": 1920,
"height": 1080,
"camera_make": "Canon",
"camera_model": "EOS 5D",
"date_taken": "2023-01-15T10:30:00Z"
}Remove an asset from the Seen index (database/search) without touching the original file on disk. Generated thumbnails/previews are still removed.
Path Parameters:
id: Asset ID
Response: 200 OK on success, 404 Not Found if asset doesn't exist, 500 Internal Server Error on failure
{
"success": true
}Permanently delete an asset from both the Seen index and the original filesystem. Thumbnails/previews are removed as well.
Path Parameters:
id: Asset ID
Response codes:
200 OK– asset deleted from disk and index404 Not Found– asset missing from the index409 Conflict– original file could not be removed (e.g., read-only filesystem)500 Internal Server Error– unexpected failure
Response body:
{
"success": true,
"deleted_from_disk": true,
"read_only": false,
"path": "/photos/image.jpg"
}When a file cannot be deleted because it is read-only, the response looks like:
{
"success": false,
"deleted_from_disk": false,
"read_only": true,
"path": "/photos/image.jpg",
"error": "File is read-only"
}Bulk permanent deletion. Accepts a JSON body with asset IDs and returns per-item status, including read-only failures.
Request Body:
{
"ids": [1, 2, 3]
}Response: 200 OK if every asset was deleted, 409 Conflict if any read-only failures occurred, 400/500 for invalid input or internal errors.
{
"success": false,
"results": [
{ "id": 1, "deleted": true, "read_only": false, "path": "/photos/a.jpg" },
{ "id": 2, "deleted": false, "read_only": true, "path": "/photos/b.jpg", "error": "File is read-only" }
],
"read_only_failures": [
{ "id": 2, "path": "/photos/b.jpg", "error": "File is read-only" }
]
}Get a 256x256 thumbnail image for an asset.
Path Parameters:
id: Asset ID
Response: 200 OK (image/webp) or 404 Not Found
Returns a WebP image with appropriate cache headers.
Get a 1600px preview image for an asset.
Path Parameters:
id: Asset ID
Response: 200 OK (image/webp) or 404 Not Found
Returns a WebP image with appropriate cache headers.
Stream a video file with range request support.
Path Parameters:
id: Asset ID
Headers (optional):
Range: HTTP range header for partial content (e.g.,bytes=0-1023)
Response:
200 OK(full file) or206 Partial Content(range request)404 Not Foundif asset doesn't exist
Returns the video file with appropriate MIME type and headers.
Extract audio from a video or audio file and return as MP3.
Path Parameters:
id: Asset ID
Response: 200 OK (audio/mpeg) or 400 Bad Request if not a video/audio file, 404 Not Found if asset doesn't exist
Note: If the source file is already MP3, it's returned directly. Otherwise, FFmpeg is used to transcode to MP3. If MP3 encoding fails, falls back to AAC in M4A container.
Download the original asset file.
Path Parameters:
id: Asset ID
Response: 200 OK with Content-Disposition: attachment or 404 Not Found
Returns the original file with appropriate MIME type and download headers.
Save the orientation/rotation for an asset.
Path Parameters:
id: Asset ID
Request Body:
{
"rotation": 90
}Valid rotation values: 0, 90, 180, 270 (degrees)
Response: 200 OK on success, 400 Bad Request for invalid rotation, 404 Not Found if asset doesn't exist
{
"success": true
}Get list of all scan paths.
Response: 200 OK
[
{
"path": "/photos",
"is_default": true,
"host_path": "/mnt/photos"
},
{
"path": "/videos",
"is_default": false,
"host_path": null
}
]Fields:
path: The scan pathis_default: Whether this is the default root pathhost_path: Host path mapping (for Docker/container scenarios, null if not applicable)
Add a new scan path. Automatically starts a watcher and BFS scan for the path.
Request Body:
{
"path": "/new/photos"
}Response: 200 OK on success, 500 Internal Server Error on database error
{
"success": true,
"message": "Path added successfully"
}Remove a scan path and delete all associated assets.
Query Parameters:
path: The path to remove
Response: 200 OK on success
{
"success": true,
"path_removed": true,
"assets_deleted": 100,
"faces_deleted": 50,
"message": "Path removed. 100 assets and 50 faces deleted."
}Start a BFS scan for a specific path.
Request Body:
{
"path": "/photos"
}Response: 202 Accepted on success, 404 Not Found if path doesn't exist, 409 Conflict if already scanning
{
"success": true,
"message": "Scan started for path"
}Pause scanning and file watching for a specific path.
Request Body:
{
"path": "/photos"
}Response: 200 OK
{
"success": true,
"message": "Path paused"
}Resume file watching for a specific path (does not restart scanning).
Request Body:
{
"path": "/photos"
}Response: 200 OK
{
"success": true,
"message": "Path resumed"
}Get the status of a specific path (scanning, watcher paused, watching).
Query Parameters:
path: The path to check
Response: 200 OK
{
"scanning": false,
"watcher_paused": false,
"watching": true
}Browse directory contents (for file picker UI).
Query Parameters:
path(optional, default: "/"): Directory path to browse (must be absolute)
Response: 200 OK or 400 Bad Request for invalid path
{
"path": "/photos",
"entries": [
{
"name": "2023",
"path": "/photos/2023",
"is_dir": true
},
{
"name": "image.jpg",
"path": "/photos/image.jpg",
"is_dir": false
}
]
}Note: Hidden files/directories (starting with .) are filtered out. Entries are sorted with directories first, then files, both alphabetically.
Clear all data from the database (assets, faces, persons). Cannot be called while a scan is running.
Response: 200 OK on success, 409 Conflict if scan is running, 500 Internal Server Error on error
{
"success": true,
"assets_deleted": 10000,
"faces_deleted": 5000,
"persons_deleted": 100,
"message": "All data cleared"
}These endpoints are only available when the facial-recognition feature is enabled.
Start face detection for all images that haven't been processed yet.
Response: 202 Accepted on success, 409 Conflict if already running
{
"status": "started",
"message": "Face detection started"
}Stop face detection and disable it.
Response: 200 OK
{
"status": "stopped",
"message": "Face detection disabled"
}Get face detection status and queue depth.
Response: 200 OK
{
"running": false,
"queue_depth": 0
}Get detailed face detection progress and statistics.
Response: 200 OK
{
"enabled": true,
"running": false,
"queue_depth": 0,
"models_loaded": {
"scrfd": true,
"arcface": true
},
"models_status": "SCRFD and ArcFace loaded",
"counts": {
"faces_total": 5000,
"persons_total": 100,
"assets_with_faces": 2000
},
"thresholds": {
"cluster_batch_size": 100,
"remaining_to_next_cluster": 50
},
"status": "Ready to cluster"
}Get current facial recognition settings.
Response: 200 OK
{
"confidence_threshold": 0.20,
"nms_iou_threshold": 0.4,
"cluster_epsilon": 0.55,
"min_cluster_size": 2,
"min_samples": 2,
"excluded_extensions": ["gif", "bmp"]
}Fields:
confidence_threshold: Minimum confidence for face detection (0.0-1.0)nms_iou_threshold: Non-maximum suppression IoU thresholdcluster_epsilon: HDBSCAN clustering epsilon parametermin_cluster_size: Minimum cluster size for HDBSCANmin_samples: Minimum samples for HDBSCANexcluded_extensions: File extensions to exclude from face detection
Update facial recognition settings.
Request Body (all fields optional):
{
"confidence_threshold": 0.25,
"nms_iou_threshold": 0.45,
"cluster_epsilon": 0.6,
"min_cluster_size": 3,
"min_samples": 3,
"excluded_extensions": ["gif", "bmp", "tiff"]
}Response: 200 OK
{
"status": "updated"
}List unassigned faces (faces not yet assigned to a person).
Query Parameters:
offset(optional, default: 0): Number of faces to skiplimit(optional, default: 60, max: 500): Maximum number of faces to return
Response: 200 OK
{
"faces": [
{
"id": 1,
"asset_id": 100,
"bbox": {
"x1": 100.0,
"y1": 150.0,
"x2": 200.0,
"y2": 250.0
},
"confidence": 0.95
}
]
}Get a thumbnail image of a detected face.
Path Parameters:
id: Face ID
Query Parameters:
size(optional, default: 160, min: 32, max: 1024): Thumbnail size in pixels
Response: 200 OK (image/png) or 404 Not Found
Trigger face clustering to group similar faces into persons.
Query Parameters:
epsilon(optional): Clustering epsilon parameter (default: 0.55, or fromSEEN_FACE_CLUSTER_EPSILONenv var)min_samples(optional): Minimum samples per cluster (default: 2, or fromSEEN_FACE_HDBSCAN_MIN_SAMPLESenv var)
Response: 200 OK on success, 409 Conflict if face detection is running, 500 Internal Server Error on error
{
"success": true,
"persons_created": 10,
"faces_assigned": 50,
"message": "Clustered 50 faces into 10 persons"
}Assign a face to a person, or unassign it.
Path Parameters:
id: Face ID
Request Body:
{
"person_id": 5
}To unassign a face, set person_id to null:
{
"person_id": null
}Response: 200 OK on success, 404 Not Found if face or person doesn't exist
{
"success": true,
"action": "assigned",
"person_id": 5
}When unassigning:
{
"success": true,
"action": "unassigned",
"previous_person_id": 5
}Trigger a full re-clustering of all faces. This clears existing person assignments and re-runs clustering on all face embeddings.
Response: 200 OK on success, 500 Internal Server Error on error
{
"success": true,
"persons": 15,
"faces": 120
}Automatically merge similar persons based on face similarity.
Query Parameters:
threshold(optional, default: 0.50): Similarity threshold for merging (0.0-1.0, lower = more similar required)
Response: 200 OK on success, 409 Conflict if face detection is running, 400 Bad Request if not enough persons
{
"success": true,
"persons_merged": 5,
"faces_merged": 25,
"remaining_persons": 10
}Refresh a person's profile by recalculating their centroid from assigned faces.
Path Parameters:
id: Person ID
Response: 200 OK on success, 404 Not Found if person doesn't exist
{
"success": true,
"profile": {
"person_id": 5,
"face_count": 10,
"centroid_dim": 512
}
}Clear all facial recognition data (faces and persons). Cannot be called while face detection is running.
Response: 200 OK on success, 409 Conflict if face detection is running, 500 Internal Server Error on error
{
"success": true,
"faces_deleted": 5000,
"persons_deleted": 100,
"message": "All facial data cleared"
}List all persons.
Response: 200 OK
[
{
"id": 1,
"name": "John Doe",
"created_at": "2023-01-15T10:30:00Z"
}
]Get information about a specific person.
Path Parameters:
id: Person ID
Response: 200 OK or 404 Not Found
{
"id": 1,
"name": "John Doe",
"created_at": "2023-01-15T10:30:00Z"
}Get list of asset IDs associated with a person.
Path Parameters:
id: Person ID
Response: 200 OK or 500 Internal Server Error
{
"asset_ids": [1, 2, 3, 4, 5]
}Update a person's name.
Path Parameters:
id: Person ID
Request Body:
{
"name": "Jane Doe"
}Response: 200 OK on success, 404 Not Found if person doesn't exist, 500 Internal Server Error on error
{
"success": true
}Delete a person and unassign all associated faces.
Path Parameters:
id: Person ID
Response: 200 OK on success, 404 Not Found if person doesn't exist, 500 Internal Server Error on error
{
"success": true
}Manually merge two persons into one.
Request Body:
{
"source_person_id": 3,
"target_person_id": 5
}Response: 200 OK on success, 404 Not Found if either person doesn't exist, 400 Bad Request if trying to merge a person into itself
{
"success": true,
"merged_person": {
"id": 5,
"name": "Person 5",
"face_count": 15,
"centroid_dim": 512
}
}Note: All faces from source_person_id are reassigned to target_person_id, and the source person is deleted.
Get the representative face ID for a person (typically the face with highest confidence).
Path Parameters:
id: Person ID
Response: 200 OK on success, 404 Not Found if person has no faces
{
"face_id": 42
}Get all faces detected in a specific asset.
Path Parameters:
id: Asset ID
Response: 200 OK or 500 Internal Server Error
[
{
"id": 1,
"person_id": 5,
"bbox": "{\"x1\":100.0,\"y1\":150.0,\"x2\":200.0,\"y2\":250.0}",
"confidence": 0.95
}
]Note: person_id may be null if the face hasn't been assigned to a person yet.
All endpoints may return the following error status codes:
400 Bad Request: Invalid request parameters or body404 Not Found: Resource not found409 Conflict: Operation cannot be performed (e.g., scan already running)500 Internal Server Error: Server error
Error responses typically include a JSON body:
{
"error": "Error message description"
}- All timestamps are Unix timestamps (seconds since epoch) unless otherwise specified
- File sizes are in bytes
- The API uses CORS and allows requests from any origin
- Rate limiting is not currently implemented
- Some endpoints may take significant time to respond (e.g.,
/faces/cluster,/clear) and should be called asynchronously - The API is designed to be stateless, but some operations (like scanning) maintain server-side state