A JSON-first command-line wrapper around the official Todoist Python SDK.
todoist-cli exposes the synchronous TodoistAPI surface as shell commands, plus a local automation layer for incremental sync, heartbeat/task context, webhooks, and OAuth helpers. It is designed to work for humans in a terminal and for automation agents such as OpenClaw, Claude Code, Codex, Cursor, or shell scripts.
Requirements:
- Python 3.10+
- A Python build with the standard-library
sqlite3module - A Todoist API token for personal/local use, or Todoist OAuth app credentials for third-party/webhook use
From this repository:
python3 -m pip install .For isolated CLI installation from GitHub:
pipx install 'git+https://github.com/jonasrombach/todoist-cli.git'
# or
uv tool install 'git+https://github.com/jonasrombach/todoist-cli.git'For development:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e '.[dev]'For personal/local use, set a Todoist API token in the environment:
export TODOIST_API_TOKEN="***"TODOIST_TOKEN is also accepted as a legacy alias.
For a dotenv-style local file, prefer the neutral config path:
mkdir -p "$HOME/.config/todoist-cli"
printf 'TODOIST_API_TOKEN=***\n' > "$HOME/.config/todoist-cli/env"
chmod 600 "$HOME/.config/todoist-cli/env"Or point to any explicit dotenv file:
export TODOIST_CLI_ENV_FILE="$HOME/.config/todoist-cli/env"Credential lookup order:
TODOIST_API_TOKENTODOIST_TOKENTODOIST_CLI_ENV_FILE, if set$HOME/.config/todoist-cli/env
Never commit real tokens or local dotenv files.
todoist-cli projects list
todoist-cli tasks list --limit 20
todoist-cli tasks add "Buy milk" --due-string tomorrow --priority P2
todoist-cli tasks get <task_id>
todoist-cli labels list
todoist-cli raw get_tasks --limit 5
todoist-cli heartbeat-contextBy default, output is compact JSON. Use --format pretty for indented JSON:
todoist-cli --format pretty tasks list --limit 5The CLI accepts Todoist UI priority names as well as raw API numbers:
--priority P1maps to API priority4.--priority P2maps to API priority3.--priority P3maps to API priority2.--priority P4,normal, ornonemaps to API priority1.
Raw numeric --priority 1..4 is still accepted for SDK/API parity.
todoist-cli is intended to be safe to call from shell scripts and agent runtimes.
- stdout contains the command result as JSON.
- stderr is reserved for human-readable diagnostics and errors.
- Exit code
0means success. - Non-zero exit means failure.
- Global flags such as
--formatmust come before the subcommand. - Mutating commands do not ask for confirmation; callers and agents must apply their own safety policy before invoking actions such as
add,update,move,complete,archive, ordelete. - Error output on stdout is a structured JSON envelope:
{"status":"error","error_type":"ValueError","message":"..."}When available, errors also include:
{"status_code":429,"request_id":"...","retryable":true}Useful patterns:
todoist-cli tasks list --limit 5 | jq .
todoist-cli --format pretty sync status
todoist-cli heartbeat-context --no-sync | jq '.tasks.today'The sync store contains personal Todoist data such as task titles, due dates, project IDs/names, labels, URLs, completed-task evidence, deleted-task evidence, and a compact local change log. For deleted tasks that were previously known locally, the store keeps a small last-known snapshot for reconciliation (title, project/section IDs, due/deadline, labels, priority, and description_present). Full task descriptions are not duplicated into deleted-task tombstones.
Default state location:
${XDG_STATE_HOME:-~/.local/state}/todoist-cli/
Files:
sync-state.sqlite3— default local sync state database.sync-state.json— legacy/optional JSON backend state.oauth-tokens.json— OAuth tokens written with mode0600.webhook-receipts.jsonl— privacy-preserving webhook receipts; rawevent_datais intentionally not stored.
Choose a custom state directory:
todoist-cli sync status --state-dir /path/to/stateChoose a state backend:
todoist-cli sync status --state-backend sqlite
todoist-cli sync status --state-backend json
export TODOIST_CLI_STATE_BACKEND=sqliteMigrate legacy JSON state into SQLite:
todoist-cli sync migrate
todoist-cli sync migrate --remove-jsonReset local sync state:
todoist-cli sync resetsync reset removes local state only. It does not delete Todoist tasks.
Bootstrap or update local state:
todoist-cli sync pull
todoist-cli sync statusForce a full sync:
todoist-cli sync pull --fullBackfill completed-task evidence:
todoist-cli sync backfill-completed --strategy completion-date --window-days 14
todoist-cli sync backfill-completed --strategy due-date --window-days 14Build compact task context from the local store:
todoist-cli heartbeat-context
todoist-cli heartbeat-context --no-syncHeartbeat buckets include:
overduetodaynext_7_dayspostponedhigh_priority_no_near_duecompleteddeleted_unknown— non-actionable deletion/reconciliation evidence. Deleted entries haveattention_required: false; if the task was previously seen locally, the output may include the last-known title/project metadata so agents can suppress stale nudges without treating the deleted task as active work.
Tasks in archived or deleted projects are treated as inactive by the sync-store payload layer and are suppressed from active automation contexts. Archiving a project is therefore treated as “not nudgeable” even if Todoist still returns the project’s tasks in raw sync state.
Personal API tokens are enough for local CLI use. Todoist webhooks require a Todoist app and user OAuth activation.
OAuth helper flow:
todoist-cli oauth authorize-url \
--client-id <client_id> \
--redirect-uri https://example.com/todoist/callback \
--scope data:read_write \
--state <random_csrf_state>
todoist-cli oauth exchange-token \
--client-id <client_id> \
--client-secret <client_secret> \
--code <code_from_redirect> \
--redirect-uri https://example.com/todoist/callback
todoist-cli oauth refresh-token \
--client-id <client_id> \
--client-secret <client_secret>Webhook receiver:
todoist-cli webhook serve \
--secret <webhook_secret> \
--host 127.0.0.1 \
--port 8080 \
--allow-event item:added \
--allow-event item:updatedTodoist requires webhook callback URLs to use public HTTPS and no explicit port. For a local machine, use a reverse proxy, tunnel, or relay. Webhooks are wake-up signals, not canonical state: the receiver verifies X-Todoist-Hmac-SHA256, records a receipt, debounces events, then triggers incremental /sync.
Top-level commands:
tasks— task operations.projects— project operations.sections— section operations.labels— personal label operations.shared-labels— shared label operations.reminders— reminder operations.location-reminders— location reminder operations.comments— task/project comments.collaborators— project collaborators.completed— completed-task endpoints.raw— call an exact SDK method by name.sync— local sync state commands.heartbeat-context— compact automation context from Todoist state.webhook— webhook receipt/server commands.oauth— OAuth helper commands.models— inspect known method specs.
Discover exact flags locally:
todoist-cli --help
todoist-cli tasks --help
todoist-cli tasks add --help
todoist-cli sync --help
todoist-cli webhook --help
todoist-cli oauth --help
todoist-cli models list | jq '.methods.add_task'For uncommon SDK operations:
todoist-cli raw get_tasks --limit 20
todoist-cli raw call get_task <task_id>
todoist-cli raw call update_task <task_id> --kwargs '{"content":"New title"}'Implemented for the synchronous TodoistAPI client:
- 51/51 public
TodoistAPImethods exposed. - All current public method parameters from the installed SDK are represented in the CLI specs.
- Resource groups covered: tasks, projects, sections, labels, shared labels, reminders, location reminders, comments, collaborators, completed tasks.
- Paginated SDK iterators are flattened to JSON arrays by default.
rawexposes exact SDK method names for less common operations.
This project is currently an SDK wrapper plus local sync/automation layer.
Todoist API v1 includes additional surfaces that may not be first-class CLI commands here yet, including uploads, templates, backups, activity logs, workspace/admin features, dynamic client registration, MCP, and broader /sync write flows. Some Todoist features also depend on the authenticated user's plan or workspace permissions, especially reminders, comments, backups, uploads, filters, templates, and activity logs.
When a feature is unavailable, expect Todoist/SDK errors to be returned through the structured error contract.
- Never commit real Todoist tokens,
.envfiles, OAuth token stores, API responses, task exports, sync databases, or logs containing personal task contents. - Treat
heartbeat-context,sync-state.sqlite3, andoauth-tokens.jsonas personal data. - Public examples should stay generic and avoid private project names, addresses, contacts, or task contents.
- Webhook receipts intentionally omit raw Todoist payloads.
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e '.[dev]'
pytest -q
ruff check .SDK coverage test:
pytest -q tests/test_sdk_coverage.pyRelease checklist:
pytest -q
ruff check .
python -m compileall -q src tests
python -m build
python -m twine check dist/*Not required for the first public release, but plausible future work:
- Publish to PyPI.
- Generate static command-reference docs from argparse/model specs.
- Add shell completion.
- Add platform-native state directories for Windows/macOS if XDG paths are not enough.
- Add first-class non-SDK Todoist API v1 commands where real workflows need them.
- Add async/custom client mode if this grows into a daemon or needs advanced timeout/proxy/tracing behavior.
- Add production webhook deployment recipes for common hosts/tunnels/relays.