Todoist
Access the Todoist API v1 with managed OAuth authentication. Manage tasks, projects, sections, labels, and comments.
Quick Start
# List all tasks
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/todoist/api/v1/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/api/v1/{resource}
The gateway proxies requests to api.todoist.com/api/v1 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:
export MATON_API_KEY="YOUR_API_KEY"
Getting Your API Key
- Sign in or create an account at maton.ai
- Go to maton.ai/settings
- Copy your API key
Connection Management
Manage your Todoist OAuth connections at https://ctrl.maton.ai.
List Connections
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
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
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
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:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/todoist/api/v1/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
GET /todoist/api/v1/projects
Response:
json
{
"results": [
{
"id": "6fwFRqmVCFvWVX5R",
"name": "Inbox",
"color": "charcoal",
"parent_id": null,
"child_order": 0,
"is_shared": false,
"is_favorite": false,
"inbox_project": true,
"view_style": "list",
"description": "",
"is_archived": false
}
],
"next_cursor": null
}
Get Project
GET /todoist/api/v1/projects/{id}
Create Project
POST /todoist/api/v1/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/api/v1/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
POST /todoist/api/v1/projects/{id}
Content-Type: application/json
{
"name": "Updated Project Name",
"color": "red"
}
Delete Project
DELETE /todoist/api/v1/projects/{id}
Returns 204 No Content on success.
Get Project Collaborators
GET /todoist/api/v1/projects/{id}/collaborators
Tasks
List Tasks
GET /todoist/api/v1/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
{
"results": [
{
"id": "6fwhG9wMHr4wxgpR",
"content": "Buy groceries",
"description": "",
"project_id": "6fwFRqmVCFvWVX5R",
"section_id": null,
"parent_id": null,
"child_order": 1,
"priority": 2,
"checked": false,
"labels": [],
"due": {
"date": "2026-02-07T10:00:00",
"string": "tomorrow at 10am",
"lang": "en",
"is_recurring": false
},
"added_at": "2026-02-06T20:41:08.449320Z"
}
],
"next_cursor": null
}
Get Task
GET /todoist/api/v1/tasks/{id}
Create Task
POST /todoist/api/v1/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/api/v1/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
POST /todoist/api/v1/tasks/{id}
Content-Type: application/json
{
"content": "Updated task content",
"priority": 3
}
Close Task (Complete)
POST /todoist/api/v1/tasks/{id}/close
Returns 204 No Content. For recurring tasks, this schedules the next occurrence.
Reopen Task
POST /todoist/api/v1/tasks/{id}/reopen
Returns 204 No Content.
Delete Task
DELETE /todoist/api/v1/tasks/{id}
Returns 204 No Content.
Sections
List Sections
GET /todoist/api/v1/sections
GET /todoist/api/v1/sections?project_id={project_id}
Response:
json
{
"results": [
{
"id": "6g424m6CQm47v7mm",
"project_id": "6g424jv8X52hP7qF",
"section_order": 1,
"name": "To Do",
"added_at": "2026-02-20T22:25:04.203675Z",
"is_archived": false,
"is_collapsed": false
}
],
"next_cursor": null
}
Get Section
GET /todoist/api/v1/sections/{id}
Create Section
POST /todoist/api/v1/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
POST /todoist/api/v1/sections/{id}
Content-Type: application/json
{
"name": "Updated Section Name"
}
Delete Section
DELETE /todoist/api/v1/sections/{id}
Returns 204 No Content.
Labels
List Labels
GET /todoist/api/v1/labels
Response:
json
{
"results": [
{
"id": "2182980313",
"name": "urgent",
"color": "red",
"order": 1,
"is_favorite": false
}
],
"next_cursor": null
}
Get Label
GET /todoist/api/v1/labels/{id}
Create Label
POST /todoist/api/v1/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
POST /todoist/api/v1/labels/{id}
Content-Type: application/json
{
"name": "updated-label",
"color": "green"
}
Delete Label
DELETE /todoist/api/v1/labels/{id}
Returns 204 No Content.
Comments
List Comments
GET /todoist/api/v1/comments?task_id={task_id}
GET /todoist/api/v1/comments?project_id={project_id}
Note: Either task_id or project_id is required.
Response:
json
{
"results": [
{
"id": "6g424pWVXPpwW7hR",
"item_id": "6g424pQr2xfCcFr2",
"content": "This is a comment",
"posted_at": "2026-02-20T22:25:20.045703Z",
"posted_uid": "57402826",
"file_attachment": null,
"reactions": null
}
],
"next_cursor": null
}
Get Comment
GET /todoist/api/v1/comments/{id}
Create Comment
POST /todoist/api/v1/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
POST /todoist/api/v1/comments/{id}
Content-Type: application/json
{
"content": "Updated comment text"
}
Delete Comment
DELETE /todoist/api/v1/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
// Create a task
const response = await fetch('https://gateway.maton.ai/todoist/api/v1/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
import os
import requests
# Create a task
response = requests.post(
'https://gateway.maton.ai/todoist/api/v1/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
jqor other commands, environment variables like$MATON_API_KEYmay 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
- Check that the
MATON_API_KEYenvironment variable is set:
echo $MATON_API_KEY
- Verify the API key is valid by listing connections:
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
- Ensure your URL path starts with
todoist. For example:
- Correct:
https://gateway.maton.ai/todoist/api/v1/tasks - Incorrect:
https://gateway.maton.ai/api/v1/tasks