This skill provides procedural guidance for working with the Todoist REST API v2 via curl and jq.
Authentication
Token Resolution
Resolve the API token in this order:
-
Check environment variable
TODOIST_API_TOKEN -
Check if the user has provided a token in the conversation context
-
If neither is available, use AskUserQuestion (or similar tool) to request the token from the user
To verify a token exists in the environment:
[ -n "$TODOIST_API_TOKEN" ] && echo "Token available" || echo "Token not set"
Making Authenticated Requests
All requests require the Authorization header with Bearer token:
curl -s -H "Authorization: Bearer $TODOIST_API_TOKEN" \
"https://api.todoist.com/rest/v2/ENDPOINT"
For POST requests with JSON body, include Content-Type:
curl -s -X POST \
-H "Authorization: Bearer $TODOIST_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"key": "value"}' \
"https://api.todoist.com/rest/v2/ENDPOINT"
Base URL
All REST API v2 endpoints use: https://api.todoist.com/rest/v2/
Confirmation Requirement
Before executing any destructive action (DELETE, close, update, archive), always ask the user for confirmation using AskUserQuestion or similar tool. A single confirmation suffices for a logical group of related actions.
Destructive actions include:
-
Deleting tasks, projects, sections, labels, or comments
-
Closing (completing) tasks
-
Updating existing resources
-
Archiving projects
Read-only operations (GET requests) do not require confirmation.
Endpoints Reference
Tasks
| List active tasks
| GET
| /tasks
| Get task
| GET
| /tasks/{id}
| Create task
| POST
| /tasks
| Update task
| POST
| /tasks/{id}
| Close task
| POST
| /tasks/{id}/close
| Reopen task
| POST
| /tasks/{id}/reopen
| Delete task
| DELETE
| /tasks/{id}
Task filters (query params for GET /tasks):
-
project_id- Filter by project -
section_id- Filter by section -
label- Filter by label name -
filter- Todoist filter query (e.g., "today", "overdue")
Task creation/update fields:
-
content(required for creation) - Task text -
description- Additional details -
project_id,section_id,parent_id- Organisation -
priority- 1 (normal) to 4 (urgent) -
due_string- Natural language ("tomorrow", "every monday") -
due_date- YYYY-MM-DD format -
due_datetime- RFC3339 format -
labels- Array of label names -
assignee_id- For shared projects -
duration,duration_unit- Estimated time
Projects
| List projects
| GET
| /projects
| Get project
| GET
| /projects/{id}
| Create project
| POST
| /projects
| Update project
| POST
| /projects/{id}
| Archive project
| POST
| /projects/{id}/archive
| Unarchive project
| POST
| /projects/{id}/unarchive
| Delete project
| DELETE
| /projects/{id}
| List collaborators
| GET
| /projects/{id}/collaborators
Project fields:
-
name(required for creation) -
parent_id- For nested projects -
color- Colour name (e.g., "berry_red", "blue") -
is_favorite- Boolean -
view_style- "list" or "board"
Sections
| List sections
| GET
| /sections
| Get section
| GET
| /sections/{id}
| Create section
| POST
| /sections
| Update section
| POST
| /sections/{id}
| Delete section
| DELETE
| /sections/{id}
Section filters (query params for GET):
project_id- Filter by project (recommended)
Section fields:
-
name(required) -
project_id(required for creation) -
order- Position within project
Labels
| List personal labels
| GET
| /labels
| Get label
| GET
| /labels/{id}
| Create label
| POST
| /labels
| Update label
| POST
| /labels/{id}
| Delete label
| DELETE
| /labels/{id}
| List shared labels
| GET
| /shared_labels
| Rename shared label
| POST
| /shared_labels/{name}/rename
| Remove shared label
| DELETE
| /shared_labels/{name}
Label fields:
-
name(required) -
color- Colour name -
order- Display order -
is_favorite- Boolean
Comments
| List comments
| GET
| /comments
| Get comment
| GET
| /comments/{id}
| Create comment
| POST
| /comments
| Update comment
| POST
| /comments/{id}
| Delete comment
| DELETE
| /comments/{id}
Comment filters (query params for GET):
-
task_id- Comments on a task (required if no project_id) -
project_id- Comments on a project (required if no task_id)
Comment fields:
-
content(required) - Markdown supported -
task_idorproject_id(one required for creation)
Pagination
Some endpoints return paginated results. Handle pagination by checking for a next_cursor field in the response and making subsequent requests with the cursor parameter.
Pagination Pattern
# Initial request
response=$(curl -s -H "Authorization: Bearer $TODOIST_API_TOKEN" \
"https://api.todoist.com/rest/v2/ENDPOINT")
# Check for more results
next_cursor=$(echo "$response" | jq -r '.next_cursor // empty')
# If cursor exists, fetch next page
if [ -n "$next_cursor" ]; then
curl -s -H "Authorization: Bearer $TODOIST_API_TOKEN" \
"https://api.todoist.com/rest/v2/ENDPOINT?cursor=$next_cursor"
fi
Complete Data Retrieval Loop
To retrieve all data when pagination is involved:
all_results="[]"
cursor=""
while true; do
if [ -z "$cursor" ]; then
response=$(curl -s -H "Authorization: Bearer $TODOIST_API_TOKEN" \
"https://api.todoist.com/rest/v2/ENDPOINT")
else
response=$(curl -s -H "Authorization: Bearer $TODOIST_API_TOKEN" \
"https://api.todoist.com/rest/v2/ENDPOINT?cursor=$cursor")
fi
# Merge results (adjust .items or root array based on endpoint)
items=$(echo "$response" | jq '.items // .')
all_results=$(echo "$all_results $items" | jq -s 'add')
# Check for next page
cursor=$(echo "$response" | jq -r '.next_cursor // empty')
has_more=$(echo "$response" | jq -r '.has_more // false')
if [ "$has_more" != "true" ] || [ -z "$cursor" ]; then
break
fi
done
echo "$all_results"
Common Patterns
List All Tasks in a Project
curl -s -H "Authorization: Bearer $TODOIST_API_TOKEN" \
"https://api.todoist.com/rest/v2/tasks?project_id=PROJECT_ID" | jq '.'
Create a Task with Due Date
curl -s -X POST \
-H "Authorization: Bearer $TODOIST_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content": "Task name", "due_string": "tomorrow", "priority": 2}' \
"https://api.todoist.com/rest/v2/tasks"
Complete a Task
curl -s -X POST \
-H "Authorization: Bearer $TODOIST_API_TOKEN" \
"https://api.todoist.com/rest/v2/tasks/TASK_ID/close"
Get Today's Tasks
curl -s -H "Authorization: Bearer $TODOIST_API_TOKEN" \
"https://api.todoist.com/rest/v2/tasks?filter=today" | jq '.'
Get Overdue Tasks
curl -s -H "Authorization: Bearer $TODOIST_API_TOKEN" \
"https://api.todoist.com/rest/v2/tasks?filter=overdue" | jq '.'
Error Handling
Check HTTP status codes and handle errors appropriately:
-
200- Success with response body -
204- Success, no content -
400- Bad request (check parameters) -
401- Authentication failed (check token) -
403- Forbidden (insufficient permissions) -
404- Resource not found -
429- Rate limited (wait and retry) -
5xx- Server error (safe to retry)
Example with Error Handling
response=$(curl -s -w "\n%{http_code}" \
-H "Authorization: Bearer $TODOIST_API_TOKEN" \
"https://api.todoist.com/rest/v2/tasks")
http_code=$(echo "$response" | tail -1)
body=$(echo "$response" | sed '$d')
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
echo "$body" | jq '.'
else
echo "Error: HTTP $http_code"
echo "$body"
fi
Idempotency
For safe retries on write operations, include the X-Request-Id header (max 36 characters):
curl -s -X POST \
-H "Authorization: Bearer $TODOIST_API_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Request-Id: $(uuidgen)" \
-d '{"content": "New task"}' \
"https://api.todoist.com/rest/v2/tasks"
Duplicate requests with the same X-Request-Id are discarded by the server.
Completed Tasks
The REST API v2 /tasks endpoint returns only active tasks. For completed tasks, use the Sync API or the newer unified API v1 endpoints:
-
GET /tasks/completed/by_completion_date- Retrieve by completion date -
GET /tasks/completed/by_due_date- Retrieve by original due date
See references/completed-tasks.md for details on retrieving completed task history.
Additional Reference
For detailed information on specific topics, consult:
-
references/completed-tasks.md- Retrieving completed task history -
references/filters.md- Todoist filter query syntax
Workflow Summary
-
Resolve token - Environment, context, or ask user
-
Verify authentication - Test with a simple GET request
-
Read operations - Execute directly without confirmation
-
Write operations - Ask for confirmation before executing
-
Handle pagination - Loop with cursor for complete data
-
Parse responses - Use jq to extract and format data