{"id":37070582,"url":"https://github.com/dev-mirzabicer/ticktick-sdk","last_synced_at":"2026-01-18T06:16:08.006Z","repository":{"id":329216442,"uuid":"1117284311","full_name":"dev-mirzabicer/ticktick-sdk","owner":"dev-mirzabicer","description":"A comprehensive async Python library for TickTick with MCP support.","archived":false,"fork":false,"pushed_at":"2025-12-28T04:31:18.000Z","size":520,"stargazers_count":23,"open_issues_count":6,"forks_count":3,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-30T10:49:52.366Z","etag":null,"topics":["ai-task-manager","mcp","task-manager","ticktick","ticktick-api","ticktick-mcp","todo"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dev-mirzabicer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-16T05:11:03.000Z","updated_at":"2025-12-29T21:23:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dev-mirzabicer/ticktick-sdk","commit_stats":null,"previous_names":["dev-mirzabicer/ticktick-mcp"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/dev-mirzabicer/ticktick-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-mirzabicer%2Fticktick-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-mirzabicer%2Fticktick-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-mirzabicer%2Fticktick-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-mirzabicer%2Fticktick-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dev-mirzabicer","download_url":"https://codeload.github.com/dev-mirzabicer/ticktick-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-mirzabicer%2Fticktick-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28413701,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T05:26:33.345Z","status":"ssl_error","status_checked_at":"2026-01-14T05:21:57.251Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-task-manager","mcp","task-manager","ticktick","ticktick-api","ticktick-mcp","todo"],"created_at":"2026-01-14T08:14:32.396Z","updated_at":"2026-01-18T06:16:07.993Z","avatar_url":"https://github.com/dev-mirzabicer.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ticktick-sdk: A TickTick MCP Server \u0026 Full Python SDK\n\n![PyPI - Version](https://img.shields.io/pypi/v/ticktick-sdk?color=green)\n[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![PyPI Downloads](https://static.pepy.tech/personalized-badge/ticktick-sdk?period=total\u0026units=INTERNATIONAL_SYSTEM\u0026left_color=GREY\u0026right_color=ORANGE\u0026left_text=downloads)](https://pepy.tech/projects/ticktick-sdk)\n\nA comprehensive async Python SDK for [TickTick](https://ticktick.com) with [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) server support.\n\n**Use TickTick programmatically from Python, or let AI assistants manage your tasks.**\n\n## Table of Contents\n\n- [Features](#features)\n- [Why This Library?](#why-this-library)\n- [Installation](#installation)\n- [MCP Server Setup \u0026 Usage](#mcp-server-setup--usage)\n  - [Step 1: Register Your App](#step-1-register-your-app)\n  - [Step 2: Get OAuth2 Access Token](#step-2-get-oauth2-access-token)\n  - [Step 3: Configure Your AI Assistant](#step-3-configure-your-ai-assistant)\n  - [CLI Reference](#cli-reference)\n  - [Example Conversations](#example-conversations)\n  - [Available MCP Tools](#available-mcp-tools-51-total)\n- [Python Library Setup \u0026 Usage](#python-library-setup--usage)\n  - [Setup](#setup)\n  - [Quick Start](#quick-start)\n  - [Tasks](#tasks)\n  - [Projects \u0026 Folders](#projects--folders)\n  - [Tags](#tags)\n  - [Habits](#habits)\n  - [Focus/Pomodoro](#focuspomodoro)\n  - [User \u0026 Statistics](#user--statistics)\n  - [Error Handling](#error-handling)\n- [Architecture](#architecture)\n- [API Reference](#api-reference)\n- [TickTick API Quirks](#important-ticktick-api-quirks)\n- [Environment Variables](#environment-variables)\n- [Running Tests](#running-tests)\n- [Troubleshooting](#troubleshooting)\n- [Contributing](#contributing)\n\n---\n\n## Features\n\n### MCP Server\n- **51 Tools**: Comprehensive coverage of TickTick functionality\n- **AI-Ready**: Works with Claude, GPT, and other MCP-compatible assistants\n- **Dual Output**: Markdown for humans, JSON for machines\n\n### Python Library\n- **Full Async Support**: Built on `httpx` for high-performance async operations\n- **Complete Task Management**: Create, read, update, delete, complete, move, pin tasks\n- **Kanban Boards**: Full column management (create, update, delete, move tasks between columns)\n- **Project Organization**: Projects, folders, kanban boards\n- **Tag System**: Hierarchical tags with colors\n- **Habit Tracking**: Full CRUD for habits with check-ins, streaks, and goals\n- **Focus/Pomodoro**: Access focus session data and statistics\n- **User Analytics**: Productivity scores, levels, completion rates\n\n### Developer Experience\n- **Type-Safe**: Full Pydantic v2 validation with comprehensive type hints\n- **Well-Tested**: 300+ tests covering both mock and live API interactions\n- **Documented**: Extensive docstrings and examples\n\n---\n\n## Why This Library?\n\n### The Two-API Problem\n\nTickTick has **two different APIs**:\n\n| API | Type | What We Use It For |\n|-----|------|-------------------|\n| **V1 (OAuth2)** | Official, documented | Project with all tasks, basic operations |\n| **V2 (Session)** | Unofficial, reverse-engineered | Tags, folders, habits, focus, subtasks, and more |\n\nThe 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.\n\n### Compared to Other Libraries\n\nBased on analysis of the actual source code of available TickTick Python libraries:\n\n| 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) |\n|---------|:------------:|:----------:|:-----------:|:--------:|:---------------:|\n| **I/O Model** | Async | Async | Sync | Sync | Sync |\n| **Type System** | Pydantic V2 | Pydantic V2 | Dicts | attrs | addict |\n| **MCP Server** | **Yes** | No | No | No | No |\n| **Habits** | **Full CRUD** | No | Basic | Basic | No |\n| **Focus/Pomo** | Yes | Yes | Yes | Yes | No |\n| **Unified V1+V2** | **Smart Routing** | Separate | Both | V2 only | V2 only |\n| **Subtasks** | Advanced | Batch | Yes | Basic | Basic |\n| **Tags** | Full (merge/rename) | Yes | Yes | Yes | No |\n\n**Key Differentiators:**\n\n- **MCP Server**: Only ticktick-sdk provides AI assistant integration via Model Context Protocol\n- **Unified API Routing**: Automatically routes operations to V1 or V2 based on feature requirements\n- **Full Habit CRUD**: Complete habit management including check-ins, streaks, archive/unarchive\n- **Async-First**: Built on `httpx` for high-performance async operations\n\n---\n\n## Installation\n\n```bash\npip install ticktick-sdk\n```\n\n**Requirements:**\n- Python 3.11+\n- TickTick account (free or Pro)\n\n---\n\n## MCP Server Setup \u0026 Usage\n\nUse TickTick with AI assistants like Claude through the Model Context Protocol.\n\n### Step 1: Register Your App\n\n1. Go to the [TickTick Developer Portal](https://developer.ticktick.com/manage)\n2. Click **\"Create App\"**\n3. Fill in:\n   - **App Name**: e.g., \"My TickTick MCP\"\n   - **Redirect URI**: `http://127.0.0.1:8080/callback`\n4. Save your **Client ID** and **Client Secret**\n\n### Step 2: Get OAuth2 Access Token\n\nRun the auth command with your credentials:\n\n```bash\nTICKTICK_CLIENT_ID=your_client_id \\\nTICKTICK_CLIENT_SECRET=your_client_secret \\\nticktick-sdk auth\n```\n\nThis will:\n1. **Open your browser** to TickTick's authorization page\n2. **Authorize the app** - Click \"Authorize\" to grant access\n3. **Return to terminal** - After authorizing, you'll see output like this:\n\n```\n============================================================\n  SUCCESS! Here is your access token:\n============================================================\n\na]234abc-5678-90de-f012-34567890abcd\n\n============================================================\n\nNEXT STEPS:\n\nFor Claude Code users:\n  Run (replace YOUR_* placeholders):\n    claude mcp add ticktick \\\n      -e TICKTICK_CLIENT_ID=YOUR_CLIENT_ID \\\n      ...\n```\n\n4. **Copy this token** - You'll need it in the next step\n\n\u003e **Note**: Sometimes the browser shows an \"invalid credentials\" error page. Just refresh the page and it should work.\n\n\u003e **SSH/Headless Users**: Add `--manual` flag for a text-based flow that doesn't require a browser.\n\n### Step 3: Configure Your AI Assistant\n\n#### Claude Code (Recommended)\n\n```bash\nclaude mcp add ticktick \\\n  -e TICKTICK_CLIENT_ID=your_client_id \\\n  -e TICKTICK_CLIENT_SECRET=your_client_secret \\\n  -e TICKTICK_ACCESS_TOKEN=your_access_token \\\n  -e TICKTICK_USERNAME=your_email \\\n  -e TICKTICK_PASSWORD=your_password \\\n  -- ticktick-sdk\n```\n\n\u003e **Note**: For `TICKTICK_ACCESS_TOKEN`, paste the token you copied from Step 2.\n\nVerify it's working:\n\n```bash\nclaude mcp list        # See all configured servers\n/mcp                   # Within Claude Code, check server status\n```\n\n#### Claude Desktop\n\nAdd to your Claude Desktop config:\n\n**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`\n**Windows**: `%APPDATA%\\Claude\\claude_desktop_config.json`\n\n```json\n{\n  \"mcpServers\": {\n    \"ticktick\": {\n      \"command\": \"ticktick-sdk\",\n      \"env\": {\n        \"TICKTICK_CLIENT_ID\": \"your_client_id\",\n        \"TICKTICK_CLIENT_SECRET\": \"your_client_secret\",\n        \"TICKTICK_ACCESS_TOKEN\": \"your_access_token\",\n        \"TICKTICK_USERNAME\": \"your_email\",\n        \"TICKTICK_PASSWORD\": \"your_password\"\n      }\n    }\n  }\n}\n```\n\n#### Other MCP-Compatible Tools\n\nThis 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.\n\n### CLI Reference\n\nThe `ticktick-sdk` command provides several subcommands:\n\n| Command | Description |\n|---------|-------------|\n| `ticktick-sdk` | Start the MCP server (default) |\n| `ticktick-sdk server` | Start the MCP server (explicit) |\n| `ticktick-sdk auth` | Get OAuth2 access token (opens browser) |\n| `ticktick-sdk auth --manual` | Get OAuth2 access token (SSH-friendly) |\n| `ticktick-sdk --version` | Show version information |\n| `ticktick-sdk --help` | Show help message |\n\n### Example Conversations\n\nOnce configured, you can ask Claude things like:\n\n- \"What tasks do I have due today?\"\n- \"Create a task to call John tomorrow at 2pm\"\n- \"Show me my high priority tasks\"\n- \"Mark the grocery shopping task as complete\"\n- \"What's my current streak for the Exercise habit?\"\n- \"Check in my meditation habit for today\"\n- \"Create a new habit to drink 8 glasses of water daily\"\n\n### Available MCP Tools (51 Total)\n\n#### Task Tools\n| Tool | Description |\n|------|-------------|\n| `ticktick_create_task` | Create a new task with title, dates, tags, etc. |\n| `ticktick_get_task` | Get task details by ID |\n| `ticktick_list_tasks` | List tasks with optional filters |\n| `ticktick_update_task` | Update task properties |\n| `ticktick_complete_task` | Mark task as complete |\n| `ticktick_delete_task` | Delete a task (moves to trash) |\n| `ticktick_move_task` | Move task between projects |\n| `ticktick_make_subtask` | Create parent-child relationship |\n| `ticktick_unparent_subtask` | Remove parent-child relationship |\n| `ticktick_completed_tasks` | List recently completed tasks |\n| `ticktick_abandoned_tasks` | List abandoned (\"won't do\") tasks |\n| `ticktick_deleted_tasks` | List deleted tasks (in trash) |\n| `ticktick_search_tasks` | Search tasks by text |\n| `ticktick_pin_task` | Pin or unpin a task |\n\n#### Project Tools\n| Tool | Description |\n|------|-------------|\n| `ticktick_list_projects` | List all projects |\n| `ticktick_get_project` | Get project details with tasks |\n| `ticktick_create_project` | Create a new project |\n| `ticktick_update_project` | Update project properties |\n| `ticktick_delete_project` | Delete a project |\n\n#### Folder Tools\n| Tool | Description |\n|------|-------------|\n| `ticktick_list_folders` | List all folders |\n| `ticktick_create_folder` | Create a folder |\n| `ticktick_rename_folder` | Rename a folder |\n| `ticktick_delete_folder` | Delete a folder |\n\n#### Kanban Column Tools\n| Tool | Description |\n|------|-------------|\n| `ticktick_list_columns` | List columns for a kanban project |\n| `ticktick_create_column` | Create a kanban column |\n| `ticktick_update_column` | Update column name or order |\n| `ticktick_delete_column` | Delete a kanban column |\n| `ticktick_move_task_to_column` | Move task to a column |\n\n#### Tag Tools\n| Tool | Description |\n|------|-------------|\n| `ticktick_list_tags` | List all tags |\n| `ticktick_create_tag` | Create a tag with color |\n| `ticktick_update_tag` | Update tag color/parent |\n| `ticktick_delete_tag` | Delete a tag |\n| `ticktick_rename_tag` | Rename a tag |\n| `ticktick_merge_tags` | Merge two tags |\n\n#### Habit Tools\n| Tool | Description |\n|------|-------------|\n| `ticktick_habits` | List all habits |\n| `ticktick_habit` | Get habit details |\n| `ticktick_habit_sections` | List sections (morning/afternoon/night) |\n| `ticktick_create_habit` | Create a new habit |\n| `ticktick_update_habit` | Update habit properties |\n| `ticktick_delete_habit` | Delete a habit |\n| `ticktick_checkin_habit` | Check in for today or a past date |\n| `ticktick_archive_habit` | Archive a habit |\n| `ticktick_unarchive_habit` | Unarchive a habit |\n| `ticktick_habit_checkins` | Get check-in history |\n\n#### User \u0026 Analytics Tools\n| Tool | Description |\n|------|-------------|\n| `ticktick_get_profile` | Get user profile |\n| `ticktick_get_status` | Get account status |\n| `ticktick_get_statistics` | Get productivity stats |\n| `ticktick_get_preferences` | Get user preferences |\n| `ticktick_focus_heatmap` | Get focus heatmap data |\n| `ticktick_focus_by_tag` | Get focus time by tag |\n\n---\n\n## Python Library Setup \u0026 Usage\n\nUse TickTick programmatically in your Python applications.\n\n### Setup\n\n#### Step 1: Register Your App\n\nSame as MCP setup - go to the [TickTick Developer Portal](https://developer.ticktick.com/manage) and create an app.\n\n#### Step 2: Create Your .env File\n\nCreate a `.env` file in your project directory:\n\n```bash\n# V1 API (OAuth2)\nTICKTICK_CLIENT_ID=your_client_id_here\nTICKTICK_CLIENT_SECRET=your_client_secret_here\nTICKTICK_REDIRECT_URI=http://127.0.0.1:8080/callback\nTICKTICK_ACCESS_TOKEN=  # Will be filled in Step 3\n\n# V2 API (Session)\nTICKTICK_USERNAME=your_ticktick_email@example.com\nTICKTICK_PASSWORD=your_ticktick_password\n\n# Optional\nTICKTICK_TIMEOUT=30\n```\n\n#### Step 3: Get OAuth2 Access Token\n\n```bash\n# Source your .env file first, or export the variables\nticktick-sdk auth\n```\n\nCopy the access token to your `.env` file.\n\n#### Step 4: Verify Setup\n\n```python\nimport asyncio\nfrom ticktick_sdk import TickTickClient\n\nasync def test():\n    async with TickTickClient.from_settings() as client:\n        profile = await client.get_profile()\n        print(f'Connected as: {profile.display_name}')\n\nasyncio.run(test())\n```\n\n### Quick Start\n\n```python\nimport asyncio\nfrom ticktick_sdk import TickTickClient\n\nasync def main():\n    async with TickTickClient.from_settings() as client:\n        # Create a task\n        task = await client.create_task(\n            title=\"Learn ticktick-sdk\",\n            tags=[\"python\", \"productivity\"],\n        )\n        print(f\"Created: {task.title} (ID: {task.id})\")\n\n        # List all tasks\n        tasks = await client.get_all_tasks()\n        print(f\"You have {len(tasks)} active tasks\")\n\n        # Complete the task\n        await client.complete_task(task.id, task.project_id)\n        print(\"Task completed!\")\n\nasyncio.run(main())\n```\n\n### Tasks\n\n#### Creating Tasks\n\n```python\nfrom datetime import datetime, timedelta\nfrom ticktick_sdk import TickTickClient\n\nasync with TickTickClient.from_settings() as client:\n    # Simple task\n    task = await client.create_task(title=\"Buy groceries\")\n\n    # Task with due date and priority\n    task = await client.create_task(\n        title=\"Submit report\",\n        due_date=datetime.now() + timedelta(days=1),\n        priority=\"high\",  # none, low, medium, high\n    )\n\n    # Task with tags and content\n    task = await client.create_task(\n        title=\"Review PR #123\",\n        content=\"Check for:\\n- Code style\\n- Tests\\n- Documentation\",\n        tags=[\"work\", \"code-review\"],\n    )\n\n    # Recurring task (MUST include start_date!)\n    task = await client.create_task(\n        title=\"Daily standup\",\n        start_date=datetime(2025, 1, 20, 9, 0),\n        recurrence=\"RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR\",\n    )\n\n    # Task with reminder\n    task = await client.create_task(\n        title=\"Meeting with team\",\n        due_date=datetime(2025, 1, 20, 14, 0),\n        reminders=[\"TRIGGER:-PT15M\"],  # 15 minutes before\n    )\n\n    # All-day task\n    task = await client.create_task(\n        title=\"Project deadline\",\n        due_date=datetime(2025, 1, 31),\n        all_day=True,\n    )\n```\n\n#### Managing Tasks\n\n```python\nasync with TickTickClient.from_settings() as client:\n    # Get a specific task\n    task = await client.get_task(task_id=\"...\")\n\n    # Update a task\n    task.title = \"Updated title\"\n    task.priority = 5  # high priority\n    await client.update_task(task)\n\n    # Complete a task\n    await client.complete_task(task_id=\"...\", project_id=\"...\")\n\n    # Delete a task (moves to trash)\n    await client.delete_task(task_id=\"...\", project_id=\"...\")\n\n    # Move task to another project\n    await client.move_task(\n        task_id=\"...\",\n        from_project_id=\"...\",\n        to_project_id=\"...\",\n    )\n```\n\n#### Subtasks\n\n```python\nasync with TickTickClient.from_settings() as client:\n    # Create parent task\n    parent = await client.create_task(title=\"Main task\")\n\n    # Create child task\n    child = await client.create_task(title=\"Subtask\")\n\n    # Make it a subtask (parent_id in create is ignored by API)\n    await client.make_subtask(\n        task_id=child.id,\n        parent_id=parent.id,\n        project_id=child.project_id,\n    )\n\n    # Remove parent relationship\n    await client.unparent_subtask(\n        task_id=child.id,\n        project_id=child.project_id,\n    )\n```\n\n#### Querying Tasks\n\n```python\nasync with TickTickClient.from_settings() as client:\n    # All active tasks\n    all_tasks = await client.get_all_tasks()\n\n    # Tasks due today\n    today = await client.get_today_tasks()\n\n    # Overdue tasks\n    overdue = await client.get_overdue_tasks()\n\n    # Tasks by tag\n    work_tasks = await client.get_tasks_by_tag(\"work\")\n\n    # Tasks by priority\n    urgent = await client.get_tasks_by_priority(\"high\")\n\n    # Search tasks\n    results = await client.search_tasks(\"meeting\")\n\n    # Recently completed\n    completed = await client.get_completed_tasks(days=7, limit=50)\n\n    # Abandoned tasks (\"won't do\")\n    abandoned = await client.get_abandoned_tasks(days=30)\n\n    # Deleted tasks (in trash)\n    deleted = await client.get_deleted_tasks(limit=50)\n```\n\n### Projects \u0026 Folders\n\n#### Projects\n\n```python\nasync with TickTickClient.from_settings() as client:\n    # List all projects\n    projects = await client.get_all_projects()\n    for project in projects:\n        print(f\"{project.name} ({project.id})\")\n\n    # Get project with all its tasks\n    project_data = await client.get_project_tasks(project_id=\"...\")\n    print(f\"Project: {project_data.project.name}\")\n    print(f\"Tasks: {len(project_data.tasks)}\")\n\n    # Create a project\n    project = await client.create_project(\n        name=\"Q1 Goals\",\n        color=\"#4A90D9\",\n        view_mode=\"kanban\",  # list, kanban, timeline\n    )\n\n    # Update a project\n    await client.update_project(\n        project_id=project.id,\n        name=\"Q1 Goals 2025\",\n        color=\"#FF5500\",\n    )\n\n    # Delete a project\n    await client.delete_project(project_id=\"...\")\n```\n\n#### Folders (Project Groups)\n\n```python\nasync with TickTickClient.from_settings() as client:\n    # List all folders\n    folders = await client.get_all_folders()\n\n    # Create a folder\n    folder = await client.create_folder(name=\"Work Projects\")\n\n    # Create project in folder\n    project = await client.create_project(\n        name=\"Client A\",\n        folder_id=folder.id,\n    )\n\n    # Rename a folder\n    await client.rename_folder(folder_id=folder.id, name=\"Work\")\n\n    # Delete a folder\n    await client.delete_folder(folder_id=\"...\")\n```\n\n### Tags\n\nTags in TickTick support hierarchy (parent-child relationships) and custom colors.\n\n```python\nasync with TickTickClient.from_settings() as client:\n    # List all tags\n    tags = await client.get_all_tags()\n    for tag in tags:\n        print(f\"{tag.label} ({tag.name}) - {tag.color}\")\n\n    # Create a tag\n    tag = await client.create_tag(\n        name=\"urgent\",\n        color=\"#FF0000\",\n    )\n\n    # Create nested tag\n    child_tag = await client.create_tag(\n        name=\"critical\",\n        parent=\"urgent\",  # Parent tag name\n    )\n\n    # Rename a tag\n    await client.rename_tag(old_name=\"urgent\", new_name=\"priority\")\n\n    # Update tag color or parent\n    await client.update_tag(\n        name=\"priority\",\n        color=\"#FF5500\",\n    )\n\n    # Merge tags (move all tasks from source to target)\n    await client.merge_tags(source=\"old-tag\", target=\"new-tag\")\n\n    # Delete a tag\n    await client.delete_tag(name=\"obsolete\")\n```\n\n### Habits\n\nTickTick habits are recurring activities you want to track daily.\n\n#### Habit Types\n\n| Type | Description | Example |\n|------|-------------|---------|\n| `Boolean` | Simple yes/no | \"Did you exercise today?\" |\n| `Real` | Numeric counter | \"How many pages did you read?\" |\n\n#### Creating and Managing Habits\n\n```python\nasync with TickTickClient.from_settings() as client:\n    # List all habits\n    habits = await client.get_all_habits()\n\n    # Boolean habit (yes/no)\n    exercise = await client.create_habit(\n        name=\"Exercise\",\n        color=\"#4A90D9\",\n        reminders=[\"07:00\", \"19:00\"],\n        target_days=30,\n        encouragement=\"Stay strong!\",\n    )\n\n    # Numeric habit\n    reading = await client.create_habit(\n        name=\"Read\",\n        habit_type=\"Real\",\n        goal=30,           # 30 pages per day\n        step=5,            # +5 button increment\n        unit=\"Pages\",\n    )\n\n    # Check in a habit (today)\n    habit = await client.checkin_habit(\"habit_id\")\n    print(f\"Streak: {habit.current_streak} days!\")\n\n    # Check in for a past date (backdate)\n    from datetime import date\n    habit = await client.checkin_habit(\"habit_id\", checkin_date=date(2025, 12, 15))\n\n    # Archive/unarchive\n    await client.archive_habit(\"habit_id\")\n    await client.unarchive_habit(\"habit_id\")\n```\n\n#### Habit Repeat Rules (RRULE Format)\n\n| Schedule | RRULE |\n|----------|-------|\n| Daily (every day) | `RRULE:FREQ=WEEKLY;BYDAY=SU,MO,TU,WE,TH,FR,SA` |\n| Weekdays only | `RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR` |\n| Weekends only | `RRULE:FREQ=WEEKLY;BYDAY=SA,SU` |\n| X times per week | `RRULE:FREQ=WEEKLY;TT_TIMES=5` |\n| Specific days | `RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR` |\n\n### Focus/Pomodoro\n\n```python\nfrom datetime import date, timedelta\n\nasync with TickTickClient.from_settings() as client:\n    # Focus heatmap (like GitHub contribution graph)\n    heatmap = await client.get_focus_heatmap(\n        start_date=date.today() - timedelta(days=90),\n        end_date=date.today(),\n    )\n\n    # Focus time by tag\n    by_tag = await client.get_focus_by_tag(days=30)\n    for tag, seconds in sorted(by_tag.items(), key=lambda x: -x[1]):\n        hours = seconds / 3600\n        print(f\"  {tag}: {hours:.1f} hours\")\n```\n\n### User \u0026 Statistics\n\n```python\nasync with TickTickClient.from_settings() as client:\n    # User profile\n    profile = await client.get_profile()\n    print(f\"Username: {profile.username}\")\n\n    # Account status\n    status = await client.get_status()\n    print(f\"Pro User: {status.is_pro}\")\n    print(f\"Inbox ID: {status.inbox_id}\")\n\n    # Productivity statistics\n    stats = await client.get_statistics()\n    print(f\"Level: {stats.level}\")\n    print(f\"Score: {stats.score}\")\n    print(f\"Tasks completed today: {stats.today_completed}\")\n```\n\n### Error Handling\n\n```python\nfrom ticktick_sdk import (\n    TickTickClient,\n    TickTickError,\n    TickTickNotFoundError,\n    TickTickAuthenticationError,\n    TickTickRateLimitError,\n    TickTickValidationError,\n)\n\nasync with TickTickClient.from_settings() as client:\n    try:\n        task = await client.get_task(\"nonexistent-id\")\n    except TickTickNotFoundError as e:\n        print(f\"Task not found: {e}\")\n    except TickTickAuthenticationError:\n        print(\"Authentication failed - check credentials\")\n    except TickTickRateLimitError:\n        print(\"Rate limited - wait and retry\")\n    except TickTickValidationError as e:\n        print(f\"Invalid input: {e}\")\n    except TickTickError as e:\n        print(f\"TickTick error: {e}\")\n```\n\n---\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                    Your Application                         │\n│              (or MCP Server for AI Assistants)              │\n└─────────────────────────┬───────────────────────────────────┘\n                          │\n┌─────────────────────────▼───────────────────────────────────┐\n│                    TickTickClient                           │\n│            High-level, user-friendly async API              │\n│   (tasks, projects, tags, habits, focus, user methods)      │\n└─────────────────────────┬───────────────────────────────────┘\n                          │\n┌─────────────────────────▼───────────────────────────────────┐\n│                  UnifiedTickTickAPI                         │\n│        Routes calls to V1 or V2, converts responses         │\n│              to unified Pydantic models                     │\n└─────────────────────────┬───────────────────────────────────┘\n                          │\n           ┌──────────────┴──────────────┐\n           ▼                             ▼\n┌──────────────────────┐      ┌──────────────────────┐\n│      V1 API          │      │      V2 API          │\n│     (OAuth2)         │      │     (Session)        │\n│                      │      │                      │\n│ • Official API       │      │ • Unofficial API     │\n│ • Project with tasks │      │ • Tags, folders      │\n│ • Limited features   │      │ • Habits, focus      │\n│                      │      │ • Full subtasks      │\n└──────────────────────┘      └──────────────────────┘\n```\n\n### Key Design Decisions\n\n1. **V2-First**: Most operations use V2 API (more features), falling back to V1 only when needed\n2. **Unified Models**: Single set of Pydantic models regardless of which API provides the data\n3. **Async Throughout**: All I/O operations are async for performance\n4. **Type Safety**: Full type hints and Pydantic validation\n\n---\n\n## API Reference\n\n### Models\n\n| Model | Description |\n|-------|-------------|\n| `Task` | Task with title, dates, priority, tags, subtasks, recurrence, etc. |\n| `Project` | Project/list container for tasks |\n| `ProjectGroup` | Folder for organizing projects |\n| `ProjectData` | Project with its tasks (from get_project_tasks) |\n| `Column` | Kanban column for organizing tasks in boards |\n| `Tag` | Tag with name, label, color, and optional parent |\n| `Habit` | Recurring habit with type, goals, streaks, and check-ins |\n| `HabitSection` | Time-of-day grouping (morning/afternoon/night) |\n| `HabitCheckin` | Individual habit check-in record |\n| `HabitPreferences` | User habit settings |\n| `User` | User profile information |\n| `UserStatus` | Account status (Pro, inbox ID, etc.) |\n| `UserStatistics` | Productivity statistics (level, score, counts) |\n| `ChecklistItem` | Subtask/checklist item within a task |\n\n### Enums\n\n| Enum | Values |\n|------|--------|\n| `TaskStatus` | `ABANDONED (-1)`, `ACTIVE (0)`, `COMPLETED (2)` |\n| `TaskPriority` | `NONE (0)`, `LOW (1)`, `MEDIUM (3)`, `HIGH (5)` |\n| `TaskKind` | `TEXT`, `NOTE`, `CHECKLIST` |\n| `ProjectKind` | `TASK`, `NOTE` |\n| `ViewMode` | `LIST`, `KANBAN`, `TIMELINE` |\n\n### Exceptions\n\n| Exception | Description |\n|-----------|-------------|\n| `TickTickError` | Base exception for all errors |\n| `TickTickAuthenticationError` | Authentication failed |\n| `TickTickNotFoundError` | Resource not found |\n| `TickTickValidationError` | Invalid input data |\n| `TickTickRateLimitError` | Rate limit exceeded |\n| `TickTickConfigurationError` | Missing configuration |\n| `TickTickForbiddenError` | Access denied |\n| `TickTickServerError` | Server-side error |\n\n---\n\n## Important: TickTick API Quirks\n\nTickTick's API has several unique behaviors you should know about:\n\n### 1. Recurrence Requires start_date\n\n**If you create a recurring task without a start_date, TickTick silently ignores the recurrence rule.**\n\n```python\n# WRONG - recurrence will be ignored!\ntask = await client.create_task(\n    title=\"Daily standup\",\n    recurrence=\"RRULE:FREQ=DAILY\",\n)\n\n# CORRECT\ntask = await client.create_task(\n    title=\"Daily standup\",\n    start_date=datetime(2025, 1, 20, 9, 0),\n    recurrence=\"RRULE:FREQ=DAILY\",\n)\n```\n\n### 2. Subtasks Require Separate Call\n\nSetting `parent_id` during task creation is **ignored** by the API:\n\n```python\n# Create the child task first\nchild = await client.create_task(title=\"Subtask\")\n\n# Then make it a subtask\nawait client.make_subtask(\n    task_id=child.id,\n    parent_id=\"parent_task_id\",\n    project_id=child.project_id,\n)\n```\n\n### 3. Soft Delete\n\nDeleting tasks moves them to trash (`deleted=1`) rather than permanently removing them.\n\n### 4. Date Clearing\n\nTo clear a task's `due_date`, you must also clear `start_date`:\n\n```python\ntask.due_date = None\ntask.start_date = None\nawait client.update_task(task)\n```\n\n### 5. Tag Order Not Preserved\n\nThe API does not preserve tag order - tags may be returned in any order.\n\n### 6. Inbox is Special\n\nThe inbox is a special project that cannot be deleted. Get its ID via `await client.get_status()`.\n\n---\n\n## Environment Variables\n\n| Variable | Required | Description |\n|----------|:--------:|-------------|\n| `TICKTICK_CLIENT_ID` | Yes | OAuth2 client ID from developer portal |\n| `TICKTICK_CLIENT_SECRET` | Yes | OAuth2 client secret |\n| `TICKTICK_ACCESS_TOKEN` | Yes | OAuth2 access token (from auth command) |\n| `TICKTICK_USERNAME` | Yes | Your TickTick email |\n| `TICKTICK_PASSWORD` | Yes | Your TickTick password |\n| `TICKTICK_REDIRECT_URI` | No | OAuth2 redirect URI (default: `http://127.0.0.1:8080/callback`) |\n| `TICKTICK_TIMEOUT` | No | Request timeout in seconds (default: `30`) |\n| `TICKTICK_DEVICE_ID` | No | Device ID for V2 API (auto-generated) |\n\n---\n\n## Running Tests\n\n```bash\n# Install dev dependencies\npip install -e \".[dev]\"\n\n# All tests (mock mode - no API calls)\npytest\n\n# With verbose output\npytest -v\n\n# Live tests (requires credentials)\npytest --live\n\n# With coverage\npytest --cov=ticktick_sdk --cov-report=term-missing\n```\n\n### Test Markers\n\n| Marker | Description |\n|--------|-------------|\n| `unit` | Unit tests (fast, isolated) |\n| `tasks` | Task-related tests |\n| `projects` | Project-related tests |\n| `tags` | Tag-related tests |\n| `habits` | Habit-related tests |\n| `focus` | Focus/Pomodoro tests |\n| `pinning` | Task pinning tests |\n| `columns` | Kanban column tests |\n| `mock_only` | Tests that only work with mocks |\n| `live_only` | Tests that only run with `--live` |\n\n---\n\n## Troubleshooting\n\n### \"Token exchange failed\"\n- Verify your Client ID and Client Secret are correct\n- Ensure the Redirect URI matches exactly (including trailing slashes)\n- Check that you're using the correct TickTick developer portal\n\n### \"Authentication failed\"\n- Check your TickTick username (email) and password\n- Try logging into ticktick.com to verify credentials\n\n### \"V2 initialization failed\"\n- Your password may contain special characters - try changing it\n- Check for 2FA/MFA (not currently supported)\n\n### \"Rate limit exceeded\"\n- Wait 30-60 seconds before retrying\n- Reduce the frequency of API calls\n\n---\n\n## Contributing\n\nContributions are welcome! Please:\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Write tests for new functionality\n4. Ensure all tests pass (`pytest`)\n5. Run type checking (`mypy src/`)\n6. Submit a pull request\n\n### Development Setup\n\n```bash\ngit clone https://github.com/dev-mirzabicer/ticktick-sdk.git\ncd ticktick-sdk\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e \".[dev]\"\n```\n\n---\n\n## License\n\nMIT License - see [LICENSE](LICENSE) for details.\n\n---\n\n## Acknowledgments\n\n- [TickTick](https://ticktick.com) for the excellent task management app\n- [Model Context Protocol](https://modelcontextprotocol.io/) for the AI integration standard\n- [FastMCP](https://github.com/jlowin/fastmcp) for the MCP framework\n- [Pydantic](https://docs.pydantic.dev/) for data validation\n- [httpx](https://www.python-httpx.org/) for async HTTP\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdev-mirzabicer%2Fticktick-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdev-mirzabicer%2Fticktick-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdev-mirzabicer%2Fticktick-sdk/lists"}