Sponsored by Deepsite.site

Ticktick Mcp

Created By
dev-mirzabicer13 hours ago
A fully-featured MCP server for TickTick
Content

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

PyPI version Python 3.11+ License: MIT

A comprehensive async Python SDK for TickTick with MCP (Model Context Protocol) server support.

Use TickTick programmatically from Python, or let AI assistants manage your tasks.

Table of Contents


Features

Python Library

  • Full Async Support: Built on httpx for high-performance async operations
  • Complete Task Management: Create, read, update, delete, complete, move tasks
  • Project Organization: Projects, folders, kanban boards
  • Tag System: Hierarchical tags with colors
  • Habit Tracking: Full CRUD for habits with check-ins, streaks, and goals
  • Focus/Pomodoro: Access focus session data and statistics
  • User Analytics: Productivity scores, levels, completion rates

MCP Server

  • 45 Tools: Comprehensive coverage of TickTick functionality
  • AI-Ready: Works with Claude, GPT, and other MCP-compatible assistants
  • Dual Output: Markdown for humans, JSON for machines

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:

APITypeWhat We Use It For
V1 (OAuth2)Official, documentedProject with all tasks, basic operations
V2 (Session)Unofficial, reverse-engineeredTags, 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:

Featureticktick-sdkpyticktickticktick-pytickthonticktick-python
I/O ModelAsyncAsyncSyncSyncSync
Type SystemPydantic V2Pydantic V2Dictsattrsaddict
MCP ServerYesNoNoNoNo
HabitsFull CRUDNoBasicBasicNo
Focus/PomoYesYesYesYesNo
Unified V1+V2Smart RoutingSeparateBothV2 onlyV2 only
SubtasksAdvancedBatchYesBasicBasic
TagsFull (merge/rename)YesYesYesNo

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

pip install ticktick-sdk

From Source (Development)

git clone https://github.com/dev-mirzabicer/ticktick-sdk.git
cd ticktick-sdk
python3 -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate
pip install -e ".[dev]"

Requirements

  • Python 3.11+
  • TickTick account (free or Pro)
  • For full functionality: both OAuth2 app registration and account credentials

Authentication Setup

This library requires both V1 and V2 authentication for full functionality.

Step 1: Register Your App (V1 OAuth2)

  1. Go to the TickTick Developer Portal
  2. Click "Create App"
  3. Fill in:
    • App Name: e.g., "My TickTick App"
    • Redirect URI: http://127.0.0.1:8080/callback
  4. Save your Client ID and Client Secret

Step 2: Create Your .env File

cp .env.example .env

Edit .env with your credentials:

# V1 API (OAuth2) - REQUIRED for get_project_tasks
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) - REQUIRED for most operations
TICKTICK_USERNAME=your_ticktick_email@example.com
TICKTICK_PASSWORD=your_ticktick_password

# Optional
TICKTICK_TIMEOUT=30  # Request timeout in seconds

Step 3: Get Your OAuth2 Access Token

Run the included helper script:

# Activate your virtual environment first
source .venv/bin/activate

# Run the OAuth flow
python scripts/get_oauth_token.py

This will:

  1. Open your browser to TickTick's authorization page
  2. Wait for you to authorize the app
  3. Print the access token

Copy the access token to your .env file:

TICKTICK_ACCESS_TOKEN=paste_your_token_here

SSH/Headless Users: Use python scripts/get_oauth_token.py --manual for a text-based flow.

Step 4: Verify Setup

python -c "
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}')

        stats = await client.get_statistics()
        print(f'Level {stats.level} | Score: {stats.score}')

asyncio.run(test())
"

Usage: Python Library

Quick Start

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

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

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

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

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

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)

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.

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. The library provides full CRUD operations for habits, including check-ins, streaks, and archiving.

Habit Types

TypeDescriptionExample
BooleanSimple yes/no"Did you exercise today?"
RealNumeric counter"How many pages did you read?"

Listing and Getting Habits

from ticktick_sdk import TickTickClient, Habit

