Skip to main content

A comprehensive async Python SDK for TickTick with MCP server support

Project description

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

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 ticktick-py tickthon 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

From PyPI (Recommended)

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 auth command:

ticktick-sdk auth

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 ticktick-sdk auth --manual for a text-based flow that doesn't require a browser.

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

Type Description Example
Boolean Simple yes/no "Did you exercise today?"
Real Numeric 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)

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

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.

Option 1: Claude Code (Recommended)

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

# Add the server with your credentials
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

Or if installed from source:

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 \
  -- /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

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

Examples:

# Start the MCP server for AI assistant integration
ticktick-sdk

# Get OAuth2 token (opens browser for authorization)
ticktick-sdk auth

# Get OAuth2 token in SSH/headless environments
ticktick-sdk auth --manual

# Check version
ticktick-sdk --version

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

Tool Description
ticktick_create_task Create a new task with title, dates, tags, etc.
ticktick_get_task Get task details by ID
ticktick_list_tasks List tasks with optional filters
ticktick_update_task Update task properties
ticktick_complete_task Mark task as complete
ticktick_delete_task Delete a task (moves to trash)
ticktick_move_task Move task between projects
ticktick_make_subtask Create parent-child relationship
ticktick_unparent_subtask Remove parent-child relationship
ticktick_completed_tasks List recently completed tasks
ticktick_abandoned_tasks List abandoned ("won't do") tasks
ticktick_deleted_tasks List deleted tasks (in trash)
ticktick_search_tasks Search tasks by text

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

Tag Tools

Tool Description
ticktick_list_tags List all tags
ticktick_create_tag Create a tag with color
ticktick_update_tag Update tag color/parent
ticktick_delete_tag Delete a tag
ticktick_rename_tag Rename a tag
ticktick_merge_tags Merge two tags

Habit Tools

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
ticktick_delete_habit Delete a habit
ticktick_checkin_habit Check in (complete for today)
ticktick_archive_habit Archive a habit
ticktick_unarchive_habit Unarchive a habit
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

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)
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.

# 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

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 setup script)
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_TIMEOUT No Request timeout in seconds (default: 30)
TICKTICK_DEVICE_ID No Device 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

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
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
  • 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ticktick_sdk-0.2.0.tar.gz (141.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ticktick_sdk-0.2.0-py3-none-any.whl (108.3 kB view details)

Uploaded Python 3

File details

Details for the file ticktick_sdk-0.2.0.tar.gz.

File metadata

  • Download URL: ticktick_sdk-0.2.0.tar.gz
  • Upload date:
  • Size: 141.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for ticktick_sdk-0.2.0.tar.gz
Algorithm Hash digest
SHA256 9cf493f6319b95562bc36fdee00da597c8b30c5e1e1c2322fb5d245e83e9b034
MD5 6fbdd0df94f96ae02532dd9b8c1a43f6
BLAKE2b-256 eacb6657494668b5843fffb7370d17ec842eaf4f482fa954f7cb3c00342986b5

See more details on using hashes here.

File details

Details for the file ticktick_sdk-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: ticktick_sdk-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 108.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for ticktick_sdk-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9c8e23fe597ea5f4323b4cb8abf4d81730ffd9735b14f384abf63a0f435a808f
MD5 c95ff5efc7416cbada35a70bcb176243
BLAKE2b-256 ca1431c63ad1639a4d2ef8add619bbf40c6216b85f19680ffc4d56084524a8a7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page