Upload

File upload and download for photos.

File upload and download for photos. Unlike tasks/notes where attachments are children of the main entity, in photos the Photo record IS the file entity — it owns chunks directly via photo_chunk. Binary content is uploaded directly to cloud storage through presigned URLs.

Access control. The caller must own the photo or belong to an album that contains it. Downloads are allowed for any album role (editor or viewer); uploads, chunk confirmation and finalize require the editor role — a viewer can view a shared photo but cannot replace its contents.

Upload flow

  1. Create a photo record via photos.photos.create. The server returns the assigned id with status: "pending"
  2. Call requestUploadUrl to get a presigned upload URL for each part
  3. PUT the encrypted chunk body to the presigned URL (standard HTTP PUT, not WebSocket)
  4. Call confirmChunk for each uploaded part — pass the etag from the S3 PUT response
  5. Call finalizeUpload to assemble parts and mark the photo as ready

For thumbnails repeat steps 2–4 with chunk_type: "thumbnail_512" (single chunk). Thumbnails do not require a separate finalize call.

requestUploadUrl

Request a presigned upload URL for a single chunk/part.

WebSocket

Request

{
  "jsonrpc": "2.0",
  "method": "photos.upload.requestUploadUrl",
  "params": {
    "file_id": "1920438291738",
    "object_key": "a1/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "chunk_type": "file"
  },
  "id": 30
}
Param Type Required Description
file_id string Photo record id
object_key string Client-generated object key for storage (format: <uuid[:2]>/<uuid>)
chunk_type string "file" (default) or "thumbnail_512"

Response

{
  "jsonrpc": "2.0",
  "result": {
    "object_key": "a1/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "upload_url": "https://storage.example.com/enbox-photos-files/a1/a1b2c3d4-...?X-Amz-..."
  },
  "id": 30
}

confirmChunk

Confirm that a chunk/part was successfully uploaded. For multipart uploads, pass the etag returned by S3 in the PUT response header.

WebSocket

Request

{
  "jsonrpc": "2.0",
  "method": "photos.upload.confirmChunk",
  "params": {
    "file_id": "1920438291738",
    "object_key": "a1/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "chunk_index": 0,
    "chunk_type": "file",
    "etag": "\"d41d8cd98f00b204e9800998ecf8427e\""
  },
  "id": 31
}
Param Type Required Description
file_id string Photo record id
object_key string Object key used during upload
chunk_index integer 0-based part order. Default 0
chunk_type string "file" (default) or "thumbnail_512"
etag string S3 ETag from the PUT response. Required for multipart file chunks

For chunk_type: "thumbnail_512", the server creates a Chunk record and sets thumbnail_512_id on the photo automatically.

Response

{
  "jsonrpc": "2.0",
  "result": {
    "chunk_id": "1920438291770",
    "chunk_index": 0,
    "size_bytes": 3145728
  },
  "id": 31
}

finalizeUpload

Complete the multipart upload and mark the photo as ready. The server assembles all confirmed parts into a single S3 object, creates the PhotoChunk record, and sets status to "ready".

WebSocket

Request

{
  "jsonrpc": "2.0",
  "method": "photos.upload.finalizeUpload",
  "params": {
    "file_id": "1920438291738"
  },
  "id": 32
}
Param Type Required Description
file_id string Photo record id

Response

{
  "jsonrpc": "2.0",
  "result": {
    "file_id": "1920438291738",
    "status": "ready"
  },
  "id": 32
}

requestDownloadUrls

Request presigned download URLs for specific chunks of a photo.

WebSocket

Request

{
  "jsonrpc": "2.0",
  "method": "photos.upload.requestDownloadUrls",
  "params": {
    "file_id": "1920438291738",
    "chunk_indexes": [0],
    "chunk_type": "file"
  },
  "id": 33
}
Param Type Required Description
file_id string Photo record id
chunk_indexes integer[] 0-based indexes of chunks to download
chunk_type string "file" (default) or "thumbnail_512"

Response

{
  "jsonrpc": "2.0",
  "result": [
    {
      "chunk_index": 0,
      "download_url": "https://storage.example.com/enbox-photos-files/a1/a1b2c3d4-...?X-Amz-...",
      "size_bytes": 3145728
    }
  ],
  "id": 33
}

For thumbnails (chunk_type: "thumbnail_512") use chunk_indexes: [0]. The result contains a single entry or an empty array if no thumbnail exists.