async with TickTickClient.from_settings() as client:
    # List all habits
    habits = await client.get_all_habits()

    for habit in habits:
        status = "Archived" if habit.is_archived else "Active"
        print(f"{habit.name}")
        print(f"  Type: {habit.habit_type}")
        print(f"  Streak: {habit.current_streak} days")
        print(f"  Total: {habit.total_checkins} check-ins")
        print(f"  Status: {status}")
        print()

    # Get a specific habit
    habit = await client.get_habit("habit_id_here")

Creating Habits

async with TickTickClient.from_settings() as client:
    # Boolean habit (yes/no) - Daily exercise
    exercise = await client.create_habit(
        name="Exercise",
        color="#4A90D9",
        reminders=["07:00", "19:00"],  # HH:MM format
        target_days=30,  # 30-day challenge
        encouragement="Stay strong!",
    )

    # Numeric habit - Reading pages
    reading = await client.create_habit(
        name="Read",
        habit_type="Real",  # Numeric
        goal=30,           # Target: 30 pages per day
        step=5,            # +5 button increment
        unit="Pages",
        color="#97E38B",
    )

    # Habit with custom schedule (weekdays only)
    meditation = await client.create_habit(
        name="Meditate",
        repeat_rule="RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR",
        reminders=["06:00"],
    )

    # Habit with flexible schedule (5 times per week)
    workout = await client.create_habit(
        name="Workout",
        repeat_rule="RRULE:FREQ=WEEKLY;TT_TIMES=5",
    )

Habit Repeat Rules (RRULE Format)

ScheduleRRULE
Daily (every day)RRULE:FREQ=WEEKLY;BYDAY=SU,MO,TU,WE,TH,FR,SA
Weekdays onlyRRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
Weekends onlyRRULE:FREQ=WEEKLY;BYDAY=SA,SU
X times per weekRRULE:FREQ=WEEKLY;TT_TIMES=5
Specific daysRRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR

Checking In Habits

async with TickTickClient.from_settings() as client:
    # Check in a boolean habit (complete for today)
    habit = await client.checkin_habit("habit_id")
    print(f"Streak: {habit.current_streak} days!")

    # Check in a numeric habit with a value
    habit = await client.checkin_habit("reading_habit_id", value=25)
    print(f"Total pages: {habit.total_checkins}")

Updating and Managing Habits

async with TickTickClient.from_settings() as client:
    # Update habit properties
    habit = await client.update_habit(
        habit_id="...",
        name="Daily Exercise",
        goal=45,  # Increase goal to 45 minutes
        color="#FF5500",
        encouragement="You've got this!",
    )

    # Archive a habit (hide but preserve data)
    await client.archive_habit("habit_id")

    # Unarchive (restore)
    await client.unarchive_habit("habit_id")

    # Delete permanently
    await client.delete_habit("habit_id")

Habit Sections and Check-in History

async with TickTickClient.from_settings() as client:
    # Get habit sections (time-of-day groupings)
    sections = await client.get_habit_sections()
    for section in sections:
        print(f"{section.display_name}: {section.id}")
    # Output:
    #   Morning: 6940c47c16b2c87db11a567d
    #   Afternoon: 6940c47c16b2c87db11a567e
    #   Night: 6940c47c16b2c87db11a567f

    # Get check-in history
    checkins = await client.get_habit_checkins(
        habit_ids=["habit1", "habit2"],
        after_stamp=20251201,  # YYYYMMDD format
    )

    for habit_id, records in checkins.items():
        print(f"Habit {habit_id}:")
        for record in records:
            print(f"  {record.checkin_stamp}: {record.value}")

    # Get habit preferences
    prefs = await client.get_habit_preferences()
    print(f"Show in calendar: {prefs.show_in_calendar}")
    print(f"Show in today: {prefs.show_in_today}")

Focus/Pomodoro

Access your focus session data and productivity analytics.

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(),
    )

    for day in heatmap:
        if day.get("duration", 0) > 0:
            hours = day["duration"] / 3600
            print(f"{day.get('date')}: {hours:.1f} hours")

    # Focus time by tag (last 30 days)
    by_tag = await client.get_focus_by_tag(days=30)

    print("Focus time by tag:")
    for tag, seconds in sorted(by_tag.items(), key=lambda x: -x[1]):
        hours = seconds / 3600
        print(f"  {tag}: {hours:.1f} hours")

