API and CLI
Everything the Frameleaf web app and apps do goes through the server’s API, and you can use it too. This page covers API keys, the Frameleaf command line tool for bulk uploads, calling the API from your own scripts, and the admin commands built into the server.
API keys
Section titled “API keys”Scripts and the CLI sign in with an API key rather than your password. Each key belongs to your account and only reaches what you can.
- Select Settings in the sidebar, then open Access & security.
- Under API keys, select Create API key.
- Give the key a name and choose the permissions it needs. Pick only what the tool needs, or full access.
- Create the key and copy it. It’s shown only once.
Keep API keys private, like passwords. Delete a key from the same list when you no longer need it.
The Frameleaf CLI
Section titled “The Frameleaf CLI”The Frameleaf CLI uploads photos and videos from a computer to your server, which is handy for big folders and archives. It can:
- upload files and folders, skipping anything already on the server
- create albums from folder names, or put everything into one album
- watch a folder and upload new files as they appear
- show the server’s version
- move a person’s whole library to another server, with
frameleaf migrate. See Move to a new server
Run it with Docker
Section titled “Run it with Docker”The easiest way is the CLI’s Docker image. Run it from the folder you want to upload; the folder is mounted read-only at /import:
docker run -it -v "$(pwd)":/import:ro \ -e FRAMELEAF_INSTANCE_URL=https://photos.example.com/api \ -e FRAMELEAF_API_KEY=your-api-key \ ghcr.io/frameleaf/frameleaf-cli:latest upload --recursive .Anything after the image name is passed to the frameleaf command. You can keep the key in a Docker env file instead of typing it.
Build it yourself
Section titled “Build it yourself”You can also build the CLI from the Frameleaf source code, which needs Node.js and pnpm:
pnpm installpnpm --filter @immich/sdk buildpnpm --filter @immich/cli buildnode packages/cli/dist/index.js --helpThe packages keep their original names; the command they build is frameleaf.
Sign in
Section titled “Sign in”frameleaf login https://photos.example.com/api YOUR-API-KEYUse your server’s address followed by /api. Your details are saved in auth.yml in ~/.config/frameleaf/. Keep that file safe, or run frameleaf logout when you’re done. An existing ~/.config/immich/ folder is still used as long as ~/.config/frameleaf/ doesn’t exist. Change the folder with -d or FRAMELEAF_CONFIG_DIR.
Instead of signing in, you can pass --url and --key, or set FRAMELEAF_INSTANCE_URL and FRAMELEAF_API_KEY.
Upload
Section titled “Upload”# a few filesframeleaf upload file1.jpg file2.jpg
# a folder and everything in itframeleaf upload --recursive directory/
# see what would happen firstframeleaf upload --dry-run --recursive directory/
# one album per folderframeleaf upload --album --recursive directory/
# everything into one albumframeleaf upload --album-name "Summer holiday" --recursive directory/
# skip any folder named Rawframeleaf upload --ignore "**/Raw/**" --recursive directory/
# straight into the archiveframeleaf upload --visibility archive --recursive directory/By default the CLI works out each file’s checksum before uploading, so it doesn’t send files the server already has. The server always checks for duplicates itself too, so --skip-hash only trades a little safety for speed on a fast connection. Hidden files and folders are skipped unless you add --include-hidden. Exclusion patterns work like an external library’s.
| Option | What it does | Environment variable |
|---|---|---|
-r, --recursive |
Include subfolders | FRAMELEAF_RECURSIVE |
-i, --ignore <pattern> |
Skip paths matching a pattern | FRAMELEAF_IGNORE_PATHS |
--skip-hash |
Don’t work out checksums before uploading | FRAMELEAF_SKIP_HASH |
-H, --include-hidden |
Include hidden folders | FRAMELEAF_INCLUDE_HIDDEN |
-a, --album |
Create albums from folder names | FRAMELEAF_AUTO_CREATE_ALBUM |
-A, --album-name <name> |
Add everything to this album | FRAMELEAF_ALBUM_NAME |
--visibility <visibility> |
archive, timeline, hidden or locked |
FRAMELEAF_VISIBILITY |
-n, --dry-run |
Show what would happen without uploading | FRAMELEAF_DRY_RUN |
-c, --concurrency <number> |
Files uploaded at once. Defaults to one less than your CPU cores | FRAMELEAF_UPLOAD_CONCURRENCY |
-j, --json-output |
Print details as JSON | FRAMELEAF_JSON_OUTPUT |
--delete |
Delete local files after uploading them | FRAMELEAF_DELETE_ASSETS |
--delete-duplicates |
Delete local files that are already on the server | FRAMELEAF_DELETE_DUPLICATES |
--no-progress |
Hide progress bars | FRAMELEAF_PROGRESS_BAR |
--watch |
Keep watching and upload new files automatically | FRAMELEAF_WATCH_CHANGES |
--json-output prints three lists: newFiles, duplicates and newAssets. Some log lines come first, so strip them before parsing:
frameleaf upload --dry-run --json-output . | tail -n +6 | jq .newFiles[]Other commands are frameleaf server-info, frameleaf logout and frameleaf help.
Old variable names
Section titled “Old variable names”Each FRAMELEAF_ variable the CLI reads also accepts its older IMMICH_ name, such as IMMICH_INSTANCE_URL or IMMICH_API_KEY, so existing scripts keep working. If both names are set to different values, the CLI refuses to start and names the pair. The immich command name also still works. Old names keep working for the current major version and stop in the next major release.
Use the API from your own scripts
Section titled “Use the API from your own scripts”The API lives at your server’s address followed by /api, and is described with the OpenAPI standard. Send your key in the x-api-key header.
This Python example uploads one file:
#!/usr/bin/python3import osfrom datetime import datetime
import requests
API_KEY = 'YOUR_API_KEY' # replace with a valid API keyBASE_URL = 'http://127.0.0.1:2283/api' # replace as needed
def upload(file): stats = os.stat(file) headers = { 'Accept': 'application/json', 'x-api-key': API_KEY, } data = { 'fileCreatedAt': datetime.fromtimestamp(stats.st_mtime), 'fileModifiedAt': datetime.fromtimestamp(stats.st_mtime), 'isFavorite': 'false', } with open(file, 'rb') as f: response = requests.post(f'{BASE_URL}/assets', headers=headers, data=data, files={'assetData': f}) print(response.json()) # {'id': 'ef96f635-61c7-4639-9e60-61a11c4bbfba', 'duplicate': False}
upload('./test.jpg')Administrators can also manage server credentials from scripts; see System settings.
Server admin commands
Section titled “Server admin commands”The server image includes frameleaf-admin for tasks you can’t do from the web app, such as resetting the administrator password, turning sign-in methods or maintenance mode on and off, printing a new server’s setup code and listing users. Run it from your Compose folder:
docker compose exec immich-server frameleaf-admin helpSee Server commands for every command, with examples.