Content
# ticktick-sdk: A TickTick MCP Server & Full Python SDK

[](https://www.python.org/downloads/)
[](https://opensource.org/licenses/MIT)
[](https://pepy.tech/projects/ticktick-sdk)
A comprehensive async Python SDK for [TickTick](https://ticktick.com) with [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) server support.
Includes full support for [Dida365 (滴答清单)](https://dida365.com) as well.
**Use TickTick programmatically from Python, or let AI assistants manage your tasks.**
## Table of Contents
- [Features](#features)
- [Why This Library?](#why-this-library)
- [Installation](#installation)
- [MCP Server Setup & Usage](#mcp-server-setup--usage)
- [Step 1: Register Your App](#step-1-register-your-app)
- [Step 2: Get OAuth2 Access Token](#step-2-get-oauth2-access-token)
- [Step 3: Configure Your AI Assistant](#step-3-configure-your-ai-assistant)
- [CLI Reference](#cli-reference)
- [Example Conversations](#example-conversations)
- [Available MCP Tools](#available-mcp-tools-43-total)
- [Python Library Setup & Usage](#python-library-setup--usage)
- [Setup](#setup)
- [Quick Start](#quick-start)
- [Tasks](#tasks)
- [Projects & Folders](#projects--folders)
- [Tags](#tags)
- [Habits](#habits)
- [Focus/Pomodoro](#focuspomodoro)
- [User & Statistics](#user--statistics)
- [Error Handling](#error-handling)
- [Architecture](#architecture)
- [API Reference](#api-reference)
- [TickTick API Quirks](#important-ticktick-api-quirks)
- [Environment Variables](#environment-variables)
- [Running Tests](#running-tests)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
---
## Features
### MCP Server
- **43 Tools**: Streamlined coverage of TickTick functionality
- **Batch Operations**: All mutations accept lists (1-100 items) for bulk operations
- **AI-Ready**: Works with Claude, GPT, and other MCP-compatible assistants
- **Dual Output**: Markdown for humans, JSON for machines
### Python Library
- **Full Async Support**: Built on `httpx` for high-performance async operations
- **Batch Operations**: Create, update, delete, complete up to 100 tasks in a single call
- **Complete Task Management**: Create, read, update, delete, complete, move, pin tasks
- **Kanban Boards**: Full column management (create, update, delete, move tasks between columns)
- **Project Organization**: Projects, folders, kanban boards
- **Tag System**: Hierarchical tags with colors
- **Habit Tracking**: Full CRUD for habits with batch check-ins, streaks, and goals
- **Focus/Pomodoro**: Access focus session data and statistics
- **User Analytics**: Productivity scores, levels, completion rates
### Developer Experience
- **Type-Safe**: Full Pydantic v2 validation with comprehensive type hints
- **Well-Tested**: 300+ tests covering both mock and live API interactions
- **Documented**: Extensive docstrings and examples
---
## Why This Library?
### The Two-API Problem
TickTick has **two different APIs**:
| API | Type | What We Use It For |
|-----|------|-------------------|
| **V1 (OAuth2)** | Official, documented | Project with all tasks, basic operations |
| **V2 (Session)** | Unofficial, reverse-engineered | Tags, folders, habits, focus, subtasks, and more |
The official V1 API is limited. Most of TickTick's power features (tags, habits, focus tracking) are only available through the undocumented V2 web API. **This library combines both**, routing each operation to the appropriate API automatically.
### Compared to Other Libraries
Based on analysis of the actual source code of available TickTick Python libraries:
| Feature | ticktick-sdk | [pyticktick](https://github.com/sebpretzer/pyticktick) | [ticktick-py](https://github.com/lazeroffmichael/ticktick-py) | [tickthon](https://github.com/anggelomos/tickthon) | [ticktick-python](https://github.com/glasslion/ticktick-python) |
|---------|:------------:|:----------:|:-----------:|:--------:|:---------------:|
| **I/O Model** | Async | Async | Sync | Sync | Sync |
| **Type System** | Pydantic V2 | Pydantic V2 | Dicts | attrs | addict |
| **MCP Server** | **Yes** | No | No | No | No |
| **Habits** | **Full CRUD** | No | Basic | Basic | No |
| **Focus/Pomo** | Yes | Yes | Yes | Yes | No |
| **Unified V1+V2** | **Smart Routing** | Separate | Both | V2 only | V2 only |
| **Subtasks** | Advanced | Batch | Yes | Basic | Basic |
| **Tags** | Full (merge/rename) | Yes | Yes | Yes | No |
**Key Differentiators:**
- **MCP Server**: Only ticktick-sdk provides AI assistant integration via Model Context Protocol
- **Unified API Routing**: Automatically routes operations to V1 or V2 based on feature requirements
- **Full Habit CRUD**: Complete habit management including check-ins, streaks, archive/unarchive
- **Async-First**: Built on `httpx` for high-performance async operations
---
## Installation
```bash
pip install ticktick-sdk
```
**Requirements:**
- Python 3.11+
- TickTick account (free or Pro)
---
## MCP Server Setup & Usage
Use TickTick with AI assistants like Claude through the Model Context Protocol.
### Step 1: Register Your App
1. Go to the [TickTick Developer Portal](https://developer.ticktick.com/manage)
2. Click **"Create App"**
3. Fill in:
- **App Name**: e.g., "My TickTick MCP"
- **Redirect URI**: `http://127.0.0.1:8080/callback`
4. Save your **Client ID** and **Client Secret**
### Step 2: Get OAuth2 Access Token
Run the auth command with your credentials:
```bash
TICKTICK_CLIENT_ID=your_client_id \
TICKTICK_CLIENT_SECRET=your_client_secret \
ticktick-sdk auth
```
This will:
1. **Open your browser** to TickTick's authorization page
2. **Authorize the app** - Click "Authorize" to grant access
3. **Return to terminal** - After authorizing, you'll see output like this:
```
============================================================
SUCCESS! Here is your access token:
============================================================
a]234abc-5678-90de-f012-34567890abcd
============================================================
NEXT STEPS:
For Claude Code users:
Run (replace YOUR_* placeholders):
claude mcp add ticktick \
-e TICKTICK_CLIENT_ID=YOUR_CLIENT_ID \
...
```
4. **Copy this token** - You'll need it in the next step
> **Note**: Sometimes the browser shows an "invalid credentials" error page. Just refresh the page and it should work.
> **SSH/Headless Users**: Add `--manual` flag for a text-based flow that doesn't require a browser.
### Step 3: Configure Your AI Assistant
#### Claude Code (Recommended)
```bash
claude mcp add ticktick \
-e TICKTICK_CLIENT_ID=your_client_id \
-e TICKTICK_CLIENT_SECRET=your_client_secret \
-e TICKTICK_ACCESS_TOKEN=your_access_token \
-e TICKTICK_USERNAME=your_email \
-e TICKTICK_PASSWORD=your_password \
-- ticktick-sdk
```
> **Note**: For `TICKTICK_ACCESS_TOKEN`, paste the token you copied from Step 2.
Verify it's working:
```bash
claude mcp list # See all configured servers
/mcp # Within Claude Code, check server status
```
#### Claude Desktop
Add to your Claude Desktop config:
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"ticktick": {
"command": "ticktick-sdk",
"env": {
"TICKTICK_CLIENT_ID": "your_client_id",
"TICKTICK_CLIENT_SECRET": "your_client_secret",
"TICKTICK_ACCESS_TOKEN": "your_access_token",
"TICKTICK_USERNAME": "your_email",
"TICKTICK_PASSWORD": "your_password"
}
}
}
}
```
#### Other MCP-Compatible Tools
This server works with any tool that supports the Model Context Protocol, which includes most modern AI assistants and IDEs. The configuration is similar - you just need to provide the command (`ticktick-sdk`) and the environment variables shown above.
### CLI Reference
The `ticktick-sdk` command provides several subcommands:
| Command | Description |
|---------|-------------|
| `ticktick-sdk` | Start the MCP server (default) |
| `ticktick-sdk server` | Start the MCP server (explicit) |
| `ticktick-sdk server --host HOST` | Use specific API host (`ticktick.com` or `dida365.com`) |
| `ticktick-sdk server --enabledModules MODULES` | Enable only specific tool modules (comma-separated) |
| `ticktick-sdk server --enabledTools TOOLS` | Enable only specific tools (comma-separated) |
| `ticktick-sdk auth` | Get OAuth2 access token (opens browser) |
| `ticktick-sdk auth --manual` | Get OAuth2 access token (SSH-friendly) |
| `ticktick-sdk --version` | Show version information |
| `ticktick-sdk --help` | Show help message |
**Tool Filtering** (reduces context window usage for AI assistants):
```bash
# Enable only task and project tools
ticktick-sdk server --enabledModules tasks,projects
# Enable specific tools only
ticktick-sdk server --enabledTools ticktick_create_tasks,ticktick_list_tasks
# Available modules: tasks, projects, folders, columns, tags, habits, user, focus
```
### Example Conversations
Once configured, you can ask Claude things like:
- "What tasks do I have due today?"
- "Create a task to call John tomorrow at 2pm"
- "Show me my high priority tasks"
- "Mark the grocery shopping task as complete"
- "What's my current streak for the Exercise habit?"
- "Check in my meditation habit for today"
- "Create a new habit to drink 8 glasses of water daily"
### Available MCP Tools (43 Total)
All mutation tools accept lists for batch operations (1-100 items).
#### Task Tools (Batch-Capable)
| Tool | Description |
|------|-------------|
| `ticktick_create_tasks` | Create 1-50 tasks with titles, dates, tags, etc. |
| `ticktick_get_task` | Get task details by ID |
| `ticktick_list_tasks` | List tasks (active/completed/abandoned/deleted via status filter) |
| `ticktick_update_tasks` | Update 1-100 tasks (includes column assignment) |
| `ticktick_complete_tasks` | Complete 1-100 tasks |
| `ticktick_delete_tasks` | Delete 1-100 tasks (moves to trash) |
| `ticktick_move_tasks` | Move 1-50 tasks between projects |
| `ticktick_set_task_parents` | Set parent-child relationships for 1-50 tasks |
| `ticktick_unparent_tasks` | Remove parent relationships from 1-50 tasks |
| `ticktick_search_tasks` | Search tasks by text |
| `ticktick_pin_tasks` | Pin or unpin 1-100 tasks |
#### Project Tools
| Tool | Description |
|------|-------------|
| `ticktick_list_projects` | List all projects |
| `ticktick_get_project` | Get project details with tasks |
| `ticktick_create_project` | Create a new project |
| `ticktick_update_project` | Update project properties |
| `ticktick_delete_project` | Delete a project |
#### Folder Tools
| Tool | Description |
|------|-------------|
| `ticktick_list_folders` | List all folders |
| `ticktick_create_folder` | Create a folder |
| `ticktick_rename_folder` | Rename a folder |
| `ticktick_delete_folder` | Delete a folder |
#### Kanban Column Tools
| Tool | Description |
|------|-------------|
| `ticktick_list_columns` | List columns for a kanban project |
| `ticktick_create_column` | Create a kanban column |
| `ticktick_update_column` | Update column name or order |
| `ticktick_delete_column` | Delete a kanban column |
#### Tag Tools
| Tool | Description |
|------|-------------|
| `ticktick_list_tags` | List all tags |
| `ticktick_create_tag` | Create a tag with color |
| `ticktick_update_tag` | Update tag properties (includes rename via label) |
| `ticktick_delete_tag` | Delete a tag |
| `ticktick_merge_tags` | Merge two tags |
#### Habit Tools (Batch-Capable)
| Tool | Description |
|------|-------------|
| `ticktick_habits` | List all habits |
| `ticktick_habit` | Get habit details |
| `ticktick_habit_sections` | List sections (morning/afternoon/night) |
| `ticktick_create_habit` | Create a new habit |
| `ticktick_update_habit` | Update habit properties (includes archive/unarchive) |
| `ticktick_delete_habit` | Delete a habit |
| `ticktick_checkin_habits` | Check in 1-50 habits (supports backdating) |
| `ticktick_habit_checkins` | Get check-in history |
#### User & Analytics Tools
| Tool | Description |
|------|-------------|
| `ticktick_get_profile` | Get user profile |
| `ticktick_get_status` | Get account status |
| `ticktick_get_statistics` | Get productivity stats |
| `ticktick_get_preferences` | Get user preferences |
| `ticktick_focus_heatmap` | Get focus heatmap data |
| `ticktick_focus_by_tag` | Get focus time by tag |
---
## Python Library Setup & Usage
Use TickTick programmatically in your Python applications.
### Setup
#### Step 1: Register Your App
Same as MCP setup - go to the [TickTick Developer Portal](https://developer.ticktick.com/manage) and create an app.
#### Step 2: Create Your .env File
Create a `.env` file in your project directory:
```bash
# V1 API (OAuth2)
TICKTICK_CLIENT_ID=your_client_id_here
TICKTICK_CLIENT_SECRET=your_client_secret_here
TICKTICK_REDIRECT_URI=http://127.0.0.1:8080/callback
TICKTICK_ACCESS_TOKEN= # Will be filled in Step 3
# V2 API (Session)
TICKTICK_USERNAME=your_ticktick_email@example.com
TICKTICK_PASSWORD=your_ticktick_password
# Optional
TICKTICK_TIMEOUT=30
```
#### Step 3: Get OAuth2 Access Token
```bash
# Source your .env file first, or export the variables
ticktick-sdk auth
```
Copy the access token to your `.env` file.
#### Step 4: Verify Setup
```python
import asyncio
from ticktick_sdk import TickTickClient
async def test():
async with TickTickClient.from_settings() as client:
profile = await client.get_profile()
print(f'Connected as: {profile.display_name}')
asyncio.run(test())
```
### Quick Start
```python
import asyncio
from ticktick_sdk import TickTickClient
async def main():
async with TickTickClient.from_settings() as client:
# Create a task
task = await client.create_task(
title="Learn ticktick-sdk",
tags=["python", "productivity"],
)
print(f"Created: {task.title} (ID: {task.id})")
# List all tasks
tasks = await client.get_all_tasks()
print(f"You have {len(tasks)} active tasks")
# Complete the task
await client.complete_task(task.id, task.project_id)
print("Task completed!")
asyncio.run(main())
```
### Tasks
#### Creating Tasks
```python
from datetime import datetime, timedelta
from ticktick_sdk import TickTickClient
async with TickTickClient.from_settings() as client:
# Simple task
task = await client.create_task(title="Buy groceries")
# Task with due date and priority
task = await client.create_task(
title="Submit report",
due_date=datetime.now() + timedelta(days=1),
priority="high", # none, low, medium, high
)
# Task with tags and content
task = await client.create_task(
title="Review PR #123",
content="Check for:\n- Code style\n- Tests\n- Documentation",
tags=["work", "code-review"],
)
# Recurring task (MUST include start_date!)
task = await client.create_task(
title="Daily standup",
start_date=datetime(2025, 1, 20, 9, 0),
recurrence="RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR",
)
# Task with reminder
task = await client.create_task(
title="Meeting with team",
due_date=datetime(2025, 1, 20, 14, 0),
reminders=["TRIGGER:-PT15M"], # 15 minutes before
)
# All-day task
task = await client.create_task(
title="Project deadline",
due_date=datetime(2025, 1, 31),
all_day=True,
)
```
#### Managing Tasks
```python
async with TickTickClient.from_settings() as client:
# Get a specific task
task = await client.get_task(task_id="...")
# Update a task
task.title = "Updated title"
task.priority = 5 # high priority
await client.update_task(task)
# Complete a task
await client.complete_task(task_id="...", project_id="...")
# Delete a task (moves to trash)
await client.delete_task(task_id="...", project_id="...")
# Move task to another project
await client.move_task(
task_id="...",
from_project_id="...",
to_project_id="...",
)
```
#### Subtasks
```python
async with TickTickClient.from_settings() as client:
# Create parent task
parent = await client.create_task(title="Main task")
# Create child task
child = await client.create_task(title="Subtask")
# Make it a subtask (parent_id in create is ignored by API)
await client.make_subtask(
task_id=child.id,
parent_id=parent.id,
project_id=child.project_id,
)
# Remove parent relationship
await client.unparent_subtask(
task_id=child.id,
project_id=child.project_id,
)
```
#### Querying Tasks
```python
async with TickTickClient.from_settings() as client:
# All active tasks
all_tasks = await client.get_all_tasks()
# Tasks due today
today = await client.get_today_tasks()
# Overdue tasks
overdue = await client.get_overdue_tasks()
# Tasks by tag
work_tasks = await client.get_tasks_by_tag("work")
# Tasks by priority
urgent = await client.get_tasks_by_priority("high")
# Search tasks
results = await client.search_tasks("meeting")
# Recently completed
completed = await client.get_completed_tasks(days=7, limit=50)
# Abandoned tasks ("won't do")
abandoned = await client.get_abandoned_tasks(days=30)
# Deleted tasks (in trash)
deleted = await client.get_deleted_tasks(limit=50)
```
### Projects & Folders
#### Projects
```python
async with TickTickClient.from_settings() as client:
# List all projects
projects = await client.get_all_projects()
for project in projects:
print(f"{project.name} ({project.id})")
# Get project with all its tasks
project_data = await client.get_project_tasks(project_id="...")
print(f"Project: {project_data.project.name}")
print(f"Tasks: {len(project_data.tasks)}")
# Create a project
project = await client.create_project(
name="Q1 Goals",
color="#4A90D9",
view_mode="kanban", # list, kanban, timeline
)
# Update a project
await client.update_project(
project_id=project.id,
name="Q1 Goals 2025",
color="#FF5500",
)
# Delete a project
await client.delete_project(project_id="...")
```
#### Folders (Project Groups)
```python
async with TickTickClient.from_settings() as client:
# List all folders
folders = await client.get_all_folders()
# Create a folder
folder = await client.create_folder(name="Work Projects")
# Create project in folder
project = await client.create_project(
name="Client A",
folder_id=folder.id,
)
# Rename a folder
await client.rename_folder(folder_id=folder.id, name="Work")
# Delete a folder
await client.delete_folder(folder_id="...")
```
### Tags
Tags in TickTick support hierarchy (parent-child relationships) and custom colors.
```python
async with TickTickClient.from_settings() as client:
# List all tags
tags = await client.get_all_tags()
for tag in tags:
print(f"{tag.label} ({tag.name}) - {tag.color}")
# Create a tag
tag = await client.create_tag(
name="urgent",
color="#FF0000",
)
# Create nested tag
child_tag = await client.create_tag(
name="critical",
parent="urgent", # Parent tag name
)
# Rename a tag
await client.rename_tag(old_name="urgent", new_name="priority")
# Update tag color or parent
await client.update_tag(
name="priority",
color="#FF5500",
)
# Merge tags (move all tasks from source to target)
await client.merge_tags(source="old-tag", target="new-tag")
# Delete a tag
await client.delete_tag(name="obsolete")
```
### Habits
TickTick habits are recurring activities you want to track daily.
#### Habit Types
| Type | Description | Example |
|------|-------------|---------|
| `Boolean` | Simple yes/no | "Did you exercise today?" |
| `Real` | Numeric counter | "How many pages did you read?" |
#### Creating and Managing Habits
```python
async with TickTickClient.from_settings() as client:
# List all habits
habits = await client.get_all_habits()
# Boolean habit (yes/no)
exercise = await client.create_habit(
name="Exercise",
color="#4A90D9",
reminders=["07:00", "19:00"],
target_days=30,
encouragement="Stay strong!",
)
# Numeric habit
reading = await client.create_habit(
name="Read",
habit_type="Real",
goal=30, # 30 pages per day
step=5, # +5 button increment
unit="Pages",
)
# Check in a habit (today)
habit = await client.checkin_habit("habit_id")
print(f"Streak: {habit.current_streak} days!")
# Check in for a past date (backdate)
from datetime import date
habit = await client.checkin_habit("habit_id", checkin_date=date(2025, 12, 15))
# Archive/unarchive
await client.archive_habit("habit_id")
await client.unarchive_habit("habit_id")
```
#### Habit Repeat Rules (RRULE Format)
| Schedule | RRULE |
|----------|-------|
| Daily (every day) | `RRULE:FREQ=WEEKLY;BYDAY=SU,MO,TU,WE,TH,FR,SA` |
| Weekdays only | `RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR` |
| Weekends only | `RRULE:FREQ=WEEKLY;BYDAY=SA,SU` |
| X times per week | `RRULE:FREQ=WEEKLY;TT_TIMES=5` |
| Specific days | `RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR` |
### Focus/Pomodoro
```python
from datetime import date, timedelta
async with TickTickClient.from_settings() as client:
# Focus heatmap (like GitHub contribution graph)
heatmap = await client.get_focus_heatmap(
start_date=date.today() - timedelta(days=90),
end_date=date.today(),
)
# Focus time by tag
by_tag = await client.get_focus_by_tag(days=30)
for tag, seconds in sorted(by_tag.items(), key=lambda x: -x[1]):
hours = seconds / 3600
print(f" {tag}: {hours:.1f} hours")
```
### User & Statistics
```python
async with TickTickClient.from_settings() as client:
# User profile
profile = await client.get_profile()
print(f"Username: {profile.username}")
# Account status
status = await client.get_status()
print(f"Pro User: {status.is_pro}")
print(f"Inbox ID: {status.inbox_id}")
# Productivity statistics
stats = await client.get_statistics()
print(f"Level: {stats.level}")
print(f"Score: {stats.score}")
print(f"Tasks completed today: {stats.today_completed}")
```
### Error Handling
```python
from ticktick_sdk import (
TickTickClient,
TickTickError,
TickTickNotFoundError,
TickTickAuthenticationError,
TickTickRateLimitError,
TickTickValidationError,
)
async with TickTickClient.from_settings() as client:
try:
task = await client.get_task("nonexistent-id")
except TickTickNotFoundError as e:
print(f"Task not found: {e}")
except TickTickAuthenticationError:
print("Authentication failed - check credentials")
except TickTickRateLimitError:
print("Rate limited - wait and retry")
except TickTickValidationError as e:
print(f"Invalid input: {e}")
except TickTickError as e:
print(f"TickTick error: {e}")
```
---
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Your Application │
│ (or MCP Server for AI Assistants) │
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ TickTickClient │
│ High-level, user-friendly async API │
│ (tasks, projects, tags, habits, focus, user methods) │
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ UnifiedTickTickAPI │
│ Routes calls to V1 or V2, converts responses │
│ to unified Pydantic models │
└─────────────────────────┬───────────────────────────────────┘
│
┌──────────────┴──────────────┐
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ V1 API │ │ V2 API │
│ (OAuth2) │ │ (Session) │
│ │ │ │
│ • Official API │ │ • Unofficial API │
│ • Project with tasks │ │ • Tags, folders │
│ • Limited features │ │ • Habits, focus │
│ │ │ • Full subtasks │
└──────────────────────┘ └──────────────────────┘
```
### Key Design Decisions
1. **V2-First**: Most operations use V2 API (more features), falling back to V1 only when needed
2. **Unified Models**: Single set of Pydantic models regardless of which API provides the data
3. **Async Throughout**: All I/O operations are async for performance
4. **Type Safety**: Full type hints and Pydantic validation
---
## API Reference
### Models
| Model | Description |
|-------|-------------|
| `Task` | Task with title, dates, priority, tags, subtasks, recurrence, etc. |
| `Project` | Project/list container for tasks |
| `ProjectGroup` | Folder for organizing projects |
| `ProjectData` | Project with its tasks (from get_project_tasks) |
| `Column` | Kanban column for organizing tasks in boards |
| `Tag` | Tag with name, label, color, and optional parent |
| `Habit` | Recurring habit with type, goals, streaks, and check-ins |
| `HabitSection` | Time-of-day grouping (morning/afternoon/night) |
| `HabitCheckin` | Individual habit check-in record |
| `HabitPreferences` | User habit settings |
| `User` | User profile information |
| `UserStatus` | Account status (Pro, inbox ID, etc.) |
| `UserStatistics` | Productivity statistics (level, score, counts) |
| `ChecklistItem` | Subtask/checklist item within a task |
### Enums
| Enum | Values |
|------|--------|
| `TaskStatus` | `ABANDONED (-1)`, `ACTIVE (0)`, `COMPLETED (2)` |
| `TaskPriority` | `NONE (0)`, `LOW (1)`, `MEDIUM (3)`, `HIGH (5)` |
| `TaskKind` | `TEXT`, `NOTE`, `CHECKLIST` |
| `ProjectKind` | `TASK`, `NOTE` |
| `ViewMode` | `LIST`, `KANBAN`, `TIMELINE` |
### Exceptions
| Exception | Description |
|-----------|-------------|
| `TickTickError` | Base exception for all errors |
| `TickTickAuthenticationError` | Authentication failed |
| `TickTickNotFoundError` | Resource not found |
| `TickTickValidationError` | Invalid input data |
| `TickTickRateLimitError` | Rate limit exceeded |
| `TickTickConfigurationError` | Missing configuration |
| `TickTickForbiddenError` | Access denied |
| `TickTickServerError` | Server-side error |
---
## Important: TickTick API Quirks
TickTick's API has several unique behaviors you should know about:
### 1. Recurrence Requires start_date
**If you create a recurring task without a start_date, TickTick silently ignores the recurrence rule.**
```python
# WRONG - recurrence will be ignored!
task = await client.create_task(
title="Daily standup",
recurrence="RRULE:FREQ=DAILY",
)
# CORRECT
task = await client.create_task(
title="Daily standup",
start_date=datetime(2025, 1, 20, 9, 0),
recurrence="RRULE:FREQ=DAILY",
)
```
### 2. Subtasks Require Separate Call
Setting `parent_id` during task creation is **ignored** by the API:
```python
# Create the child task first
child = await client.create_task(title="Subtask")
# Then make it a subtask
await client.make_subtask(
task_id=child.id,
parent_id="parent_task_id",
project_id=child.project_id,
)
```
### 3. Soft Delete
Deleting tasks moves them to trash (`deleted=1`) rather than permanently removing them.
### 4. Date Clearing
To clear a task's `due_date`, you must also clear `start_date`:
```python
task.due_date = None
task.start_date = None
await client.update_task(task)
```
### 5. Tag Order Not Preserved
The API does not preserve tag order - tags may be returned in any order.
### 6. Inbox is Special
The inbox is a special project that cannot be deleted. Get its ID via `await client.get_status()`.
---
## Environment Variables
| Variable | Required | Description |
|----------|:--------:|-------------|
| `TICKTICK_CLIENT_ID` | Yes | OAuth2 client ID from developer portal |
| `TICKTICK_CLIENT_SECRET` | Yes | OAuth2 client secret |
| `TICKTICK_ACCESS_TOKEN` | Yes | OAuth2 access token (from auth command) |
| `TICKTICK_USERNAME` | Yes | Your TickTick email |
| `TICKTICK_PASSWORD` | Yes | Your TickTick password |
| `TICKTICK_REDIRECT_URI` | No | OAuth2 redirect URI (default: `http://127.0.0.1:8080/callback`) |
| `TICKTICK_HOST` | No | API host: `ticktick.com` (default) or `dida365.com` (Chinese) |
| `TICKTICK_TIMEOUT` | No | Request timeout in seconds (default: `30`) |
| `TICKTICK_DEVICE_ID` | No | Device ID for V2 API (auto-generated) |
---
## Running Tests
```bash
# Install dev dependencies
pip install -e ".[dev]"
# All tests (mock mode - no API calls)
pytest
# With verbose output
pytest -v
# Live tests (requires credentials)
pytest --live
# With coverage
pytest --cov=ticktick_sdk --cov-report=term-missing
```
### Test Markers
| Marker | Description |
|--------|-------------|
| `unit` | Unit tests (fast, isolated) |
| `tasks` | Task-related tests |
| `projects` | Project-related tests |
| `tags` | Tag-related tests |
| `habits` | Habit-related tests |
| `focus` | Focus/Pomodoro tests |
| `pinning` | Task pinning tests |
| `columns` | Kanban column tests |
| `mock_only` | Tests that only work with mocks |
| `live_only` | Tests that only run with `--live` |
---
## Troubleshooting
### "Token exchange failed"
- Verify your Client ID and Client Secret are correct
- Ensure the Redirect URI matches exactly (including trailing slashes)
- Check that you're using the correct TickTick developer portal
### "Authentication failed"
- Check your TickTick username (email) and password
- Try logging into ticktick.com to verify credentials
### "V2 initialization failed"
- Your password may contain special characters - try changing it
- Check for 2FA/MFA (not currently supported)
### "Rate limit exceeded"
- Wait 30-60 seconds before retrying
- Reduce the frequency of API calls
---
## Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Write tests for new functionality
4. Ensure all tests pass (`pytest`)
5. Run type checking (`mypy src/`)
6. Submit a pull request
### Development Setup
```bash
git clone https://github.com/dev-mirzabicer/ticktick-sdk.git
cd ticktick-sdk
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```
---
## License
MIT License - see [LICENSE](LICENSE) for details.
---
## Acknowledgments
- [TickTick](https://ticktick.com) for the excellent task management app
- [Model Context Protocol](https://modelcontextprotocol.io/) for the AI integration standard
- [FastMCP](https://github.com/jlowin/fastmcp) for the MCP framework
- [Pydantic](https://docs.pydantic.dev/) for data validation
- [httpx](https://www.python-httpx.org/) for async HTTP
Connection Info
You Might Also Like
markitdown
Python tool for converting files and office documents to Markdown.
everything-claude-code
Complete Claude Code configuration collection - agents, skills, hooks,...
awesome-claude-skills
A curated list of awesome Claude Skills, resources, and tools for...
antigravity-awesome-skills
The Ultimate Collection of 130+ Agentic Skills for Claude...
memU
MemU is a memory framework for LLM and AI agents, organizing multimodal...
openfang
Open-source Agent Operating System