User & Statistics

async with TickTickClient.from_settings() as client:
    # User profile
    profile = await client.get_profile()
    print(f"Username: {profile.username}")
    print(f"Display Name: {profile.display_name}")
    print(f"Email: {profile.email}")

    # 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}")
    print(f"Total completed: {stats.completed_total}")
    print(f"Total pomodoros: {stats.total_pomo_count}")
    print(f"Pomo duration: {stats.total_pomo_duration / 3600:.1f} hours")

    # User preferences
    prefs = await client.get_preferences()
    print(f"Timezone: {prefs.get('timeZone')}")
    print(f"Week starts on: {['Sun', 'Mon'][prefs.get('weekStartDay', 0)]}")
    print(f"Default list: {prefs.get('defaultProjectId')}")

Error Handling

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}")

Full Sync

Get the complete account state in one call:

async with TickTickClient.from_settings() as client:
    state = await client.sync()

    projects = state.get("projectProfiles", [])
    tasks = state.get("syncTaskBean", {}).get("update", [])
    tags = state.get("tags", [])
    habits = state.get("habits", [])
    folders = state.get("projectGroups", [])

    print(f"Projects: {len(projects)}")
    print(f"Tasks: {len(tasks)}")
    print(f"Tags: {len(tags)}")
    print(f"Habits: {len(habits)}")
    print(f"Folders: {len(folders)}")
    print(f"Inbox ID: {state.get('inboxId')}")

Usage: MCP Server

The MCP server enables AI assistants (like Claude) to manage your TickTick tasks through natural language.

The easiest way to use the MCP server with Claude Code:

# Add the server with your credentials
claude mcp add ticktick \
  --transport stdio \
  --env TICKTICK_CLIENT_ID=your_client_id \
  --env TICKTICK_CLIENT_SECRET=your_client_secret \
  --env TICKTICK_ACCESS_TOKEN=your_access_token \
  --env TICKTICK_USERNAME=your_email \
  --env TICKTICK_PASSWORD=your_password \
  -- ticktick-sdk

Or if installed from source:

claude mcp add ticktick \
  --transport stdio \
  --env TICKTICK_CLIENT_ID=your_client_id \
  --env TICKTICK_CLIENT_SECRET=your_client_secret \
  --env TICKTICK_ACCESS_TOKEN=your_access_token \
  --env TICKTICK_USERNAME=your_email \
  --env TICKTICK_PASSWORD=your_password \
  -- /path/to/ticktick-sdk/.venv/bin/ticktick-sdk

Verify it's working:

claude mcp list        # See all configured servers
/mcp                   # Within Claude Code, check server status

Option 2: Claude Desktop

Add to your Claude Desktop config:

macOS: ~/Library/Application Support/Claude/claude_desktop_config.json Windows: %APPDATA%\Claude\claude_desktop_config.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"
      }
    }
  }
}

Or if using a virtual environment:

{
  "mcpServers": {
    "ticktick": {
      "command": "/path/to/ticktick-sdk/.venv/bin/python",
      "args": ["-m", "ticktick_sdk.server"],
      "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"
      }
    }
  }
}

Option 3: Run Standalone

# Make sure your .env is configured, then run:
ticktick-sdk

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 (45 Total)

Task Tools

ToolDescription
ticktick_create_taskCreate a new task with title, dates, tags, etc.
ticktick_get_taskGet task details by ID
ticktick_list_tasksList tasks with optional filters
ticktick_update_taskUpdate task properties
ticktick_complete_taskMark task as complete
ticktick_delete_taskDelete a task (moves to trash)
ticktick_move_taskMove task between projects
ticktick_make_subtaskCreate parent-child relationship
ticktick_unparent_subtaskRemove parent-child relationship
ticktick_completed_tasksList recently completed tasks
ticktick_abandoned_tasksList abandoned ("won't do") tasks
ticktick_deleted_tasksList deleted tasks (in trash)
ticktick_search_tasksSearch tasks by text

