A comprehensive async Python SDK for TickTick with MCP (Model Context Protocol) server support. Includes full support for Dida365 (滴答清单) as well. Use TickTick programmatically from Python, or let AI assistants manage your tasks. - Why This Library? - Installation - MCP Server Setup & Usage - Step 1: Register Your App - Step 2: Get OAuth2 Access Token - Step 3: Configure Your AI Assistant - CLI Ref
Add this skill
npx mdskills install dev-mirzabicer/ticktick-sdkComprehensive MCP server for TickTick with 43 tools, batch operations, and excellent documentation
1# ticktick-sdk: A TickTick MCP Server & Full Python SDK234[](https://www.python.org/downloads/)5[](https://opensource.org/licenses/MIT)6[](https://pepy.tech/projects/ticktick-sdk)78A comprehensive async Python SDK for [TickTick](https://ticktick.com) with [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) server support.910Includes full support for [Dida365 (滴答清单)](https://dida365.com) as well.1112**Use TickTick programmatically from Python, or let AI assistants manage your tasks.**1314## Table of Contents1516- [Features](#features)17- [Why This Library?](#why-this-library)18- [Installation](#installation)19- [MCP Server Setup & Usage](#mcp-server-setup--usage)20 - [Step 1: Register Your App](#step-1-register-your-app)21 - [Step 2: Get OAuth2 Access Token](#step-2-get-oauth2-access-token)22 - [Step 3: Configure Your AI Assistant](#step-3-configure-your-ai-assistant)23 - [CLI Reference](#cli-reference)24 - [Example Conversations](#example-conversations)25 - [Available MCP Tools](#available-mcp-tools-43-total)26- [Python Library Setup & Usage](#python-library-setup--usage)27 - [Setup](#setup)28 - [Quick Start](#quick-start)29 - [Tasks](#tasks)30 - [Projects & Folders](#projects--folders)31 - [Tags](#tags)32 - [Habits](#habits)33 - [Focus/Pomodoro](#focuspomodoro)34 - [User & Statistics](#user--statistics)35 - [Error Handling](#error-handling)36- [Architecture](#architecture)37- [API Reference](#api-reference)38- [TickTick API Quirks](#important-ticktick-api-quirks)39- [Environment Variables](#environment-variables)40- [Running Tests](#running-tests)41- [Troubleshooting](#troubleshooting)42- [Contributing](#contributing)4344---4546## Features4748### MCP Server49- **43 Tools**: Streamlined coverage of TickTick functionality50- **Batch Operations**: All mutations accept lists (1-100 items) for bulk operations51- **AI-Ready**: Works with Claude, GPT, and other MCP-compatible assistants52- **Dual Output**: Markdown for humans, JSON for machines5354### Python Library55- **Full Async Support**: Built on `httpx` for high-performance async operations56- **Batch Operations**: Create, update, delete, complete up to 100 tasks in a single call57- **Complete Task Management**: Create, read, update, delete, complete, move, pin tasks58- **Kanban Boards**: Full column management (create, update, delete, move tasks between columns)59- **Project Organization**: Projects, folders, kanban boards60- **Tag System**: Hierarchical tags with colors61- **Habit Tracking**: Full CRUD for habits with batch check-ins, streaks, and goals62- **Focus/Pomodoro**: Access focus session data and statistics63- **User Analytics**: Productivity scores, levels, completion rates6465### Developer Experience66- **Type-Safe**: Full Pydantic v2 validation with comprehensive type hints67- **Well-Tested**: 300+ tests covering both mock and live API interactions68- **Documented**: Extensive docstrings and examples6970---7172## Why This Library?7374### The Two-API Problem7576TickTick has **two different APIs**:7778| API | Type | What We Use It For |79|-----|------|-------------------|80| **V1 (OAuth2)** | Official, documented | Project with all tasks, basic operations |81| **V2 (Session)** | Unofficial, reverse-engineered | Tags, folders, habits, focus, subtasks, and more |8283The 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.8485### Compared to Other Libraries8687Based on analysis of the actual source code of available TickTick Python libraries:8889| 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) |90|---------|:------------:|:----------:|:-----------:|:--------:|:---------------:|91| **I/O Model** | Async | Async | Sync | Sync | Sync |92| **Type System** | Pydantic V2 | Pydantic V2 | Dicts | attrs | addict |93| **MCP Server** | **Yes** | No | No | No | No |94| **Habits** | **Full CRUD** | No | Basic | Basic | No |95| **Focus/Pomo** | Yes | Yes | Yes | Yes | No |96| **Unified V1+V2** | **Smart Routing** | Separate | Both | V2 only | V2 only |97| **Subtasks** | Advanced | Batch | Yes | Basic | Basic |98| **Tags** | Full (merge/rename) | Yes | Yes | Yes | No |99100**Key Differentiators:**101102- **MCP Server**: Only ticktick-sdk provides AI assistant integration via Model Context Protocol103- **Unified API Routing**: Automatically routes operations to V1 or V2 based on feature requirements104- **Full Habit CRUD**: Complete habit management including check-ins, streaks, archive/unarchive105- **Async-First**: Built on `httpx` for high-performance async operations106107---108109## Installation110111```bash112pip install ticktick-sdk113```114115**Requirements:**116- Python 3.11+117- TickTick account (free or Pro)118119---120121## MCP Server Setup & Usage122123Use TickTick with AI assistants like Claude through the Model Context Protocol.124125### Step 1: Register Your App1261271. Go to the [TickTick Developer Portal](https://developer.ticktick.com/manage)1282. Click **"Create App"**1293. Fill in:130 - **App Name**: e.g., "My TickTick MCP"131 - **Redirect URI**: `http://127.0.0.1:8080/callback`1324. Save your **Client ID** and **Client Secret**133134### Step 2: Get OAuth2 Access Token135136Run the auth command with your credentials:137138```bash139TICKTICK_CLIENT_ID=your_client_id \140TICKTICK_CLIENT_SECRET=your_client_secret \141ticktick-sdk auth142```143144This will:1451. **Open your browser** to TickTick's authorization page1462. **Authorize the app** - Click "Authorize" to grant access1473. **Return to terminal** - After authorizing, you'll see output like this:148149```150============================================================151 SUCCESS! Here is your access token:152============================================================153154a]234abc-5678-90de-f012-34567890abcd155156============================================================157158NEXT STEPS:159160For Claude Code users:161 Run (replace YOUR_* placeholders):162 claude mcp add ticktick \163 -e TICKTICK_CLIENT_ID=YOUR_CLIENT_ID \164 ...165```1661674. **Copy this token** - You'll need it in the next step168169> **Note**: Sometimes the browser shows an "invalid credentials" error page. Just refresh the page and it should work.170171> **SSH/Headless Users**: Add `--manual` flag for a text-based flow that doesn't require a browser.172173### Step 3: Configure Your AI Assistant174175#### Claude Code (Recommended)176177```bash178claude mcp add ticktick \179 -e TICKTICK_CLIENT_ID=your_client_id \180 -e TICKTICK_CLIENT_SECRET=your_client_secret \181 -e TICKTICK_ACCESS_TOKEN=your_access_token \182 -e TICKTICK_USERNAME=your_email \183 -e TICKTICK_PASSWORD=your_password \184 -- ticktick-sdk185```186187> **Note**: For `TICKTICK_ACCESS_TOKEN`, paste the token you copied from Step 2.188189Verify it's working:190191```bash192claude mcp list # See all configured servers193/mcp # Within Claude Code, check server status194```195196#### Claude Desktop197198Add to your Claude Desktop config:199200**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`201**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`202203```json204{205 "mcpServers": {206 "ticktick": {207 "command": "ticktick-sdk",208 "env": {209 "TICKTICK_CLIENT_ID": "your_client_id",210 "TICKTICK_CLIENT_SECRET": "your_client_secret",211 "TICKTICK_ACCESS_TOKEN": "your_access_token",212 "TICKTICK_USERNAME": "your_email",213 "TICKTICK_PASSWORD": "your_password"214 }215 }216 }217}218```219220#### Other MCP-Compatible Tools221222This 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.223224### CLI Reference225226The `ticktick-sdk` command provides several subcommands:227228| Command | Description |229|---------|-------------|230| `ticktick-sdk` | Start the MCP server (default) |231| `ticktick-sdk server` | Start the MCP server (explicit) |232| `ticktick-sdk server --host HOST` | Use specific API host (`ticktick.com` or `dida365.com`) |233| `ticktick-sdk server --enabledModules MODULES` | Enable only specific tool modules (comma-separated) |234| `ticktick-sdk server --enabledTools TOOLS` | Enable only specific tools (comma-separated) |235| `ticktick-sdk auth` | Get OAuth2 access token (opens browser) |236| `ticktick-sdk auth --manual` | Get OAuth2 access token (SSH-friendly) |237| `ticktick-sdk --version` | Show version information |238| `ticktick-sdk --help` | Show help message |239240**Tool Filtering** (reduces context window usage for AI assistants):241242```bash243# Enable only task and project tools244ticktick-sdk server --enabledModules tasks,projects245246# Enable specific tools only247ticktick-sdk server --enabledTools ticktick_create_tasks,ticktick_list_tasks248249# Available modules: tasks, projects, folders, columns, tags, habits, user, focus250```251252### Example Conversations253254Once configured, you can ask Claude things like:255256- "What tasks do I have due today?"257- "Create a task to call John tomorrow at 2pm"258- "Show me my high priority tasks"259- "Mark the grocery shopping task as complete"260- "What's my current streak for the Exercise habit?"261- "Check in my meditation habit for today"262- "Create a new habit to drink 8 glasses of water daily"263264### Available MCP Tools (43 Total)265266All mutation tools accept lists for batch operations (1-100 items).267268#### Task Tools (Batch-Capable)269| Tool | Description |270|------|-------------|271| `ticktick_create_tasks` | Create 1-50 tasks with titles, dates, tags, etc. |272| `ticktick_get_task` | Get task details by ID |273| `ticktick_list_tasks` | List tasks (active/completed/abandoned/deleted via status filter) |274| `ticktick_update_tasks` | Update 1-100 tasks (includes column assignment) |275| `ticktick_complete_tasks` | Complete 1-100 tasks |276| `ticktick_delete_tasks` | Delete 1-100 tasks (moves to trash) |277| `ticktick_move_tasks` | Move 1-50 tasks between projects |278| `ticktick_set_task_parents` | Set parent-child relationships for 1-50 tasks |279| `ticktick_unparent_tasks` | Remove parent relationships from 1-50 tasks |280| `ticktick_search_tasks` | Search tasks by text |281| `ticktick_pin_tasks` | Pin or unpin 1-100 tasks |282283#### Project Tools284| Tool | Description |285|------|-------------|286| `ticktick_list_projects` | List all projects |287| `ticktick_get_project` | Get project details with tasks |288| `ticktick_create_project` | Create a new project |289| `ticktick_update_project` | Update project properties |290| `ticktick_delete_project` | Delete a project |291292#### Folder Tools293| Tool | Description |294|------|-------------|295| `ticktick_list_folders` | List all folders |296| `ticktick_create_folder` | Create a folder |297| `ticktick_rename_folder` | Rename a folder |298| `ticktick_delete_folder` | Delete a folder |299300#### Kanban Column Tools301| Tool | Description |302|------|-------------|303| `ticktick_list_columns` | List columns for a kanban project |304| `ticktick_create_column` | Create a kanban column |305| `ticktick_update_column` | Update column name or order |306| `ticktick_delete_column` | Delete a kanban column |307308#### Tag Tools309| Tool | Description |310|------|-------------|311| `ticktick_list_tags` | List all tags |312| `ticktick_create_tag` | Create a tag with color |313| `ticktick_update_tag` | Update tag properties (includes rename via label) |314| `ticktick_delete_tag` | Delete a tag |315| `ticktick_merge_tags` | Merge two tags |316317#### Habit Tools (Batch-Capable)318| Tool | Description |319|------|-------------|320| `ticktick_habits` | List all habits |321| `ticktick_habit` | Get habit details |322| `ticktick_habit_sections` | List sections (morning/afternoon/night) |323| `ticktick_create_habit` | Create a new habit |324| `ticktick_update_habit` | Update habit properties (includes archive/unarchive) |325| `ticktick_delete_habit` | Delete a habit |326| `ticktick_checkin_habits` | Check in 1-50 habits (supports backdating) |327| `ticktick_habit_checkins` | Get check-in history |328329#### User & Analytics Tools330| Tool | Description |331|------|-------------|332| `ticktick_get_profile` | Get user profile |333| `ticktick_get_status` | Get account status |334| `ticktick_get_statistics` | Get productivity stats |335| `ticktick_get_preferences` | Get user preferences |336| `ticktick_focus_heatmap` | Get focus heatmap data |337| `ticktick_focus_by_tag` | Get focus time by tag |338339---340341## Python Library Setup & Usage342343Use TickTick programmatically in your Python applications.344345### Setup346347#### Step 1: Register Your App348349Same as MCP setup - go to the [TickTick Developer Portal](https://developer.ticktick.com/manage) and create an app.350351#### Step 2: Create Your .env File352353Create a `.env` file in your project directory:354355```bash356# V1 API (OAuth2)357TICKTICK_CLIENT_ID=your_client_id_here358TICKTICK_CLIENT_SECRET=your_client_secret_here359TICKTICK_REDIRECT_URI=http://127.0.0.1:8080/callback360TICKTICK_ACCESS_TOKEN= # Will be filled in Step 3361362# V2 API (Session)363TICKTICK_USERNAME=your_ticktick_email@example.com364TICKTICK_PASSWORD=your_ticktick_password365366# Optional367TICKTICK_TIMEOUT=30368```369370#### Step 3: Get OAuth2 Access Token371372```bash373# Source your .env file first, or export the variables374ticktick-sdk auth375```376377Copy the access token to your `.env` file.378379#### Step 4: Verify Setup380381```python382import asyncio383from ticktick_sdk import TickTickClient384385async def test():386 async with TickTickClient.from_settings() as client:387 profile = await client.get_profile()388 print(f'Connected as: {profile.display_name}')389390asyncio.run(test())391```392393### Quick Start394395```python396import asyncio397from ticktick_sdk import TickTickClient398399async def main():400 async with TickTickClient.from_settings() as client:401 # Create a task402 task = await client.create_task(403 title="Learn ticktick-sdk",404 tags=["python", "productivity"],405 )406 print(f"Created: {task.title} (ID: {task.id})")407408 # List all tasks409 tasks = await client.get_all_tasks()410 print(f"You have {len(tasks)} active tasks")411412 # Complete the task413 await client.complete_task(task.id, task.project_id)414 print("Task completed!")415416asyncio.run(main())417```418419### Tasks420421#### Creating Tasks422423```python424from datetime import datetime, timedelta425from ticktick_sdk import TickTickClient426427async with TickTickClient.from_settings() as client:428 # Simple task429 task = await client.create_task(title="Buy groceries")430431 # Task with due date and priority432 task = await client.create_task(433 title="Submit report",434 due_date=datetime.now() + timedelta(days=1),435 priority="high", # none, low, medium, high436 )437438 # Task with tags and content439 task = await client.create_task(440 title="Review PR #123",441 content="Check for:\n- Code style\n- Tests\n- Documentation",442 tags=["work", "code-review"],443 )444445 # Recurring task (MUST include start_date!)446 task = await client.create_task(447 title="Daily standup",448 start_date=datetime(2025, 1, 20, 9, 0),449 recurrence="RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR",450 )451452 # Task with reminder453 task = await client.create_task(454 title="Meeting with team",455 due_date=datetime(2025, 1, 20, 14, 0),456 reminders=["TRIGGER:-PT15M"], # 15 minutes before457 )458459 # All-day task460 task = await client.create_task(461 title="Project deadline",462 due_date=datetime(2025, 1, 31),463 all_day=True,464 )465```466467#### Managing Tasks468469```python470async with TickTickClient.from_settings() as client:471 # Get a specific task472 task = await client.get_task(task_id="...")473474 # Update a task475 task.title = "Updated title"476 task.priority = 5 # high priority477 await client.update_task(task)478479 # Complete a task480 await client.complete_task(task_id="...", project_id="...")481482 # Delete a task (moves to trash)483 await client.delete_task(task_id="...", project_id="...")484485 # Move task to another project486 await client.move_task(487 task_id="...",488 from_project_id="...",489 to_project_id="...",490 )491```492493#### Subtasks494495```python496async with TickTickClient.from_settings() as client:497 # Create parent task498 parent = await client.create_task(title="Main task")499500 # Create child task501 child = await client.create_task(title="Subtask")502503 # Make it a subtask (parent_id in create is ignored by API)504 await client.make_subtask(505 task_id=child.id,506 parent_id=parent.id,507 project_id=child.project_id,508 )509510 # Remove parent relationship511 await client.unparent_subtask(512 task_id=child.id,513 project_id=child.project_id,514 )515```516517#### Querying Tasks518519```python520async with TickTickClient.from_settings() as client:521 # All active tasks522 all_tasks = await client.get_all_tasks()523524 # Tasks due today525 today = await client.get_today_tasks()526527 # Overdue tasks528 overdue = await client.get_overdue_tasks()529530 # Tasks by tag531 work_tasks = await client.get_tasks_by_tag("work")532533 # Tasks by priority534 urgent = await client.get_tasks_by_priority("high")535536 # Search tasks537 results = await client.search_tasks("meeting")538539 # Recently completed540 completed = await client.get_completed_tasks(days=7, limit=50)541542 # Abandoned tasks ("won't do")543 abandoned = await client.get_abandoned_tasks(days=30)544545 # Deleted tasks (in trash)546 deleted = await client.get_deleted_tasks(limit=50)547```548549### Projects & Folders550551#### Projects552553```python554async with TickTickClient.from_settings() as client:555 # List all projects556 projects = await client.get_all_projects()557 for project in projects:558 print(f"{project.name} ({project.id})")559560 # Get project with all its tasks561 project_data = await client.get_project_tasks(project_id="...")562 print(f"Project: {project_data.project.name}")563 print(f"Tasks: {len(project_data.tasks)}")564565 # Create a project566 project = await client.create_project(567 name="Q1 Goals",568 color="#4A90D9",569 view_mode="kanban", # list, kanban, timeline570 )571572 # Update a project573 await client.update_project(574 project_id=project.id,575 name="Q1 Goals 2025",576 color="#FF5500",577 )578579 # Delete a project580 await client.delete_project(project_id="...")581```582583#### Folders (Project Groups)584585```python586async with TickTickClient.from_settings() as client:587 # List all folders588 folders = await client.get_all_folders()589590 # Create a folder591 folder = await client.create_folder(name="Work Projects")592593 # Create project in folder594 project = await client.create_project(595 name="Client A",596 folder_id=folder.id,597 )598599 # Rename a folder600 await client.rename_folder(folder_id=folder.id, name="Work")601602 # Delete a folder603 await client.delete_folder(folder_id="...")604```605606### Tags607608Tags in TickTick support hierarchy (parent-child relationships) and custom colors.609610```python611async with TickTickClient.from_settings() as client:612 # List all tags613 tags = await client.get_all_tags()614 for tag in tags:615 print(f"{tag.label} ({tag.name}) - {tag.color}")616617 # Create a tag618 tag = await client.create_tag(619 name="urgent",620 color="#FF0000",621 )622623 # Create nested tag624 child_tag = await client.create_tag(625 name="critical",626 parent="urgent", # Parent tag name627 )628629 # Rename a tag630 await client.rename_tag(old_name="urgent", new_name="priority")631632 # Update tag color or parent633 await client.update_tag(634 name="priority",635 color="#FF5500",636 )637638 # Merge tags (move all tasks from source to target)639 await client.merge_tags(source="old-tag", target="new-tag")640641 # Delete a tag642 await client.delete_tag(name="obsolete")643```644645### Habits646647TickTick habits are recurring activities you want to track daily.648649#### Habit Types650651| Type | Description | Example |652|------|-------------|---------|653| `Boolean` | Simple yes/no | "Did you exercise today?" |654| `Real` | Numeric counter | "How many pages did you read?" |655656#### Creating and Managing Habits657658```python659async with TickTickClient.from_settings() as client:660 # List all habits661 habits = await client.get_all_habits()662663 # Boolean habit (yes/no)664 exercise = await client.create_habit(665 name="Exercise",666 color="#4A90D9",667 reminders=["07:00", "19:00"],668 target_days=30,669 encouragement="Stay strong!",670 )671672 # Numeric habit673 reading = await client.create_habit(674 name="Read",675 habit_type="Real",676 goal=30, # 30 pages per day677 step=5, # +5 button increment678 unit="Pages",679 )680681 # Check in a habit (today)682 habit = await client.checkin_habit("habit_id")683 print(f"Streak: {habit.current_streak} days!")684685 # Check in for a past date (backdate)686 from datetime import date687 habit = await client.checkin_habit("habit_id", checkin_date=date(2025, 12, 15))688689 # Archive/unarchive690 await client.archive_habit("habit_id")691 await client.unarchive_habit("habit_id")692```693694#### Habit Repeat Rules (RRULE Format)695696| Schedule | RRULE |697|----------|-------|698| Daily (every day) | `RRULE:FREQ=WEEKLY;BYDAY=SU,MO,TU,WE,TH,FR,SA` |699| Weekdays only | `RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR` |700| Weekends only | `RRULE:FREQ=WEEKLY;BYDAY=SA,SU` |701| X times per week | `RRULE:FREQ=WEEKLY;TT_TIMES=5` |702| Specific days | `RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR` |703704### Focus/Pomodoro705706```python707from datetime import date, timedelta708709async with TickTickClient.from_settings() as client:710 # Focus heatmap (like GitHub contribution graph)711 heatmap = await client.get_focus_heatmap(712 start_date=date.today() - timedelta(days=90),713 end_date=date.today(),714 )715716 # Focus time by tag717 by_tag = await client.get_focus_by_tag(days=30)718 for tag, seconds in sorted(by_tag.items(), key=lambda x: -x[1]):719 hours = seconds / 3600720 print(f" {tag}: {hours:.1f} hours")721```722723### User & Statistics724725```python726async with TickTickClient.from_settings() as client:727 # User profile728 profile = await client.get_profile()729 print(f"Username: {profile.username}")730731 # Account status732 status = await client.get_status()733 print(f"Pro User: {status.is_pro}")734 print(f"Inbox ID: {status.inbox_id}")735736 # Productivity statistics737 stats = await client.get_statistics()738 print(f"Level: {stats.level}")739 print(f"Score: {stats.score}")740 print(f"Tasks completed today: {stats.today_completed}")741```742743### Error Handling744745```python746from ticktick_sdk import (747 TickTickClient,748 TickTickError,749 TickTickNotFoundError,750 TickTickAuthenticationError,751 TickTickRateLimitError,752 TickTickValidationError,753)754755async with TickTickClient.from_settings() as client:756 try:757 task = await client.get_task("nonexistent-id")758 except TickTickNotFoundError as e:759 print(f"Task not found: {e}")760 except TickTickAuthenticationError:761 print("Authentication failed - check credentials")762 except TickTickRateLimitError:763 print("Rate limited - wait and retry")764 except TickTickValidationError as e:765 print(f"Invalid input: {e}")766 except TickTickError as e:767 print(f"TickTick error: {e}")768```769770---771772## Architecture773774```775┌─────────────────────────────────────────────────────────────┐776│ Your Application │777│ (or MCP Server for AI Assistants) │778└─────────────────────────┬───────────────────────────────────┘779 │780┌─────────────────────────▼───────────────────────────────────┐781│ TickTickClient │782│ High-level, user-friendly async API │783│ (tasks, projects, tags, habits, focus, user methods) │784└─────────────────────────┬───────────────────────────────────┘785 │786┌─────────────────────────▼───────────────────────────────────┐787│ UnifiedTickTickAPI │788│ Routes calls to V1 or V2, converts responses │789│ to unified Pydantic models │790└─────────────────────────┬───────────────────────────────────┘791 │792 ┌──────────────┴──────────────┐793 ▼ ▼794┌──────────────────────┐ ┌──────────────────────┐795│ V1 API │ │ V2 API │796│ (OAuth2) │ │ (Session) │797│ │ │ │798│ • Official API │ │ • Unofficial API │799│ • Project with tasks │ │ • Tags, folders │800│ • Limited features │ │ • Habits, focus │801│ │ │ • Full subtasks │802└──────────────────────┘ └──────────────────────┘803```804805### Key Design Decisions8068071. **V2-First**: Most operations use V2 API (more features), falling back to V1 only when needed8082. **Unified Models**: Single set of Pydantic models regardless of which API provides the data8093. **Async Throughout**: All I/O operations are async for performance8104. **Type Safety**: Full type hints and Pydantic validation811812---813814## API Reference815816### Models817818| Model | Description |819|-------|-------------|820| `Task` | Task with title, dates, priority, tags, subtasks, recurrence, etc. |821| `Project` | Project/list container for tasks |822| `ProjectGroup` | Folder for organizing projects |823| `ProjectData` | Project with its tasks (from get_project_tasks) |824| `Column` | Kanban column for organizing tasks in boards |825| `Tag` | Tag with name, label, color, and optional parent |826| `Habit` | Recurring habit with type, goals, streaks, and check-ins |827| `HabitSection` | Time-of-day grouping (morning/afternoon/night) |828| `HabitCheckin` | Individual habit check-in record |829| `HabitPreferences` | User habit settings |830| `User` | User profile information |831| `UserStatus` | Account status (Pro, inbox ID, etc.) |832| `UserStatistics` | Productivity statistics (level, score, counts) |833| `ChecklistItem` | Subtask/checklist item within a task |834835### Enums836837| Enum | Values |838|------|--------|839| `TaskStatus` | `ABANDONED (-1)`, `ACTIVE (0)`, `COMPLETED (2)` |840| `TaskPriority` | `NONE (0)`, `LOW (1)`, `MEDIUM (3)`, `HIGH (5)` |841| `TaskKind` | `TEXT`, `NOTE`, `CHECKLIST` |842| `ProjectKind` | `TASK`, `NOTE` |843| `ViewMode` | `LIST`, `KANBAN`, `TIMELINE` |844845### Exceptions846847| Exception | Description |848|-----------|-------------|849| `TickTickError` | Base exception for all errors |850| `TickTickAuthenticationError` | Authentication failed |851| `TickTickNotFoundError` | Resource not found |852| `TickTickValidationError` | Invalid input data |853| `TickTickRateLimitError` | Rate limit exceeded |854| `TickTickConfigurationError` | Missing configuration |855| `TickTickForbiddenError` | Access denied |856| `TickTickServerError` | Server-side error |857858---859860## Important: TickTick API Quirks861862TickTick's API has several unique behaviors you should know about:863864### 1. Recurrence Requires start_date865866**If you create a recurring task without a start_date, TickTick silently ignores the recurrence rule.**867868```python869# WRONG - recurrence will be ignored!870task = await client.create_task(871 title="Daily standup",872 recurrence="RRULE:FREQ=DAILY",873)874875# CORRECT876task = await client.create_task(877 title="Daily standup",878 start_date=datetime(2025, 1, 20, 9, 0),879 recurrence="RRULE:FREQ=DAILY",880)881```882883### 2. Subtasks Require Separate Call884885Setting `parent_id` during task creation is **ignored** by the API:886887```python888# Create the child task first889child = await client.create_task(title="Subtask")890891# Then make it a subtask892await client.make_subtask(893 task_id=child.id,894 parent_id="parent_task_id",895 project_id=child.project_id,896)897```898899### 3. Soft Delete900901Deleting tasks moves them to trash (`deleted=1`) rather than permanently removing them.902903### 4. Date Clearing904905To clear a task's `due_date`, you must also clear `start_date`:906907```python908task.due_date = None909task.start_date = None910await client.update_task(task)911```912913### 5. Tag Order Not Preserved914915The API does not preserve tag order - tags may be returned in any order.916917### 6. Inbox is Special918919The inbox is a special project that cannot be deleted. Get its ID via `await client.get_status()`.920921---922923## Environment Variables924925| Variable | Required | Description |926|----------|:--------:|-------------|927| `TICKTICK_CLIENT_ID` | Yes | OAuth2 client ID from developer portal |928| `TICKTICK_CLIENT_SECRET` | Yes | OAuth2 client secret |929| `TICKTICK_ACCESS_TOKEN` | Yes | OAuth2 access token (from auth command) |930| `TICKTICK_USERNAME` | Yes | Your TickTick email |931| `TICKTICK_PASSWORD` | Yes | Your TickTick password |932| `TICKTICK_REDIRECT_URI` | No | OAuth2 redirect URI (default: `http://127.0.0.1:8080/callback`) |933| `TICKTICK_HOST` | No | API host: `ticktick.com` (default) or `dida365.com` (Chinese) |934| `TICKTICK_TIMEOUT` | No | Request timeout in seconds (default: `30`) |935| `TICKTICK_DEVICE_ID` | No | Device ID for V2 API (auto-generated) |936937---938939## Running Tests940941```bash942# Install dev dependencies943pip install -e ".[dev]"944945# All tests (mock mode - no API calls)946pytest947948# With verbose output949pytest -v950951# Live tests (requires credentials)952pytest --live953954# With coverage955pytest --cov=ticktick_sdk --cov-report=term-missing956```957958### Test Markers959960| Marker | Description |961|--------|-------------|962| `unit` | Unit tests (fast, isolated) |963| `tasks` | Task-related tests |964| `projects` | Project-related tests |965| `tags` | Tag-related tests |966| `habits` | Habit-related tests |967| `focus` | Focus/Pomodoro tests |968| `pinning` | Task pinning tests |969| `columns` | Kanban column tests |970| `mock_only` | Tests that only work with mocks |971| `live_only` | Tests that only run with `--live` |972973---974975## Troubleshooting976977### "Token exchange failed"978- Verify your Client ID and Client Secret are correct979- Ensure the Redirect URI matches exactly (including trailing slashes)980- Check that you're using the correct TickTick developer portal981982### "Authentication failed"983- Check your TickTick username (email) and password984- Try logging into ticktick.com to verify credentials985986### "V2 initialization failed"987- Your password may contain special characters - try changing it988- Check for 2FA/MFA (not currently supported)989990### "Rate limit exceeded"991- Wait 30-60 seconds before retrying992- Reduce the frequency of API calls993994---995996## Contributing997998Contributions are welcome! Please:99910001. Fork the repository10012. Create a feature branch (`git checkout -b feature/amazing-feature`)10023. Write tests for new functionality10034. Ensure all tests pass (`pytest`)10045. Run type checking (`mypy src/`)10056. Submit a pull request10061007### Development Setup10081009```bash1010git clone https://github.com/dev-mirzabicer/ticktick-sdk.git1011cd ticktick-sdk1012python3 -m venv .venv1013source .venv/bin/activate1014pip install -e ".[dev]"1015```10161017---10181019## License10201021MIT License - see [LICENSE](LICENSE) for details.10221023---10241025## Acknowledgments10261027- [TickTick](https://ticktick.com) for the excellent task management app1028- [Model Context Protocol](https://modelcontextprotocol.io/) for the AI integration standard1029- [FastMCP](https://github.com/jlowin/fastmcp) for the MCP framework1030- [Pydantic](https://docs.pydantic.dev/) for data validation1031- [httpx](https://www.python-httpx.org/) for async HTTP1032
Full transparency — inspect the skill content before installing.