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])