[Feature Request] from-immich upload -- and -- from-immich Archive -- Locked Folder Contents
Im trying to migrate from a existing Immich instance to a new one which works as expected prior to the release of v1.133.1 of Immich which added the locked folder.
Immich-go continues to work but its missing the Locked Folder content.
immich-go upload from-immich --from-server=http://192.168.40.150 --from-api-key=HIx9Ril4 --server=http://127.0.0.1:2283 --api-key=6NqDg2eIS
Through some trial and error I've managed to get access to locked assets via Postman, but it does involve using a session token which I'm unfamiliar with.
There is a API call (on the GUI) to /status which outputs
{
"pinCode":true,
"password":true,
"isElevated":false
}
This shows us that we are not elevated which i think means we don't have access to the locked folder, Without a session token it will output.
{
"message": "This endpoint can only be used with a session token",
"error": "Bad Request",
"statusCode": 400,
"correlationId": "6drhl2ih"
}
Its worth noting that this is the output when using the x-api-key which we use on the other API calls. So it has to be removed for all 'session' based locked folder tasks i think.
You can then POST to /api/auth/session/unlock ensuring your session cookie exists with a json pinCode with your six digit pin, example curl command. Where the cookie access token comes from im unsure.
curl 'http://192.168.40.150/api/auth/session/unlock' \
-H 'Accept: */*' \
-H 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
-H 'Cache-Control: no-cache' \
-H 'Connection: keep-alive' \
-b 'immich_is_authenticated=true; immich_access_token=irrbAwXXXXXXXXXXXXXXaDgIvc; immich_auth_type=oauth' \
-H 'Origin: http://192.168.40.150' \
-H 'Pragma: no-cache' \
-H 'Referer: http://192.168.40.150/auth/pin-prompt?continue=%2Flocked' \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36' \
-H 'content-type: application/json' \
--data-raw '{"pinCode":"123456"}' \
--insecure
This will then (if successful) return a status 200 OK with a blank body
If you request the status again this time will show a elevation but with a Pin Expire time
{
"pinCode": true,
"password": true,
"isElevated": true,
"pinExpiresAt": "2025-06-09T09:55:21.348Z"
}
You can then query /api/timeline/buckets?visibility=locked and get a timeBucket which a count of assets
[
{
"timeBucket":"2025-06-01T00:00:00.000Z",
"count":2
}
]
You can then query this /api/timeline/bucket?timeBucket=2025-06-01T00%3A00%3A00.000Z&visibility=locked which will list all[?] of the assets within
{
"city": [
null,
null
],
"country": [
null,
null
],
"duration": [
null,
null
],
"id": [
"a0bbd94f-0d28-4b88-9bda-f34d10745720",
"fc16b421-2b5a-4973-8897-29e2ccccc0de"
],
"visibility": [
"locked",
"locked"
],
"isFavorite": [
false,
false
],
"isImage": [
true,
true
],
"isTrashed": [
false,
false
],
"livePhotoVideoId": [
null,
null
],
"localDateTime": [
"2025-06-09T08:21:20.55+00:00",
"2025-06-09T08:21:14.001+00:00"
],
"ownerId": [
"c2946930-d4ae-467a-bba8-a8619df0fc90",
"c2946930-d4ae-467a-bba8-a8619df0fc90"
],
"projectionType": [
null,
null
],
"ratio": [
1.882,
1.499
],
"status": [
"active",
"active"
],
"thumbhash": [
"NwgKDIKol4d6h4d9hoB4gFYGSA==",
"2wcKPYYGiXaMd3iEeFh4hlBlB0R1"
]
}
You can then loop through all of the assets for its metadata metadata /api/assets/a0bbd94f-0d28-4b88-9bda-f34d10745720
{
"id": "a0bbd94f-0d28-4b88-9bda-f34d10745720",
"deviceAssetId": "web-image-120.jpg-1749457280550",
"ownerId": "c2946930-d4ae-467a-bba8-a8619df0fc90",
"owner": {
"id": "c2946930-d4ae-467a-bba8-a8619df0fc90",
"email": "[email protected]",
"name": "Shadow",
"profileImagePath": "",
"avatarColor": "orange",
"profileChangedAt": "2025-05-15T21:20:45.586939+00:00"
},
"deviceId": "WEB",
"libraryId": null,
"type": "IMAGE",
"originalPath": "/photos/upload/c2946930-d4ae-467a-bba8-a8619df0fc90/a8/b7/a8b7ef63-85b1-4653-b577-5f52a7df3fe2.jpg",
"originalFileName": "image-120.jpg",
"originalMimeType": "image/jpeg",
"thumbhash": "NwgKDIKol4d6h4d9hoB4gFYGSA==",
"fileCreatedAt": "2025-06-09T08:21:20.550Z",
"fileModifiedAt": "2025-06-09T08:21:20.550Z",
"localDateTime": "2025-06-09T08:21:20.550Z",
"updatedAt": "2025-06-09T08:22:35.191Z",
"isFavorite": false,
"isArchived": false,
"isTrashed": false,
"visibility": "locked",
"duration": "0:00:00.00000",
"exifInfo": {
"make": null,
"model": null,
"exifImageWidth": 640,
"exifImageHeight": 340,
"fileSizeInByte": 27837,
"orientation": null,
"dateTimeOriginal": "2025-06-09T08:21:20.55+00:00",
"modifyDate": "2025-06-09T08:21:20.55+00:00",
"timeZone": null,
"lensModel": null,
"fNumber": null,
"focalLength": null,
"iso": null,
"exposureTime": null,
"latitude": null,
"longitude": null,
"city": null,
"state": null,
"country": null,
"description": "",
"projectionType": null,
"rating": null
},
"livePhotoVideoId": null,
"tags": [],
"people": [],
"unassignedFaces": [],
"checksum": "JfuHXhi3jxMf7NoEZ6WlDND4cDI=",
"stack": null,
"isOffline": false,
"hasMetadata": true,
"duplicateId": null,
"resized": true
}
And download them /api/assets/a0bbd94f-0d28-4b88-9bda-f34d10745720/original?c=NwgKDIKol4d6h4d9hoB4gFYGSA%3D%3D
Any of the commands for the locked assets which dont have a valid / none expired pin will return a not found response
{
"message": "Not found or no asset.download access",
"error": "Bad Request",
"statusCode": 400,
"correlationId": "3j8sgr8k"
}
All of these use the session cookie, During my poking around I don't think the pin expire time extends. So if you're able to to something with this and with a very long running pull etc you may have to reauthenticate / regenerate the session multiple times.
With this potential way to fetch the locked assets paired with https://github.com/simulot/immich-go/issues/945 would allow a [from-immich -> to-immich] full migration.
Which of course could be turned on by using the commands/parameters
I don't know [go] to have a go myself creating a PR but hopefully this tiny bit of research helps with any development.
I'm checking how this can be implemented
The session is unlocked for 15 minutes.
Immich-go is using the api call searchAssets.
The query field visibily=locked lists exclusively photos from the locked folder
The from-immich sub command is available also for archiving the content of the server on the disk.
The archive will contains the locked photos, without protection. Are you comfortable with this?
I'm checking how this can be implemented
The session is unlocked for 15 minutes.
Immich-go is using the api call searchAssets. The query field
visibily=lockedlists exclusively photos from the locked folderThe
from-immichsub command is available also for archiving the content of the server on the disk. The archive will contains the locked photos, without protection. Are you comfortable with this?
For me that doesn't matter as its only a initial migration, but for others it may cause a problem. But if others are willing to provide you with there pin then they must trust them.
But maybe something could be added to say encrypt the files on download so they cant be opened using the pin / other, with the addition of flags to encrypt / leave plain. and maybe another flag to decrypt/encrypt the directory if needed
Then when they are being moved 'recognise' that they are protected and require a pin to upload them.
I know its scope creep, for me personally just the ability to immich -> immich (including locked folder) is good enough for me :)