Project Tools

ToolDescription
ticktick_list_projectsList all projects
ticktick_get_projectGet project details with tasks
ticktick_create_projectCreate a new project
ticktick_update_projectUpdate project properties
ticktick_delete_projectDelete a project

Folder Tools

ToolDescription
ticktick_list_foldersList all folders
ticktick_create_folderCreate a folder
ticktick_rename_folderRename a folder
ticktick_delete_folderDelete a folder

Tag Tools

ToolDescription
ticktick_list_tagsList all tags
ticktick_create_tagCreate a tag with color
ticktick_update_tagUpdate tag color/parent
ticktick_delete_tagDelete a tag
ticktick_rename_tagRename a tag
ticktick_merge_tagsMerge two tags

Habit Tools

ToolDescription
ticktick_habitsList all habits
ticktick_habitGet habit details
ticktick_habit_sectionsList sections (morning/afternoon/night)
ticktick_create_habitCreate a new habit
ticktick_update_habitUpdate habit properties
ticktick_delete_habitDelete a habit
ticktick_checkin_habitCheck in (complete for today)
ticktick_archive_habitArchive a habit
ticktick_unarchive_habitUnarchive a habit
ticktick_habit_checkinsGet check-in history

User & Analytics Tools

ToolDescription
ticktick_get_profileGet user profile
ticktick_get_statusGet account status
ticktick_get_statisticsGet productivity stats
ticktick_get_preferencesGet user preferences
ticktick_focus_heatmapGet focus heatmap data
ticktick_focus_by_tagGet focus time by tag

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

ModelDescription
TaskTask with title, dates, priority, tags, subtasks, recurrence, etc.
ProjectProject/list container for tasks
ProjectGroupFolder for organizing projects
ProjectDataProject with its tasks (from get_project_tasks)
TagTag with name, label, color, and optional parent
HabitRecurring habit with type, goals, streaks, and check-ins
HabitSectionTime-of-day grouping (morning/afternoon/night)
HabitCheckinIndividual habit check-in record
HabitPreferencesUser habit settings
UserUser profile information
UserStatusAccount status (Pro, inbox ID, etc.)
UserStatisticsProductivity statistics (level, score, counts)
ChecklistItemSubtask/checklist item within a task

Enums

EnumValues
TaskStatusABANDONED (-1), ACTIVE (0), COMPLETED (2)
TaskPriorityNONE (0), LOW (1), MEDIUM (3), HIGH (5)
TaskKindTEXT, NOTE, CHECKLIST
ProjectKindTASK, NOTE
ViewModeLIST, KANBAN, TIMELINE

Exceptions

ExceptionDescription
TickTickErrorBase exception for all errors
TickTickAuthenticationErrorAuthentication failed
TickTickNotFoundErrorResource not found
TickTickValidationErrorInvalid input data
TickTickRateLimitErrorRate limit exceeded
TickTickConfigurationErrorMissing configuration
TickTickForbiddenErrorAccess denied
TickTickServerErrorServer-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.

# 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:

# 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. Deleted tasks remain accessible via get_task.

4. Date Clearing

To clear a task's due_date, you must also clear start_date:

# Clear both dates together
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:

  • client.inbox_id (after init)
  • await client.get_status()status.inbox_id

7. Habit Check-ins

Habit check-ins update total_checkins and current_streak on the habit object. The check-in history is queried separately via get_habit_checkins().


Environment Variables

