Backup your Any.do tasks to JSON and Markdown.
Flow inspired by Any.do's own web implementation, ensuring this client remains respectful of Any.do's infrastructure while providing backup capabilities that do not currently exist on the official site.
This project is created as a tribute to Any.do's excellent task management service.
Flow inspired by Any.do's own efficient web implementation, ensuring this client remains respectful of Any.do's infrastructure while providing useful backup capabilities that do not currently exist on the official site.
- 🛡️ Server-Friendly: Designed to minimize impact on Any.do's infrastructure with smart change detection and incremental sync
- 🔐 Secure Authentication: Session persistence with email 2FA support
- 📊 Multiple Export Formats: JSON and Markdown exports
- uv
- Any.do account
git clone <repository-url>
cd anydo-api
uv sync
uv run anydownOn first run, the script will prompt you to create a config.json with your Any.do credentials. You'll then receive a 2FA code by email to paste in.
Credentials can also be supplied via environment variables (ANYDO_EMAIL, ANYDO_PASSWORD) or the config file directly.
uv run anydown # Smart sync (incremental when possible)
uv run anydown --full-sync # Force full sync
uv run anydown --quiet # Reduce output
uv run anydown --debug # Verbose debug logginguv run anydown-debug # Troubleshoot login issues
uv run anydown-dupes # Find duplicate tasks (dry run)
uv run anydown-dupes --delete # Fresh-sync, confirm, then delete via API
uv run anydown-dupes --delete --yes # Skip confirmation prompt
uv run anydown-dupes --keep newest # Keep newest copy instead of oldestRun as a scheduled cron job that syncs every hour:
docker compose up -dThis expects:
config.jsonin the repo root (mounted read-only)outputs/directory will be created for exports
The container uses supercronic to run hourly syncs. Session state is persisted in a Docker volume. Timezone is autodetected from the host via /etc/localtime.
To override the timezone sent to the Any.do API, set ANYDO_TIMEZONE in your environment or docker-compose.yml.
config.json (auto-created on first run, gitignored):
{
"email": "your@email.com",
"password": "your_password",
"save_raw_data": true,
"auto_export": true,
"text_wrap_width": 80,
"dedup_keep": "oldest"
}dedup_keep controls which copy anydown-dupes --delete preserves: "oldest" (default) or "newest". Tasks are only considered duplicates when their title, list, parent task, note, and subtasks all match exactly.
If you hit login issues (2FA complications, rate limiting), you can extract a session cookie from your browser:
- Open Any.do in your browser, ensure you're logged in
- Open DevTools (F12) > Application > Cookies >
https://any.do - Copy the
SPRING_SECURITY_REMEMBER_ME_COOKIEvalue - Create
session.json(seesession.json.examplefor the template)
outputs/
├── raw-json/ # Complete API responses
└── markdown/ # Formatted task tables
Files are timestamped (YYYY-MM-DD_HHMM-SS_anydo-tasks.*) and only created when data has actually changed (SHA-256 hash comparison).
uv sync # Install all deps (including dev)
uv run pytest -v # Run tests
uv run pytest -v --cov=anydown # With coverage
uv run ruff check . # Lint
uv run ruff check --fix . # Auto-fix lint
uv run ruff format . # FormatOr via Make:
make test
make lint
make formatanydo-api/
├── src/anydown/
│ ├── __init__.py
│ ├── client.py # API client library
│ ├── cli.py # Main CLI entry point
│ ├── debug_login.py # Login troubleshooting
│ └── find_duplicates.py # Duplicate finder & remover
├── tests/
├── pyproject.toml # Config, deps, tool settings
├── uv.lock # Locked dependency versions
├── Dockerfile
├── docker-compose.yml
├── entrypoint.sh
└── crontab
- Smart sync: Uses incremental sync to check if anything changed since the last run; only performs a full download when changes are detected
- Session persistence: Saves auth session to avoid repeated 2FA prompts
- Change detection: SHA-256 hashing of exported data prevents writing duplicate files
- Rate limiting: Client-side cooldown prevents full syncs more than once per minute
- Compression: Requests gzip/br/zstd; decompression handled by the HTTP library
- Retry logic: Exponential backoff with automatic retries on 429/5xx
Made with love for the Any.do community