ClawSkills logoClawSkills

Todoist

Todoist API integration with managed OAuth. Manage tasks, projects, sections, labels, and comments. Use this skill when users want to create, update, complete,

Introduction

# Todoist

Access the Todoist REST API v2 with managed OAuth authentication. Manage tasks, projects, sections, labels, and comments.

## Quick Start

```bash # List all tasks python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://gateway.maton.ai/todoist/rest/v2/tasks') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

## Base URL

``` https://gateway.maton.ai/todoist/rest/v2/{resource} ```

The gateway proxies requests to `api.todoist.com/rest/v2` and automatically injects your OAuth token.

## Authentication

All requests require the Maton API key in the Authorization header:

``` Authorization: Bearer $MATON_API_KEY ```

**Environment Variable:** Set your API key as `MATON_API_KEY`:

```bash export MATON_API_KEY="YOUR_API_KEY" ```

### Getting Your API Key

1. Sign in or create an account at [maton.ai](https://maton.ai) 2. Go to [maton.ai/settings](https://maton.ai/settings) 3. Copy your API key

## Connection Management

Manage your Todoist OAuth connections at `https://ctrl.maton.ai`.

### List Connections

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections?app=todoist&status=ACTIVE') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

### Create Connection

```bash python <<'EOF' import urllib.request, os, json data = json.dumps({'app': 'todoist'}).encode() req = urllib.request.Request('https://ctrl.maton.ai/connections', data=data, method='POST') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('Content-Type', 'application/json') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

### Get Connection

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

**Response:** ```json { "connection": { "connection_id": "21fd90f9-5935-43cd-b6c8-bde9d915ca80", "status": "ACTIVE", "creation_time": "2025-12-08T07:20:53.488460Z", "last_updated_time": "2026-01-31T20:03:32.593153Z", "url": "https://connect.maton.ai/?session_token=...", "app": "todoist", "metadata": {} } } ```

Open the returned `url` in a browser to complete OAuth authorization.

### Delete Connection

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}', method='DELETE') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

### Specifying Connection

If you have multiple Todoist connections, specify which one to use with the `Maton-Connection` header:

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://gateway.maton.ai/todoist/rest/v2/tasks') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('Maton-Connection', '21fd90f9-5935-43cd-b6c8-bde9d915ca80') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

If omitted, the gateway uses the default (oldest) active connection.

## API Reference

### Projects

#### List Projects

```bash GET /todoist/rest/v2/projects ```

**Response:** ```json [ { "id": "2366738772", "name": "Inbox", "color": "charcoal", "parent_id": null, "order": 0, "is_shared": false, "is_favorite": false, "is_inbox_project": true, "view_style": "list", "url": "https://app.todoist.com/app/project/..." } ] ```

#### Get Project

```bash GET /todoist/rest/v2/projects/{id} ```

#### Create Project

```bash POST /todoist/rest/v2/projects Content-Type: application/json

{ "name": "My Project", "color": "blue", "is_favorite": true, "view_style": "board" } ```

**Parameters:** - `name` (required) - Project name - `parent_id` - Parent project ID for nesting - `color` - Project color (e.g., "red", "blue", "green") - `is_favorite` - Boolean favorite status - `view_style` - "list" or "board" (default: list)

**Example:** ```bash python <<'EOF' import urllib.request, os, json data = json.dumps({'name': 'My New Project', 'color': 'blue'}).encode() req = urllib.request.Request('https://gateway.maton.ai/todoist/rest/v2/projects', data=data, method='POST') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('Content-Type', 'application/json') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

#### Update Project

```bash POST /todoist/rest/v2/projects/{id} Content-Type: application/json

{ "name": "Updated Project Name", "color": "red" } ```

#### Delete Project

```bash DELETE /todoist/rest/v2/projects/{id} ```

Returns 204 No Content on success.

#### Get Project Collaborators

```bash GET /todoist/rest/v2/projects/{id}/collaborators ```

### Tasks

#### List Tasks

```bash GET /todoist/rest/v2/tasks ```

**Query Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `project_id` | string | Filter by project | | `section_id` | string | Filter by section | | `label` | string | Filter by label name | | `filter` | string | Todoist filter expression | | `ids` | string | Comma-separated task IDs |

**Response:** ```json [ { "id": "9993408170", "content": "Buy groceries", "description": "", "project_id": "2366834771", "section_id": null, "parent_id": null, "order": 1, "priority": 2, "is_completed": false, "labels": [], "due": { "date": "2026-02-07", "string": "tomorrow", "lang": "en", "is_recurring": false }, "url": "https://app.todoist.com/app/task/9993408170", "comment_count": 0, "created_at": "2026-02-06T20:41:08.449320Z" } ] ```

#### Get Task

```bash GET /todoist/rest/v2/tasks/{id} ```

#### Create Task

```bash POST /todoist/rest/v2/tasks Content-Type: application/json

{ "content": "Buy groceries", "project_id": "2366834771", "priority": 2, "due_string": "tomorrow at 10am", "labels": ["shopping", "errands"] } ```

**Required Fields:** - `content` - Task content/title

**Optional Fields:** - `description` - Task description - `project_id` - Project to add task to (defaults to Inbox) - `section_id` - Section within project - `parent_id` - Parent task ID for subtasks - `labels` - Array of label names - `priority` - 1 (normal) to 4 (urgent) - `due_string` - Natural language due date ("tomorrow", "next Monday 3pm") - `due_date` - ISO format YYYY-MM-DD - `due_datetime` - RFC3339 format with timezone - `assignee_id` - User ID to assign task - `duration` - Task duration (integer) - `duration_unit` - "minute" or "day"

**Example:** ```bash python <<'EOF' import urllib.request, os, json data = json.dumps({ 'content': 'Complete project report', 'priority': 4, 'due_string': 'tomorrow at 5pm', 'labels': ['work', 'urgent'] }).encode() req = urllib.request.Request('https://gateway.maton.ai/todoist/rest/v2/tasks', data=data, method='POST') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('Content-Type', 'application/json') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

#### Update Task

```bash POST /todoist/rest/v2/tasks/{id} Content-Type: application/json

{ "content": "Updated task content", "priority": 3 } ```

#### Close Task (Complete)

```bash POST /todoist/rest/v2/tasks/{id}/close ```

Returns 204 No Content. For recurring tasks, this schedules the next occurrence.

#### Reopen Task

```bash POST /todoist/rest/v2/tasks/{id}/reopen ```

Returns 204 No Content.

#### Delete Task

```bash DELETE /todoist/rest/v2/tasks/{id} ```

Returns 204 No Content.

### Sections

#### List Sections

```bash GET /todoist/rest/v2/sections GET /todoist/rest/v2/sections?project_id={project_id} ```

**Response:** ```json [ { "id": "214670251", "project_id": "2366834771", "order": 1, "name": "To Do" } ] ```

#### Get Section

```bash GET /todoist/rest/v2/sections/{id} ```

#### Create Section

```bash POST /todoist/rest/v2/sections Content-Type: application/json

{ "name": "In Progress", "project_id": "2366834771", "order": 2 } ```

**Required Fields:** - `name` - Section name - `project_id` - Parent project ID

#### Update Section

```bash POST /todoist/rest/v2/sections/{id} Content-Type: application/json

{ "name": "Updated Section Name" } ```

#### Delete Section

```bash DELETE /todoist/rest/v2/sections/{id} ```

Returns 204 No Content.

### Labels

#### List Labels

```bash GET /todoist/rest/v2/labels ```

**Response:** ```json [ { "id": "2182980313", "name": "urgent", "color": "red", "order": 1, "is_favorite": false } ] ```

#### Get Label

```bash GET /todoist/rest/v2/labels/{id} ```

#### Create Label

```bash POST /todoist/rest/v2/labels Content-Type: application/json

{ "name": "work", "color": "blue", "is_favorite": true } ```

**Parameters:** - `name` (required) - Label name - `color` - Label color - `order` - Sort order - `is_favorite` - Boolean favorite status

#### Update Label

```bash POST /todoist/rest/v2/labels/{id} Content-Type: application/json

{ "name": "updated-label", "color": "green" } ```

#### Delete Label

```bash DELETE /todoist/rest/v2/labels/{id} ```

Returns 204 No Content.

### Comments

#### List Comments

```bash GET /todoist/rest/v2/comments?task_id={task_id} GET /todoist/rest/v2/comments?project_id={project_id} ```

**Note:** Either `task_id` or `project_id` is required.

**Response:** ```json [ { "id": "3966541561", "task_id": "9993408170", "project_id": null, "content": "This is a comment", "posted_at": "2026-02-06T20:41:35.734376Z", "posted_by_id": "57402826" } ] ```

#### Get Comment

```bash GET /todoist/rest/v2/comments/{id} ```

#### Create Comment

```bash POST /todoist/rest/v2/comments Content-Type: application/json

{ "task_id": "9993408170", "content": "Don't forget to check the budget" } ```

**Required Fields:** - `content` - Comment text - `task_id` OR `project_id` - Where to attach the comment

#### Update Comment

```bash POST /todoist/rest/v2/comments/{id} Content-Type: application/json

{ "content": "Updated comment text" } ```

#### Delete Comment

```bash DELETE /todoist/rest/v2/comments/{id} ```

Returns 204 No Content.

## Priority Values

| Priority | Meaning | |----------|---------| | 1 | Normal (default) | | 2 | Medium | | 3 | High | | 4 | Urgent |

## Due Date Formats

Use ONE of these formats per request:

- `due_string` - Natural language: "tomorrow", "next Monday at 3pm", "every week" - `due_date` - Date only: "2026-02-15" - `due_datetime` - Full datetime: "2026-02-15T14:00:00Z"

## Code Examples

### JavaScript

```javascript // Create a task const response = await fetch('https://gateway.maton.ai/todoist/rest/v2/tasks', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.MATON_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ content: 'Review pull request', priority: 3, due_string: 'today at 5pm' }) }); const task = await response.json(); ```

### Python

```python import os import requests

# Create a task response = requests.post( 'https://gateway.maton.ai/todoist/rest/v2/tasks', headers={'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}, json={ 'content': 'Review pull request', 'priority': 3, 'due_string': 'today at 5pm' } ) task = response.json() ```

## Notes

- Task IDs and Project IDs are strings, not integers - Priority 4 is the highest (urgent), priority 1 is normal - Use only one due date format per request (due_string, due_date, or due_datetime) - Closing a recurring task schedules the next occurrence - The Inbox project cannot be deleted - IMPORTANT: When piping curl output to `jq` or other commands, environment variables like `$MATON_API_KEY` may not expand correctly in some shell environments

## Error Handling

| Status | Meaning | |--------|---------| | 204 | Success (no content) - for close, reopen, delete operations | | 400 | Invalid request or missing Todoist connection | | 401 | Invalid or missing Maton API key | | 404 | Resource not found | | 429 | Rate limited | | 4xx/5xx | Passthrough error from Todoist API |

### Troubleshooting: API Key Issues

1. Check that the `MATON_API_KEY` environment variable is set:

```bash echo $MATON_API_KEY ```

2. Verify the API key is valid by listing connections:

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

### Troubleshooting: Invalid App Name

1. Ensure your URL path starts with `todoist`. For example:

- Correct: `https://gateway.maton.ai/todoist/rest/v2/tasks` - Incorrect: `https://gateway.maton.ai/rest/v2/tasks`

## Resources

- [Todoist REST API v2 Documentation](https://developer.todoist.com/rest/v2) - [Todoist API v1 Documentation](https://developer.todoist.com/api/v1) - [Todoist Filter Syntax](https://todoist.com/help/articles/introduction-to-filters) - [Todoist OAuth Documentation](https://developer.todoist.com/guides/#oauth) - [Maton Community](https://discord.com/invite/dBfFAcefs2) - [Maton Support](mailto:[email protected])

More Products