Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Master the Todoist API for task management automation, including projects, tasks, labels, filters, webhooks, and Python SDK patterns. This skill covers REST API v2, Sync API v9, and integration patterns.
When to Use This Skill
USE Todoist API when:
Automating task creation from external systems
Building integrations with other productivity tools
Creating custom task dashboards or reports
Implementing GTD workflows programmatically
Syncing tasks with calendar applications
Building CLI tools for task management
Automating recurring task patterns
Integrating with CI/CD for project tracking
DON'T USE Todoist API when:
Need complex project management (use Jira, Asana)
Require database-style queries (use Notion API)
Need real-time collaboration on tasks (use Linear)
Building for enterprise with SSO requirements
Need Gantt charts or resource management
Prerequisites
API Authentication
# Get your API token from:# https://todoist.com/app/settings/integrations/developer# Set environment variableexport TODOIST_API_KEY="your-api-token-here"# Verify authentication
curl -s -X GET "https://api.todoist.com/rest/v2/projects" \
-H "Authorization: Bearer $TODOIST_API_KEY" | jq '.[0]'
Python SDK Installation
# Install official Python SDK
pip install todoist-api-python
# Or with uv
uv pip install todoist-api-python
# For sync API features
pip install todoist-api-python requests
Verify Setup
from todoist_api_python import TodoistAPI
api = TodoistAPI("your-api-token")
# Test connectiontry:
projects = api.get_projects()
print(f"Connected! Found projects")
Exception e:
()
# Create Kanban-style sections
sections = ["Backlog", "To Do", "In Progress", "Review", "Done"]
for i, section_name inenumerate(sections):
api.add_section(
name=section_name,
project_id="2345678901",
order=i
)
# Get sections
sections = api.get_sections(project_id="2345678901")
for section in sections:
print(f"Section: {section.name} (ID: {section.id})")
# Move task to section
api.update_task(
task_id="1234567890",
section_id="IN_PROGRESS_SECTION_ID"
)
5. Comments Management
REST API - Comments:
# Get comments for task
curl -s -X GET "https://api.todoist.com/rest/v2/comments?task_id=TASK_ID" \
-H "Authorization: Bearer $TODOIST_API_KEY" | jq
# Get comments for project
curl -s -X GET "https://api.todoist.com/rest/v2/comments?project_id=PROJECT_ID" \
-H "Authorization: Bearer $TODOIST_API_KEY" | jq
# Add comment to task
curl -s -X POST "https://api.todoist.com/rest/v2/comments" \
-H "Authorization: Bearer $TODOIST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"task_id": "TASK_ID",
"content": "This is a comment on the task"
}' | jq
# Add comment with attachment
curl -s -X POST "https://api.todoist.com/rest/v2/comments" \
-H "Authorization: Bearer $TODOIST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"task_id": "TASK_ID",
"content": "See attached file",
"attachment": {
"file_name": "report.pdf",
"file_type": "application/pdf",
"file_url": "https://example.com/report.pdf"
}
}' | jq
# Update comment
curl -s -X POST "https://api.todoist.com/rest/v2/comments/COMMENT_ID" \
-H "Authorization: Bearer $TODOIST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Updated comment content"
}' | jq
# Delete comment
curl -s -X DELETE "https://api.todoist.com/rest/v2/comments/COMMENT_ID" \
-H "Authorization: Bearer $TODOIST_API_KEY"
Python SDK - Comments:
# Get comments for task
comments = api.get_comments(task_id="1234567890")
for comment in comments:
print(f" {comment.posted_at}: {comment.content}")
# Add comment
new_comment = api.add_comment(
task_id="1234567890",
content="Added some notes about this task"
)
# Update comment
api.update_comment(
comment_id=new_comment.id,
content="Updated notes"
)
# Delete comment
api.delete_comment(comment_id=new_comment.id)
6. Filters and Queries
Filter Syntax:
# Date filters"today"# Due today"tomorrow"# Due tomorrow"overdue"# Past due date"next 7 days"# Due in next week"no date"# No due date set"Jan 15"# Specific date"before: Jan 20"# Before date"after: Jan 10"# After date# Priority filters"p1"# Priority 1 (urgent)"p2"# Priority 2 (high)"p3"# Priority 3 (medium)"p4"# Priority 4 (normal)"(p1 | p2)"# Priority 1 OR 2# Label filters"@work"# Has label "work""@work & @urgent"# Has both labels"@work | @personal"# Has either label"!@work"# Does NOT have label# Project filters"#Work"# In project "Work""##Work"# In project "Work" and sub-projects"#Work & #Q1"# In both projects (intersection)# Search filters"search: meeting"# Content contains "meeting"# Assignee filters"assigned to: me"# Assigned to current user"assigned to: John"# Assigned to John"assigned by: me"# Assigned by current user# Combined filters"today & @work"# Due today with work label"(today | overdue) & p1"# Today or overdue AND priority 1"#Work & !@done"# In Work project without done label
#!/usr/bin/env python3"""daily_report.py - Generate daily task report"""from todoist_api_python import TodoistAPI
from datetime import datetime
import os
api = TodoistAPI(os.environ["TODOIST_API_KEY"])
defgenerate_daily_report():
"""Generate a daily task report"""
today = datetime.now().strftime("%Y-%m-%d")
# Get tasks for different filters
overdue = api.get_tasks(filter="overdue")
due_today = api.get_tasks(filter="today")
high_priority = api.get_tasks(filter="(p1 | p2) & !today & !overdue")
# Group today's tasks by project
projects = {}
for task in due_today:
project_id = task.project_id
if project_id notin projects:
try:
project = api.get_project(project_id)
projects[project_id] = {"name": project.name, "tasks": []}
except:
projects[project_id] = {"name": "Unknown", "tasks": []}
projects[project_id]["tasks"].append(task)
# Generate report
report = f"""# Daily Task Report - {today}
## Summary
- Overdue: {len(overdue)}
- Due Today: {len(due_today)}
- High Priority (upcoming): {len(high_priority)}
## Overdue Tasks
"""for task in overdue:
report += f"- [{priority_emoji(task.priority)}] {task.content} (Due: {task.due.string})\n"
report += "\n## Today's Tasks by Project\n"for project_id, project_data in projects.items():
report += f"\n### {project_data['name']}\n"for task in project_data["tasks"]:
report += f"- [{priority_emoji(task.priority)}] {task.content}\n"
report += "\n## High Priority (Upcoming)\n"for task in high_priority[:5]:
due = task.due.string if task.due else"No date"
report += f"- [{priority_emoji(task.priority)}] {task.content} (Due: {due})\n"return report
defpriority_emoji(priority):
"""Convert priority number to visual indicator"""return {4: "!", 3: "*", 2: "-", 1: " "}.get(priority, " ")
if __name__ == "__main__":
report = generate_daily_report()
print(report)
# Optionally save to file
filename = f"daily_report_{datetime.now().strftime('%Y-%m-%d')}.md"withopen(filename, "w") as f:
f.write(report)
print(f"\nReport saved to: {filename}")
Integration Examples
Integration with Slack
#!/usr/bin/env python3"""slack_todoist.py - Post Todoist tasks to Slack"""import os
import requests
from todoist_api_python import TodoistAPI
TODOIST_API_KEY = os.environ["TODOIST_API_KEY"]
SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"]
api = TodoistAPI(TODOIST_API_KEY)
defpost_daily_tasks_to_slack():
"""Post today's tasks to Slack"""
tasks = api.get_tasks(filter="today")
ifnot tasks:
message = "No tasks due today!"else:
task_list = "\n".join([f"- {t.content}"for t in tasks])
message = f"*Tasks for Today ({len(tasks)}):*\n{task_list}"
payload = {
"text": message,
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": message
}
}
]
}
response = requests.post(SLACK_WEBHOOK_URL, json=payload)
return response.status_code == 200if __name__ == "__main__":
if post_daily_tasks_to_slack():
print("Posted to Slack successfully")
else:
print("Failed to post to Slack")
Integration with Calendar (Google Calendar)
#!/usr/bin/env python3"""calendar_sync.py - Sync Todoist tasks with Google Calendar"""from todoist_api_python import TodoistAPI
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from datetime import datetime, timedelta
import os
api = TodoistAPI(os.environ["TODOIST_API_KEY"])
defsync_tasks_to_calendar():
"""Sync tasks with due dates to Google Calendar"""
creds = Credentials.from_authorized_user_file("token.json")
service = build("calendar", "v3", credentials=creds)
# Get tasks with due dates in next 7 days
tasks = api.get_tasks(filter="next 7 days")
for task in tasks:
ifnot task.due:
continue# Check if event already exists
existing = find_existing_event(service, task.id)
if existing:
continue# Create calendar event
event = {
"summary": task.content,
"description": f"Todoist Task ID: {task.id}\nPriority: {task.priority}",
"start": {
"date": task.due.date,
},
"end": {
"date": task.due.date,
},
"extendedProperties": {
"private": {
"todoist_id": task.id
}
}
}
service.events().insert(calendarId="primary", body=event).execute()
print(f"Created calendar event: {task.content}")
deffind_existing_event(service, todoist_id):
"""Find existing calendar event for Todoist task"""
events = service.events().list(
calendarId="primary",
privateExtendedProperty=f"todoist_id={todoist_id}"
).execute()
return events.get("items", [])
Best Practices
1. Rate Limiting
import time
from functools import wraps
defrate_limit(calls_per_minute=50):
"""Decorator to rate limit API calls"""
min_interval = 60.0 / calls_per_minute
last_called = [0.0]
defdecorator(func):
@wraps(func)defwrapper(*args, **kwargs):
elapsed = time.time() - last_called[0]
wait_time = min_interval - elapsed
if wait_time > 0:
time.sleep(wait_time)
result = func(*args, **kwargs)
last_called[0] = time.time()
return result
return wrapper
return decorator
@rate_limit(calls_per_minute=50)defapi_call(func, *args, **kwargs):
return func(*args, **kwargs)
2. Error Handling
from todoist_api_python import TodoistAPI
import requests
defsafe_api_call(func, *args, max_retries=3, **kwargs):
"""Execute API call with retry logic"""for attempt inrange(max_retries):
try:
return func(*args, **kwargs)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
# Rate limited
wait_time = int(e.response.headers.get("Retry-After", 60))
print(f"Rate limited. Waiting {wait_time}s...")
time.sleep(wait_time)
elif e.response.status_code >= 500:
# Server error, retry
time.sleep(2 ** attempt)
else:
raiseexcept requests.exceptions.ConnectionError:
time.sleep(2 ** attempt)
raise Exception(f"Failed after {max_retries} retries")
3. Batch Operations
defbatch_create_tasks(tasks, batch_size=50):
"""Create tasks in batches to avoid rate limits"""
results = []
for i inrange(0, len(tasks), batch_size):
batch = tasks[i:i + batch_size]
batch_results = sync_batch_add(batch)
results.extend(batch_results)
if i + batch_size < len(tasks):
time.sleep(1) # Brief pause between batchesreturn results
4. Caching
import json
from pathlib import Path
from datetime import datetime, timedelta
CACHE_DIR = Path.home() / ".cache" / "todoist"
CACHE_TTL = timedelta(minutes=5)
defget_cached_or_fetch(key, fetch_func, ttl=CACHE_TTL):
"""Get from cache or fetch fresh data"""
CACHE_DIR.mkdir(parents=True, exist_ok=True)
cache_file = CACHE_DIR / f"{key}.json"if cache_file.exists():
data = json.loads(cache_file.read_text())
cached_at = datetime.fromisoformat(data["cached_at"])
if datetime.now() - cached_at < ttl:
return data["value"]
value = fetch_func()
cache_data = {
"cached_at": datetime.now().isoformat(),
"value": value
}
cache_file.write_text(json.dumps(cache_data, default=str))
return value
Troubleshooting
Common Issues
Issue: 401 Unauthorized
# Verify your API token
curl -s -X GET "https://api.todoist.com/rest/v2/projects" \
-H "Authorization: Bearer $TODOIST_API_KEY"# Check if token is set correctly
echo $TODOIST_API_KEY
# Regenerate token at:# https://todoist.com/app/settings/integrations/developer
Issue: 429 Too Many Requests
# Implement exponential backoffimport time
defretry_with_backoff(func, max_retries=5):
for i inrange(max_retries):
try:
return func()
except Exception as e:
if"429"instr(e):
wait = 2 ** i
print(f"Rate limited, waiting {wait}s")
time.sleep(wait)
else:
raise
Issue: Task not appearing
# Check if task was created in different project
all_tasks = api.get_tasks()
for task in all_tasks:
if"keyword"in task.content.lower():
print(f"Found: {task.content} in project {task.project_id}")
Issue: Due dates not parsing
# Use explicit date format
api.add_task(
content="Test task",
due_date="2025-01-20"# ISO format
)
# Or use due_datetime for specific time
api.add_task(
content="Test task",
due_datetime="2025-01-20T14:00:00Z"# ISO with time
)
Version History
Version
Date
Changes
1.0.0
2025-01-17
Initial release with comprehensive Todoist API coverage