Photos
Encrypted photo and video library with album grouping and tagging.
Encrypted photo and video library with album grouping and tagging. User-facing content (filenames, EXIF metadata, media classification) is encrypted client-side before reaching the server. Structural metadata used for timeline sorting, smart scopes, and storage quotas (dates, flags, file sizes) is stored in the clear.
Each photo can belong to multiple albums simultaneously (Apple Photos / Google Photos semantics). Deleting an album removes only the membership — photos themselves are not deleted.
All methods use the JSON-RPC 2.0 protocol over WebSocket. See Enbox API for transport details, error codes, query operators, and encryption model.
Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
— | Primary key. Read-only, auto-generated |
owner_id |
string |
— | Owner user id. Read-only |
owner_wrapped_key |
string |
✅ | Content key wrapped by owner’s account key (base64) |
size_bytes |
integer |
✅ | Logical plaintext file size in bytes |
status |
string |
— | Upload state. Read-only. Values: pending (uploading) · ready (finalized) |
taken_at |
string? |
ISO 8601 EXIF DateTimeOriginal. Used for timeline sort. null for files without EXIF date |
|
favorite |
boolean |
Favorites smart scope flag. Default false |
|
hidden |
boolean |
Hidden smart scope flag (excluded from Library). Default false |
|
trashed |
boolean |
Trash smart scope / soft-delete flag. Default false |
|
thumbnail_512_id |
string? |
— | Chunk id of the encrypted 512 px thumbnail. Read-only. null until uploaded |
enc_name |
string |
✅ | Encrypted filename. Plain string after decryption (e.g. "IMG_2847.HEIC") |
enc_mime_type |
string |
✅ | Encrypted MIME type. Plain string after decryption (e.g. "image/jpeg", "video/mp4") |
enc_overview |
string? |
Encrypted media overview. See PhotoOverview. null until set |
|
enc_metadata |
string? |
Encrypted EXIF / location / camera details. See PhotoMetadata. null until set |
|
chunk_count |
integer |
— | Number of uploaded chunks. Read-only, computed |
created_at |
string |
— | ISO 8601 creation timestamp. Read-only |
updated_at |
string |
— | ISO 8601 last update timestamp. Read-only |
photo_album |
PhotoAlbum[] | Album assignments | |
photo_tag |
string[] |
Assigned tag IDs | |
photo_chunk |
PhotoChunk[] | Ordered chunk references. Returned in detail view |
Filterable: favorite · hidden · trashed
Default ordering: -taken_at, -created_at
PhotoAlbum
Junction entry linking a photo to an album with an optional wrapped key for shared-album access.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
— | Junction row id. Read-only. Include when updating existing assignments |
album_id |
string |
✅ | Album id |
album_wrapped_key |
string? |
Content key wrapped by album key. Set when album is shared |
PhotoChunk
Ordered chunk reference for a photo. Created by confirmChunk.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
— | Primary key. Read-only |
photo_id |
string |
✅ | Parent Photo id |
chunk_index |
integer |
✅ | 0-based chunk order |
chunk_id |
string |
✅ | Reference to the stored encrypted blob |
Encrypted field schemas
Each enc_* column is an AES-GCM blob. After decryption the plaintext is a JSON value whose structure is documented below.
Danger: The server cannot validate encrypted field structures. Writing an incorrect schema will produce corrupted data for all clients. Follow the type definitions below exactly.
PhotoOverview
Decrypted content of enc_overview. Object. Loaded in every list/grid view for media-type badges and smart scope filtering.
| Field | Type | Required | Allowed values | Description |
|---|---|---|---|---|
media_type |
string |
✅ | photo · video · selfie · live · portrait · panorama · screenshot · burst · animated |
Media classification |
duration |
float |
✅ | — | Video length in seconds. 0 for still images |
Warning: Both fields are required. Set
durationto0for non-video media types.
PhotoMetadata
Decrypted content of enc_metadata. Object. Loaded only on the detail / info screen.
| Field | Type | Required | Description |
|---|---|---|---|
width |
integer |
✅ | Image/video width in pixels |
height |
integer |
✅ | Image/video height in pixels |
camera |
string |
✅ | Camera model (e.g. "iPhone 15 Pro", "Canon EOS R5"). Empty string if unknown |
exposure |
string |
✅ | Exposure summary (e.g. "f/1.8 1/120s ISO 64"). Empty string if unknown |
location |
GeoLocation? | ✅ | GPS location. null if not available |
people |
string[] |
✅ | Detected person names (from face recognition). Empty array if none |
caption |
string |
✅ | User-entered or AI-generated caption. Empty string if none |
Warning: All fields are required and must be present in the JSON object. Use
""(empty string) for text fields without a value,[]for the people array, andnullfor location when GPS data is not available.
GeoLocation
Nested object within PhotoMetadata. Nullable — the location field may be null.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
✅ | Reverse-geocoded place name (e.g. "Paris, France"). Empty string if unknown |
lat |
float |
✅ | Latitude in decimal degrees |
lng |
float |
✅ | Longitude in decimal degrees |
find
List photos with filtering, sorting, and pagination.
WebSocket
Request
{
"jsonrpc": "2.0",
"method": "photos.photos.find",
"params": {
"filter": { "trashed": { "$eq": false }, "hidden": { "$eq": false } },
"sort": [{ "field": "taken_at", "dir": "desc" }],
"limit": 100,
"fields": [
"id", "size_bytes", "status", "taken_at", "favorite", "hidden", "trashed",
"thumbnail_512_id", "chunk_count",
"enc_name", "enc_mime_type", "enc_overview",
"owner_id", "owner_wrapped_key",
"photo_album.id", "photo_album.album_id", "photo_album.album_wrapped_key",
"photo_tag.tag_id"
]
},
"id": 1
}
Response
{
"jsonrpc": "2.0",
"result": {
"items": [
{
"id": "1920438291738",
"size_bytes": 3145728,
"status": "ready",
"taken_at": "2026-05-10T14:32:00Z",
"favorite": true,
"hidden": false,
"trashed": false,
"thumbnail_512_id": "1920438291780",
"chunk_count": 1,
"enc_name": "<base64>",
"enc_mime_type": "<base64>",
"enc_overview": "<base64>",
"owner_id": "42",
"owner_wrapped_key": "<base64>",
"photo_album": [
{ "id": "1920438291950", "album_id": "1920438291900", "album_wrapped_key": null }
],
"photo_tag": ["1920438291800"]
}
],
"total": 1284
},
"id": 1
}
findOneById
Fetch a single photo with full detail including metadata, chunks, albums, and tags.
WebSocket
Request
{
"jsonrpc": "2.0",
"method": "photos.photos.findOneById",
"params": {
"id": "1920438291738",
"fields": [
"id", "size_bytes", "status", "taken_at", "favorite", "hidden", "trashed",
"thumbnail_512_id", "chunk_count", "created_at", "updated_at",
"enc_name", "enc_mime_type", "enc_overview", "enc_metadata",
"owner_id", "owner_wrapped_key",
"photo_album.id", "photo_album.album_id", "photo_album.album_wrapped_key",
"photo_tag.tag_id",
"photo_chunk.id", "photo_chunk.chunk_index", "photo_chunk.chunk_id"
]
},
"id": 2
}
Response
{
"jsonrpc": "2.0",
"result": {
"id": "1920438291738",
"size_bytes": 3145728,
"status": "ready",
"taken_at": "2026-05-10T14:32:00Z",
"favorite": true,
"hidden": false,
"trashed": false,
"thumbnail_512_id": "1920438291780",
"chunk_count": 1,
"created_at": "2026-05-10T15:00:00Z",
"updated_at": "2026-05-10T15:01:00Z",
"enc_name": "<base64>",
"enc_mime_type": "<base64>",
"enc_overview": "<base64>",
"enc_metadata": "<base64>",
"owner_id": "42",
"owner_wrapped_key": "<base64>",
"photo_album": [
{ "id": "1920438291950", "album_id": "1920438291900", "album_wrapped_key": null }
],
"photo_tag": ["1920438291800"],
"photo_chunk": [
{ "id": "1920438291770", "chunk_index": 0, "chunk_id": "1920438291771" }
]
},
"id": 2
}
create
Create a new photo record. The server assigns id, created_at, and updated_at. After creation, upload binary content via the Upload service.
WebSocket
Request
{
"jsonrpc": "2.0",
"method": "photos.photos.create",
"params": {
"document": {
"size_bytes": 3145728,
"taken_at": "2026-05-10T14:32:00Z",
"enc_name": "<base64>",
"enc_mime_type": "<base64>",
"enc_overview": "<base64>",
"enc_metadata": "<base64>",
"owner_id": "42",
"owner_wrapped_key": "<base64>",
"photo_album": [
{ "album_id": "1920438291900" }
],
"photo_tag": ["1920438291800"]
}
},
"id": 3
}
Response
{
"jsonrpc": "2.0",
"result": {
"id": "1920438291738",
"size_bytes": 3145728,
"status": "pending",
"enc_name": "<base64>",
"created_at": "2026-05-10T15:00:00Z"
},
"id": 3
}
updateOneById
Partial update — only specified fields change; omitted fields are untouched. Nested collections are updated automatically: items with id are updated, items without id are created, items missing from the array are deleted.
WebSocket
Request
{
"jsonrpc": "2.0",
"method": "photos.photos.updateOneById",
"params": {
"id": "1920438291738",
"document": {
"favorite": true,
"enc_overview": "<base64-new>",
"photo_album": [
{ "id": "1920438291950", "album_id": "1920438291900" },
{ "album_id": "1920438291910" }
]
}
},
"id": 4
}
Response
{
"jsonrpc": "2.0",
"result": {
"id": "1920438291738",
"favorite": true,
"enc_overview": "<base64-new>",
"updated_at": "2026-05-13T18:45:00Z"
},
"id": 4
}
replaceOneById
Full replacement — all writable fields must be provided. Unlike updateOneById, omitted fields are reset to defaults. Nested collections (photo_album, photo_tag, photo_chunk) are not accepted in this method — use updateOneById to modify them.
WebSocket
Request
{
"jsonrpc": "2.0",
"method": "photos.photos.replaceOneById",
"params": {
"id": "1920438291738",
"document": {
"size_bytes": 3145728,
"taken_at": "2026-05-10T14:32:00Z",
"favorite": true,
"hidden": false,
"trashed": false,
"enc_name": "<base64>",
"enc_mime_type": "<base64>",
"enc_overview": "<base64>",
"enc_metadata": "<base64>"
}
},
"id": 4
}
Response
{
"jsonrpc": "2.0",
"result": {
"id": "1920438291738",
"enc_name": "<base64>",
"updated_at": "2026-05-13T18:45:00Z"
},
"id": 4
}
update
Batch update multiple photos in a single transaction (all-or-nothing). See Batch operations.
WebSocket
Request
{
"jsonrpc": "2.0",
"method": "photos.photos.update",
"params": {
"items": [
{ "id": "1920438291738", "document": { "favorite": true } },
{ "id": "1920438291739", "document": { "favorite": true } }
]
},
"id": 5
}
Response
{
"jsonrpc": "2.0",
"result": {
"affected": 2,
"items": [
{ "id": "1920438291738", "favorite": true },
{ "id": "1920438291739", "favorite": true }
]
},
"id": 5
}
deleteOneById
Delete a photo. All album/tag assignments and chunk references are deleted with it.
WebSocket
Request
{
"jsonrpc": "2.0",
"method": "photos.photos.deleteOneById",
"params": {
"id": "1920438291738"
},
"id": 6
}
Response
{
"jsonrpc": "2.0",
"result": {
"id": "1920438291738",
"enc_name": "<base64>"
},
"id": 6
}
delete
Batch delete photos in a single transaction (all-or-nothing). See Batch operations.
WebSocket
Request
{
"jsonrpc": "2.0",
"method": "photos.photos.delete",
"params": {
"items": [
{ "id": "1920438291738" },
{ "id": "1920438291739" }
]
},
"id": 7
}
Response
{
"jsonrpc": "2.0",
"result": {
"affected": 2,
"items": [
{ "id": "1920438291738" },
{ "id": "1920438291739" }
]
},
"id": 7
}