VariableRequiredDescription
TICKTICK_CLIENT_IDYesOAuth2 client ID from developer portal
TICKTICK_CLIENT_SECRETYesOAuth2 client secret
TICKTICK_ACCESS_TOKENYesOAuth2 access token (from setup script)
TICKTICK_USERNAMEYesYour TickTick email
TICKTICK_PASSWORDYesYour TickTick password
TICKTICK_REDIRECT_URINoOAuth2 redirect URI (default: http://127.0.0.1:8080/callback)
TICKTICK_TIMEOUTNoRequest timeout in seconds (default: 30)
TICKTICK_DEVICE_IDNoDevice ID for V2 API (auto-generated)

Running Tests

# All tests (mock mode - no API calls)
pytest

# With verbose output
pytest -v

# Specific test file
pytest tests/test_client_tasks.py

# Specific test class
pytest tests/test_client_habits.py::TestCreateHabit

# Live tests (requires .env with valid credentials)
pytest --live

# With coverage
pytest --cov=ticktick_sdk --cov-report=term-missing

# Run only habit tests
pytest -m habits

# Run only mock tests
pytest -m mock_only

Test Markers

MarkerDescription
unitUnit tests (fast, isolated)
tasksTask-related tests
projectsProject-related tests
tagsTag-related tests
habitsHabit-related tests
focusFocus/Pomodoro tests
mock_onlyTests that only work with mocks
live_onlyTests 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
  • Ensure there are no extra spaces in your .env file

"V2 initialization failed"

  • Your password may contain special characters - try changing it
  • Check for 2FA/MFA (not currently supported)
  • Some regional accounts may have restrictions

"Rate limit exceeded"

  • Wait 30-60 seconds before retrying
  • Reduce the frequency of API calls
  • Consider caching frequently-accessed data

"Task not found" after creation

  • The API uses eventual consistency - add a small delay
  • Verify the task ID is correct (24-character hex string)

Habits not syncing

  • Habits require V2 API - ensure V2 credentials are correct
  • Check that habits are enabled in your TickTick settings

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

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 for details.


Acknowledgments

Server Config

{
  "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"
      }
    }
  }
}
Recommend Servers
TraeBuild with Free GPT-4.1 & Claude 3.7. Fully MCP-Ready.
CursorThe AI Code Editor
Amap Maps高德地图官方 MCP Server
DeepChatYour AI Partner on Desktop
WindsurfThe new purpose-built IDE to harness magic
Tavily Mcp
Context7Context7 MCP Server -- Up-to-date code documentation for LLMs and AI code editors
Howtocook Mcp基于Anduin2017 / HowToCook (程序员在家做饭指南)的mcp server,帮你推荐菜谱、规划膳食,解决“今天吃什么“的世纪难题; Based on Anduin2017/HowToCook (Programmer's Guide to Cooking at Home), MCP Server helps you recommend recipes, plan meals, and solve the century old problem of "what to eat today"
EdgeOne Pages MCPAn MCP service designed for deploying HTML content to EdgeOne Pages and obtaining an accessible public URL.
Jina AI MCP ToolsA Model Context Protocol (MCP) server that integrates with Jina AI Search Foundation APIs.
BlenderBlenderMCP connects Blender to Claude AI through the Model Context Protocol (MCP), allowing Claude to directly interact with and control Blender. This integration enables prompt assisted 3D modeling, scene creation, and manipulation.
Serper MCP ServerA Serper MCP Server
Playwright McpPlaywright MCP server
TimeA Model Context Protocol server that provides time and timezone conversion capabilities. This server enables LLMs to get current time information and perform timezone conversions using IANA timezone names, with automatic system timezone detection.
Baidu Map百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
MiniMax MCPOfficial MiniMax Model Context Protocol (MCP) server that enables interaction with powerful Text to Speech, image generation and video generation APIs.
ChatWiseThe second fastest AI chatbot™
Visual Studio Code - Open Source ("Code - OSS")Visual Studio Code
AiimagemultistyleA Model Context Protocol (MCP) server for image generation and manipulation using fal.ai's Stable Diffusion model.
MCP AdvisorMCP Advisor & Installation - Use the right MCP server for your needs
Zhipu Web SearchZhipu Web Search MCP Server is a search engine specifically designed for large models. It integrates four search engines, allowing users to flexibly compare and switch between them. Building upon the web crawling and ranking capabilities of traditional search engines, it enhances intent recognition capabilities, returning results more suitable for large model processing (such as webpage titles, URLs, summaries, site names, site icons, etc.). This helps AI applications achieve "dynamic knowledge acquisition" and "precise scenario adaptation" capabilities.