todoist-api
Scannednpx machina-cli add skill aneym/agent-skills/todoist-api --openclawTodoist API Skill
Interact with the Todoist REST API v2 via curl and jq.
Authentication
Resolve the API token in order:
- Environment variable
TODOIST_API_TOKEN - User-provided token in conversation
- Ask the user (token is at: Todoist Settings → Integrations → Developer)
[ -n "$TODOIST_API_TOKEN" ] && echo "Token available" || echo "Token not set"
All requests use Bearer auth:
curl -s -H "Authorization: Bearer $TODOIST_API_TOKEN" \
"https://api.todoist.com/rest/v2/ENDPOINT"
POST requests add 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
https://api.todoist.com/rest/v2/
Confirmation Requirement
Before executing any destructive action (DELETE, close, update, archive), ask the user for confirmation. A single confirmation covers a logical group of related actions.
Destructive: delete, close/complete, update, archive. Read-only (GET): no confirmation needed.
Priority Mapping
⚠️ The API and UI use inverted priority numbers:
| UI Label | API priority field | Filter syntax |
|---|---|---|
| P1 (urgent, red) | 4 | p1 |
| P2 (high, orange) | 3 | p2 |
| P3 (medium, blue) | 2 | p3 |
| P4 (normal, none) | 1 | p4 |
When creating/updating tasks, use the API value (4 = urgent).
When using filter queries, use the UI label (p1 = urgent).
Task Links (v2 IDs)
⚠️ The REST API url field uses deprecated numeric IDs. Todoist deprecated /app/task/<numeric_id> URLs (end of 2025). The new format uses alphanumeric v2 IDs.
New URL format: https://app.todoist.com/app/task/<v2_id>
The REST API v2 does NOT return v2_id. Use the Sync API v9 to get it:
Get v2_id for a single task
curl -s -X POST \
-H "Authorization: Bearer $TODOIST_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"item_id": "TASK_ID"}' \
"https://api.todoist.com/sync/v9/items/get" | jq '.item.v2_id'
Works with both numeric IDs and v2_ids as input.
Get v2_ids for all tasks (bulk)
curl -s -X POST \
-H "Authorization: Bearer $TODOIST_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"sync_token": "*", "resource_types": ["items"]}' \
"https://api.todoist.com/sync/v9/sync" | jq '[.items[] | {id, v2_id, content}]'
Build a task link
# After getting v2_id from Sync API:
TASK_ID="9971910530"
V2_ID=$(curl -s -X POST \
-H "Authorization: Bearer $TODOIST_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"item_id\": \"$TASK_ID\"}" \
"https://api.todoist.com/sync/v9/items/get" | jq -r '.item.v2_id')
echo "https://app.todoist.com/app/task/$V2_ID"
Efficient bulk pattern (REST + Sync combo)
When listing multiple tasks, do ONE Sync call for the v2_id map instead of N individual lookups:
# 1. Get tasks via REST API (for filtering)
TASKS=$(curl -s -H "Authorization: Bearer $TODOIST_API_TOKEN" \
"https://api.todoist.com/rest/v2/tasks?filter=today")
# 2. Build v2_id lookup from Sync API (one call)
V2_MAP=$(curl -s -X POST \
-H "Authorization: Bearer $TODOIST_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"sync_token": "*", "resource_types": ["items"]}' \
"https://api.todoist.com/sync/v9/sync" | jq '[.items[] | {(.id): .v2_id}] | add')
# 3. Merge: replace URLs with v2 format
echo "$TASKS" | jq --argjson map "$V2_MAP" '
[.[] | . + {link: "https://app.todoist.com/app/task/\($map[.id])"}]
'
When presenting task links to the user, always use v2_id URLs. Never use the url field from the REST API directly.
Task Response Object
The REST API returns tasks with this structure:
{
"id": "123456789",
"content": "Task name",
"description": "Additional details",
"comment_count": 0,
"is_completed": false,
"order": 1,
"priority": 1,
"project_id": "987654321",
"section_id": null,
"parent_id": null,
"labels": [],
"creator_id": "111",
"created_at": "2024-01-15T10:30:00.000000Z",
"assignee_id": null,
"assigner_id": null,
"url": "https://app.todoist.com/app/task/123456789",
"duration": null,
"deadline": null,
"due": {
"date": "2024-01-20",
"string": "every monday",
"lang": "en",
"is_recurring": true
}
}
Note: The url field uses deprecated numeric IDs. See "Task Links (v2 IDs)" above for the correct URL format.
The due object
| Field | Type | Description |
|---|---|---|
date | string | YYYY-MM-DD or RFC3339 datetime |
string | string | Human-readable recurrence/date text |
lang | string | Language code |
is_recurring | boolean | Whether the task recurs |
due is null if no due date is set.
The deadline field
A hard deadline separate from due. When both are set, due is the soft/scheduled date and deadline is the hard cutoff. Can be set via deadline_date (YYYY-MM-DD) or deadline_datetime (RFC3339) when creating/updating tasks.
Endpoints Reference
Tasks
| Operation | Method | Endpoint |
|---|---|---|
| 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 projectsection_id— filter by sectionlabel— filter by label namefilter— Todoist filter query (e.g.today,overdue). Seereferences/filters.md
Task creation/update fields:
content(required for creation) — task textdescription— additional detailsproject_id,section_id,parent_id— organizationpriority— 1 (normal) to 4 (urgent). See Priority Mapping abovedue_string— natural language ("tomorrow", "every monday")due_date— YYYY-MM-DDdue_datetime— RFC3339deadline_date— YYYY-MM-DD hard deadlinedeadline_datetime— RFC3339 hard deadlinelabels— array of label namesassignee_id— for shared projectsduration,duration_unit— estimated time
Projects
| Operation | Method | Endpoint |
|---|---|---|
| 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), parent_id, color (e.g. "berry_red", "blue"), is_favorite, view_style ("list" or "board")
Sections
| Operation | Method | Endpoint |
|---|---|---|
| List sections | GET | /sections |
| Get section | GET | /sections/{id} |
| Create section | POST | /sections |
| Update section | POST | /sections/{id} |
| Delete section | DELETE | /sections/{id} |
Section fields: name (required), project_id (required for creation), order
Labels
| Operation | Method | Endpoint |
|---|---|---|
| List personal labels | GET | /labels |
| Get label | GET | /labels/{id} |
| Create label | POST | /labels |
| Update label | POST | /labels/{id} |
| Delete label | DELETE | /labels/{id} |
Label fields: name (required), color, order, is_favorite
Comments
| Operation | Method | Endpoint |
|---|---|---|
| List comments | GET | /comments |
| Get comment | GET | /comments/{id} |
| Create comment | POST | /comments |
| Update comment | POST | /comments/{id} |
| Delete comment | DELETE | /comments/{id} |
Comment query params: task_id or project_id (one required for listing)
Comment fields: content (required, markdown supported), task_id or project_id (one required for creation)
Pagination
Most REST v2 endpoints (tasks, projects, sections, labels) return flat JSON arrays — no pagination needed.
Pagination with cursors applies only to specific endpoints like completed tasks (API v1). See references/completed-tasks.md for the cursor-based pagination pattern.
Common Patterns
Create a task with due date
curl -s -X POST \
-H "Authorization: Bearer $TODOIST_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content": "Buy milk", "due_string": "tomorrow", "priority": 4}' \
"https://api.todoist.com/rest/v2/tasks"
Note: priority: 4 = urgent (P1 in UI).
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 '.'
Complete a task
curl -s -X POST \
-H "Authorization: Bearer $TODOIST_API_TOKEN" \
"https://api.todoist.com/rest/v2/tasks/TASK_ID/close"
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 '.'
Error Handling
| Code | Meaning |
|---|---|
| 200 | Success with body |
| 204 | Success, no content |
| 400 | Bad request |
| 401 | Auth failed |
| 403 | Forbidden |
| 404 | Not found |
| 429 | Rate limited (wait + retry) |
| 5xx | Server error (safe to retry) |
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 writes, include X-Request-Id (max 36 chars):
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"
Completed Tasks
REST v2 /tasks returns only active tasks. For completed task history, see references/completed-tasks.md.
Additional Reference
references/completed-tasks.md— completed task history (API v1 endpoints, cursor pagination)references/filters.md— Todoist filter query syntax
Source
git clone https://github.com/aneym/agent-skills/blob/main/skills/todoist-api/SKILL.mdView on GitHub Overview
This skill lets you read, create, update, and delete Todoist data via the REST API v2 using curl and jq. It covers authentication, CRUD operations, filter queries, pagination, completed task history, and natural language due dates. Use it when you want programmatic access to tasks, projects, sections, labels, and comments.
How This Skill Works
Resolve the API token from the environment or user input, then authenticate all requests with a Bearer token to the Todoist REST v2 base URL. The examples use curl with JSON payloads and jq for parsing. Destructive actions require a user confirmation before proceeding.
When to Use It
- Read or list Todoist data (tasks, projects, sections, labels, comments) via GET endpoints.
- Create new Todoist items such as tasks, projects, sections, labels, or comments via POST endpoints.
- Update existing items (e.g., due dates, priorities, completion status) via PUT/PATCH endpoints.
- Delete, close, or archive items, with a user confirmation prompt before proceeding.
- Filter lists, paginate results, and work with completed task history and natural language due dates.
Quick Start
- Step 1: Ensure the Todoist API token is available via the environment variable TODOIST_API_TOKEN or provided by the user.
- Step 2: List tasks to verify connectivity: curl -s -H "Authorization: Bearer $TODOIST_API_TOKEN" "https://api.todoist.com/rest/v2/tasks"
- Step 3: For destructive actions, confirm with the user before executing (delete/close/update/archive) and perform the action with the appropriate REST endpoint.
Best Practices
- Resolve and store the API token securely (prefer the TODOIST_API_TOKEN environment variable) and avoid hardcoding.
- Always use Bearer authentication and include Content-Type: application/json for POST/PUT payloads.
- Before destructive actions (delete, close, update, archive), ask the user for confirmation; one confirmation covers a logical group of actions.
- When IDs are needed, map to v2_ids using the Sync API (v9) to build stable task links.
- Honor inverted priority mapping for UI (p1 = 4 in API) and use UI labels (p1, p2, etc.) when constructing filters.
Example Use Cases
- Fetch all tasks due today: GET https://api.todoist.com/rest/v2/tasks?filter=today
- Create a new task with a natural language due date: POST /tasks with {content, due_string} in the body
- Update a task's due date and priority: PUT /tasks/{id} with updated fields
- Delete a task after user confirmation: DELETE /tasks/{id}
- Obtain a v2_id for a task via the Sync API and link to the app: POST /sync/v9/items/get and build https://app.todoist.com/app/task/<v2_id>