Fast, parallel photo sync from the cloud to local storage. Single binary, runs unattended.
- Parallel downloads - configurable concurrency, starts downloading before enumeration completes
- Incremental sync - scans large libraries in seconds via CloudKit sync tokens, only fetches what changed
- Resumable transfers - partial downloads resume via HTTP Range, verified by size and content hash
- Single binary - no runtime dependencies, runs on macOS, Linux, and Windows
- Unattended operation - watch mode, systemd integration, headless 2FA, Docker-ready
iCloud Photos is supported today. Google Takeout and Immich are next.
Tip
Coming from icloudpd? The Migration Guide maps every flag and shows how to pick up where you left off without re-downloading.
Homebrew
brew install rhoopr/kei/keiDocker
docker pull ghcr.io/rhoopr/kei:latestSee the Docker guide for compose files and headless 2FA.
Pre-built binaries
Grab one from GitHub Releases. macOS (Apple Silicon + Intel), Linux (ARM64 + x86_64), Windows (x86_64).
From source
git clone https://github.com/rhoopr/kei.git kei && cd kei
cargo build --releaseImportant
If you have Advanced Data Protection (ADP) enabled on your iCloud account, kei can't access your photos. ADP blocks the web API that kei uses. To fix this, you need to change both settings on your iPhone/iPad: disable ADP (Settings > Apple ID > iCloud > Advanced Data Protection) and enable "Access iCloud Data on the Web" (Settings > Apple ID > iCloud). See the Authentication wiki for details.
kei sync -u you@example.com -d ~/Photos/iCloud --save-passwordYou'll be prompted for your password (or set ICLOUD_PASSWORD), then asked to approve 2FA on a trusted device. Downloads start right after. --save-password encrypts your password in the OS keyring (or an AES-256 file on headless systems), and kei saves your username and directory to ~/.config/kei/config.toml, so subsequent runs are just:
kei syncOr use the interactive wizard: kei config setup.
For Docker, cron, and systemd setups, --password-file and --password-command give you more control over secret delivery. See the Credentials wiki page.
# Specific albums, skip videos, last 100 photos only
kei sync --album "Favorites" --recent 100 --skip-videos
# All libraries (personal + shared) in one run
kei sync --library all
# Keep syncing every hour
kei sync --watch-with-interval 3600
# Preview what would download
kei sync --only-print-filenames
# Dry run (no writes to disk)
kei sync --dry-runRun kei sync --help for all flags, or see the wiki for the full CLI reference.
kei downloads on a streaming pipeline - it starts fetching files as soon as the first API page comes back, rather than waiting to enumerate the whole library. After the first full sync, it uses Apple's CloudKit syncToken to pull only what changed. A no-change check takes 1-2 API calls.
Downloads run with configurable concurrency (default 10). Partial downloads are saved as .kei-tmp files and resumed via HTTP Range headers. Every file is verified against its expected size and content-type before being committed.
State lives in a SQLite database alongside your session data (see --data-dir). The DB tracks what's been downloaded, what failed, and where files landed on disk.
| Command | |
|---|---|
sync |
Download photos |
login |
Authenticate and complete 2FA |
list |
List albums or libraries |
password |
Manage stored credentials (set, clear, backend) |
config |
Show resolved config (show) or run the setup wizard (setup) |
reset |
Delete state database (state) or clear sync tokens (sync-token) |
status |
Show sync stats and database summary |
verify |
Check downloads exist; --checksums for SHA256 |
import-existing |
Import local files so they aren't re-downloaded |
- SQLite state tracking - never re-downloads what it already has
- Watch mode with systemd notify, PID file, graceful shutdown
- Multi-library sync (
--library allfor personal + shared) - Flexible password sources: prompt, env var, file, shell command, OS keyring
- Content filtering: live photo mode, filename globs, album exclusions, date ranges,
--recent N - Flexible folder structure with
{album}token and full strftime support, EXIF datetime stamping - Multi-arch Docker images (amd64/arm64) with headless 2FA
- Notification scripts on events (2FA required, sync complete, failures)
- TOML config with env var overrides (
KEI_*) for every flag - Structured exit codes (0 success, 2 partial, 3 auth) for scripting
- Wiki - full CLI reference, configuration, Docker, troubleshooting
- Migration Guide - switching from
icloudpd - Changelog
- How iCloud's Incremental Sync Works - deep dive on CloudKit syncTokens
Contributions welcome. Open an issue first if you're planning something big.
cargo fmt -- --check && cargo clippy && cargo testMIT - see LICENSE.md
kei started as icloudpd-rs, a Rust rewrite of icloud-photos-downloader. Thanks to the original maintainers for their reverse-engineering work.
