| Crates.io | todoist-api |
| lib.rs | todoist-api |
| version | 1.0.0-alpha.1 |
| created_at | 2025-08-16 14:02:15.733681+00 |
| updated_at | 2026-01-09 10:11:27.93983+00 |
| description | A Rust wrapper for the Todoist Unified API v1 |
| homepage | |
| repository | https://github.com/romaintb/todoist-api |
| max_upload_size | |
| id | 1798482 |
| size | 972,898 |
A comprehensive Rust wrapper for the Todoist Unified API v1, providing a clean and ergonomic interface for managing tasks, projects, labels, sections, and comments with cursor-based pagination.
Add this to your Cargo.toml:
[dependencies]
todoist-api = "1.0.0-alpha.1"
use todoist_api::TodoistWrapper;
use todoist_api::models::CreateTaskArgs;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new Todoist client
let todoist = TodoistWrapper::new("your-api-token".to_string());
// Get all tasks (returns paginated response)
let response = todoist.get_tasks(None, None).await?;
println!("Found {} tasks", response.results.len());
// Create a new task
let args = CreateTaskArgs {
content: "Buy groceries".to_string(),
project_id: None,
..Default::default()
};
let new_task = todoist.create_task(&args).await?;
println!("Created task: {}", new_task.content);
// Complete a task
todoist.complete_task(&new_task.id).await?;
println!("Task completed!");
Ok(())
}
The library provides comprehensive error handling with specific error types that allow you to handle different scenarios appropriately:
use todoist_api::{TodoistWrapper, TodoistError, TodoistResult};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let todoist = TodoistWrapper::new("your-api-token".to_string());
match todoist.get_projects(None, None).await {
Ok(response) => println!("Found {} projects", response.results.len()),
Err(TodoistError::RateLimited { retry_after, message }) => {
println!("Rate limited: {} (retry after {} seconds)", message, retry_after.unwrap_or(0));
// Handle rate limiting - wait and retry
}
Err(TodoistError::AuthenticationError { message }) => {
println!("Authentication failed: {}", message);
// Handle authentication issues
}
Err(TodoistError::NotFound { resource_type, resource_id, message }) => {
println!("Resource not found: {} (ID: {:?}) - {}", resource_type, resource_id, message);
// Handle missing resources
}
Err(TodoistError::EmptyResponse { endpoint, message }) => {
println!("Empty response from {}: {}", endpoint, message);
// Handle unexpected empty responses
}
Err(e) => println!("Other error: {}", e),
}
Ok(())
}
The library automatically detects rate limiting (HTTP 429) and provides retry information:
use std::time::Duration;
// Handle rate limiting with automatic retry
async fn get_tasks_with_retry(todoist: &TodoistWrapper) -> TodoistResult<PaginatedResponse<Task>> {
let mut attempts = 0;
let max_attempts = 3;
loop {
attempts += 1;
let result = todoist.get_tasks(None, None).await;
match result {
Ok(response) => return Ok(response),
Err(TodoistError::RateLimited { retry_after, message }) if attempts < max_attempts => {
let delay = retry_after.unwrap_or(60);
println!("Rate limited (attempt {}/{}): {}. Waiting {} seconds...",
attempts, max_attempts, message, delay);
tokio::time::sleep(Duration::from_secs(delay)).await;
continue;
}
Err(e) => return Err(e),
}
}
}
RateLimited - API rate limiting with retry informationAuthenticationError - Invalid or expired API tokenAuthorizationError - Insufficient permissionsNotFound - Resource not foundValidationError - Invalid request parametersServerError - Todoist server errors (5xx)NetworkError - Network/connection issuesParseError - Response parsing failuresEmptyResponse - Unexpected empty API responsesGeneric - Other errors with optional status codeslet todoist = TodoistWrapper::new("your-api-token".to_string());
// Get all tasks (returns paginated response)
let response = todoist.get_tasks(None, None).await?;
for task in response.results {
println!("Task: {}", task.content);
}
// Use response.next_cursor for pagination
if let Some(cursor) = response.next_cursor {
println!("More results available with cursor: {}", cursor);
}
// Get a specific task
let task = todoist.get_task("task_id").await?;
// Get tasks for a specific project (paginated)
let response = todoist.get_tasks_for_project("project_id", Some(10), None).await?;
// Use response.next_cursor for pagination
// Get tasks by filter query (paginated)
let filter_args = TaskFilterArgs {
query: "today".to_string(),
lang: Some("en".to_string()),
limit: Some(10),
cursor: None, // Use previous response.next_cursor for next page
};
let response = todoist.get_tasks_by_filter(&filter_args).await?;
// Create a simple task
let args = CreateTaskArgs {
content: "Task content".to_string(),
project_id: Some("project_id".to_string()),
..Default::default()
};
let task = todoist.create_task(&args).await?;
// Create a task with full options
let create_args = CreateTaskArgs {
content: "Complex task".to_string(),
description: Some("Task description".to_string()),
project_id: Some("project_id".to_string()),
priority: Some(3),
due_string: Some("tomorrow at 12:00".to_string()),
labels: Some(vec!["important".to_string()]),
..Default::default()
};
let task = todoist.create_task(&create_args).await?;
// Update a task
let update_args = UpdateTaskArgs {
content: Some("Updated content".to_string()),
priority: Some(4),
due_string: Some("next week".to_string()),
..Default::default()
};
let updated_task = todoist.update_task("task_id", &update_args).await?;
// Complete a task
todoist.complete_task("task_id").await?;
// Reopen a completed task
todoist.reopen_task("task_id").await?;
// Delete a task
todoist.delete_task("task_id").await?;
// Get completed tasks by completion date (paginated)
use todoist_api::models::CompletedTasksFilterArgs;
let completed_args = CompletedTasksFilterArgs {
since: Some("2025-01-01T00:00:00Z".to_string()),
until: Some("2025-01-31T23:59:59Z".to_string()),
project_id: Some("project_id".to_string()),
limit: Some(50),
cursor: None,
..Default::default()
};
let response = todoist.get_completed_tasks_by_completion_date(&completed_args).await?;
for task in response.results {
println!("Completed: {} at {}", task.content, task.completed_at.unwrap_or_default());
}
// Get completed tasks by due date (paginated)
let response = todoist.get_completed_tasks_by_due_date(&completed_args).await?;
// Get all projects (paginated)
let response = todoist.get_projects(None, None).await?;
for project in response.results {
println!("Project: {}", project.name);
}
// Get a specific project
let project = todoist.get_project("project_id").await?;
// Get projects with filtering
let filter_args = ProjectFilterArgs {
limit: Some(20),
cursor: None,
};
let projects = todoist.get_projects_filtered(&filter_args).await?;
// Create a new project
let create_args = CreateProjectArgs {
name: "New Project".to_string(),
color: Some("blue".to_string()),
is_favorite: Some(true),
view_style: Some("list".to_string()),
parent_id: None,
};
let project = todoist.create_project(&create_args).await?;
// Update a project
let update_args = UpdateProjectArgs {
name: Some("Updated Project Name".to_string()),
color: Some("red".to_string()),
is_favorite: Some(false),
view_style: Some("board".to_string()),
};
let updated_project = todoist.update_project("project_id", &update_args).await?;
// Delete a project
todoist.delete_project("project_id").await?;
// Get all labels (paginated)
let response = todoist.get_labels(None, None).await?;
for label in response.results {
println!("Label: {}", label.name);
}
// Get a specific label
let label = todoist.get_label("label_id").await?;
// Get labels with filtering
let filter_args = LabelFilterArgs {
limit: Some(50),
cursor: None,
};
let labels = todoist.get_labels_filtered(&filter_args).await?;
// Create a new label
let create_args = CreateLabelArgs {
name: "Important".to_string(),
color: Some("red".to_string()),
order: Some(1),
is_favorite: Some(true),
};
let label = todoist.create_label(&create_args).await?;
// Update a label
let update_args = UpdateLabelArgs {
name: Some("Very Important".to_string()),
color: Some("dark_red".to_string()),
order: Some(0),
is_favorite: Some(true),
};
let updated_label = todoist.update_label("label_id", &update_args).await?;
// Delete a label
todoist.delete_label("label_id").await?;
// Get all sections (paginated)
let response = todoist.get_sections(None, None).await?;
for section in response.results {
println!("Section: {}", section.name);
}
// Get a specific section
let section = todoist.get_section("section_id").await?;
// Get sections for a project (paginated)
let filter_args = SectionFilterArgs {
project_id: Some("project_id".to_string()),
limit: Some(20),
cursor: None, // Use previous response.next_cursor for next page
};
let response = todoist.get_sections_filtered(&filter_args).await?;
// Create a new section
let create_args = CreateSectionArgs {
name: "New Section".to_string(),
project_id: "project_id".to_string(),
order: Some(1),
};
let section = todoist.create_section(&create_args).await?;
// Update a section
let update_args = UpdateSectionArgs {
name: "Updated Section Name".to_string(),
};
let updated_section = todoist.update_section("section_id", &update_args).await?;
// Delete a section
todoist.delete_section("section_id").await?;
// Get all comments
let comments = todoist.get_comments().await?;
// Get a specific comment
let comment = todoist.get_comment("comment_id").await?;
// Get comments for a task
let filter_args = CommentFilterArgs {
task_id: Some("task_id".to_string()),
project_id: None,
limit: Some(20),
cursor: None,
};
let task_comments = todoist.get_comments_filtered(&filter_args).await?;
// Create a new comment
let create_args = CreateCommentArgs {
content: "This is a comment".to_string(),
task_id: Some("task_id".to_string()),
project_id: None,
attachment: None,
};
let comment = todoist.create_comment(&create_args).await?;
// Update a comment
let update_args = UpdateCommentArgs {
content: "Updated comment content".to_string(),
};
let updated_comment = todoist.update_comment("comment_id", &update_args).await?;
// Delete a comment
todoist.delete_comment("comment_id").await?;
The library provides comprehensive data models for all Todoist entities:
PaginatedResponse<T> - Generic wrapper for paginated API responses with results and next_cursorTask - Complete task information with all fields (v1 API model)Project - Project details and metadataLabel - Label information and stylingSection - Section organization within projects (v1 API model)Comment - Comment system for tasks and projectsAttachment - File attachments for commentsUser - User information and preferencesDue - Due date and time informationDeadline - Deadline informationDuration - Task duration trackingFor flexible API operations, the library provides argument types:
CreateTaskArgs - Full task creation optionsUpdateTaskArgs - Task update parametersCreateProjectArgs - Project creation optionsUpdateProjectArgs - Project update parametersCreateLabelArgs - Label creation optionsUpdateLabelArgs - Label update parametersCreateSectionArgs - Section creation optionsUpdateSectionArgs - Section update parametersCreateCommentArgs - Comment creation optionsUpdateCommentArgs - Comment update parametersFor advanced querying and pagination:
TaskFilterArgs - Task filtering and paginationCompletedTasksFilterArgs - Completed tasks filtering with date rangesProjectFilterArgs - Project filtering and paginationLabelFilterArgs - Label filtering and paginationSectionFilterArgs - Section filtering and paginationCommentFilterArgs - Comment filtering and paginationAll operations return TodoistResult<T> with specific error types for precise error handling:
use todoist_api::{TodoistWrapper, TodoistError};
match todoist.get_tasks(None, None).await {
Ok(response) => println!("Found {} tasks", response.results.len()),
Err(TodoistError::RateLimited { retry_after, message }) => {
println!("Rate limited: {} (retry after {} seconds)", message, retry_after.unwrap_or(0));
// Handle rate limiting - wait and retry
}
Err(TodoistError::AuthenticationError { message }) => {
eprintln!("Authentication failed: {}", message);
// Handle authentication issues
}
Err(TodoistError::EmptyResponse { endpoint, message }) => {
eprintln!("Empty response from {}: {}", endpoint, message);
// Handle unexpected empty responses
}
Err(e) => eprintln!("Other error: {}", e),
}
The library provides specific error types for different scenarios:
RateLimited - API rate limiting with retry informationAuthenticationError - Invalid or expired API tokenAuthorizationError - Insufficient permissionsNotFound - Resource not foundValidationError - Invalid request parametersServerError - Todoist server errors (5xx)NetworkError - Network/connection issuesParseError - Response parsing failuresEmptyResponse - Unexpected empty API responsesGeneric - Other errors with optional status codesThe library uses sensible defaults:
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
For detailed release notes and version history, see CHANGELOG.md.