TodoistSDK

An unofficial Haskell SDK for the Todoist REST API. Manage projects, tasks, comments, sections, and labels with type-safe, ergonomic Haskell functions.
Features
- Complete API Coverage: Projects, Tasks, Comments, Sections, and Labels
- Type-Safe: Leverages Haskell's type system to prevent common errors
- Getter-Only Lenses: Read-only lenses for convenient field access and composition
- Ergonomic Builder Pattern: Easily construct API requests with optional fields
- Automatic Pagination: Transparently fetches all pages for list operations
- Flexible Error Handling: Operations return
Either TodoistError a for explicit error handling
- Testing Support (In Progress): Includes Trace interpreter for testing without API calls
Installation
Using Stack
Add to your stack.yaml:
extra-deps:
- todoist-sdk-0.1.2.1
Add to your package.yaml or .cabal file:
dependencies:
- todoist-sdk
Using Cabal
Add to your .cabal file's build-depends:
build-depends:
base >=4.7 && <5
, todoist-sdk
Then install:
cabal update
cabal install todoist-sdk
Quick Start
Get your API token from Todoist Settings → Integrations → Developer.
Try it in the REPL
$ stack repl
>>> import Web.Todoist
>>> let config = newTodoistConfig "your-api-token-here"
>>> result <- todoist config getAllProjects
>>> case result of
Left err -> print err
Right projects -> mapM_ print projects
Complete Example
Create a file example.hs:
#!/usr/bin/env stack
-- stack --resolver lts-24.7 script --package todoist-sdk --package text
{-# LANGUAGE OverloadedStrings #-}
import Web.Todoist
main :: IO ()
main = do
-- Configure with your API token
let config = newTodoistConfig "your-api-token-here"
result <- todoist config $ do
-- Create a new project
let newProj = runBuilder (createProjectBuilder "My Haskell Project")
(withDescription "Learning Haskell SDK" <> withViewStyle Board)
project <- addProject newProj
-- Create a task in the project
let newTask = runBuilder (newTaskBuilder "Read documentation")
(withProjectId (_id project) <> withPriority 2)
task <- createTask newTask
-- Get all tasks
tasks <- getTasks taskParamBuilder
pure (project, task, tasks)
case result of
Left err -> putStrLn $ "Error: " ++ show err
Right (proj, task, tasks) -> do
putStrLn $ "Created project: " ++ show (_name proj)
putStrLn $ "Created task: " ++ show (_content task)
putStrLn $ "Total tasks: " ++ show (length tasks)
Run it:
chmod +x example.hs
./example.hs
Common Usage Examples
Working with Projects
import Web.Todoist
let config = newTodoistConfig "your-api-token"
-- Get all projects
result <- todoist config getAllProjects
-- Create a project with optional fields
let newProject = runBuilder (createProjectBuilder "Shopping List")
(withColor "blue" <> withViewStyle List <> withIsFavorite True)
project <- todoist config (addProject createProjectBuilder)
-- Update a project
let update = runBuilder emptyProjectUpdate (withName "Updated Name")
updated <- todoist config (updateProject update projectId)
-- Delete a project
todoist config (deleteProject projectId)
Working with Tasks
-- Create a task with due date
let task = runBuilder (newTaskBuilder "Buy milk")
(withProjectId "project-123"
<> withDueString "tomorrow"
<> withPriority 3
<> withLabels ["grocery", "urgent"])
result <- todoist config (createTask task)
-- Get tasks with filters
let params = TaskParam
{ project_id = Just "project-123"
, filter = Nothing
, label_id = Nothing
, cursor = Nothing
, limit = Nothing
}
tasks <- todoist config (getTasks params)
-- Complete a task
todoist config (closeTask taskId)
-- Update a task
let update = runBuilder updateTaskBuilder (withContent "Buy 2% milk")
updated <- todoist config (updateTask update taskId)
-- Add a comment to a task
let comment = runBuilder (newCommentBuilder "Don't forget organic!")
(withTaskId "task-456")
result <- todoist config (addComment comment)
-- Get all comments for a project
let params = CommentParam
{ project_id = Just "project-123"
, task_id = Nothing
, cursor = Nothing
, limit = Nothing
, public_key = Nothing
}
comments <- todoist config (getComments params)
Working with Sections
-- Create a section
let section = runBuilder (newSection "In Progress" "project-123") mempty
result <- todoist config (addSection section)
-- Get sections for a project with builder pattern
let params = runBuilder newSectionParam (withProjectId "project-123" <> withLimit 50)
sections <- todoist config (getSections params)
Working with Labels
-- Create a label
let label = runBuilder (createLabelBuilder "urgent") mempty
result <- todoist config (addLabel label)
-- Get all labels
let params = runBuilder labelParamBuilder (withLimit 50)
labels <- todoist config (getLabels params)
Working with Lenses
TodoistSDK provides getter-only lenses for all domain types, enabling convenient field access and composition. All lenses are read-only; for mutations, use the builder pattern.
Basic Usage
import Web.Todoist
import Web.Todoist.Lens ((^.)) -- Import the getter operator
-- Get all projects
Right projects <- todoist config getAllProjects
-- Extract fields using lenses
let firstProject = head projects
let projectName = firstProject ^. name
let projectColor = firstProject ^. color
let isShared = firstProject ^. isShared
print projectName -- Prints: Name "My Project"
Composing Lenses
Lenses can be composed to access nested fields:
-- Get tasks
Right tasks <- todoist config (getTasks taskParamBuilder)
-- Access nested fields
let task = head tasks
let maybeDueDate = task ^. taskDue >>= (^. dueDate)
let taskContent = task ^. taskContent
-- Check if task has a deadline
case task ^. taskDeadline of
Just deadline -> putStrLn $ "Deadline: " ++ show (deadline ^. deadlineDate)
Nothing -> putStrLn "No deadline set"
Available Lenses
All domain types provide lenses for their fields:
Project: projectId, name, description, order, color, isCollapsed, isShared, isFavorite, isArchived, canAssignTasks, viewStyle, createdAt, updatedAt
Task: taskId, taskContent, taskDescription, taskProjectId, taskSectionId, taskParentId, taskLabels, taskPriority, taskDue, taskDeadline, taskDuration, taskIsCollapsed, taskOrder, taskAssigneeId, taskAssignerId, taskCompletedAt, taskCreatorId, taskCreatedAt, taskUpdatedAt
Comment: commentId, commentContent, commentPosterId, commentPostedAt, commentTaskId, commentProjectId, commentAttachment
Section: sectionId, sectionName, sectionProjectId, sectionIsCollapsed, sectionOrder
Label: labelId, labelName, labelColor, labelOrder, labelIsFavorite
Due: dueDate, dueString, dueLang, dueIsRecurring, dueTimezone
Deadline: deadlineDate, deadlineLang
Duration: amount, unit
Mutation with Builders
Lenses are read-only. For field mutations, use the builder pattern:
-- Reading with lenses
let oldName = project ^. name
-- Updating with builders (not lenses)
let update = runBuilder updateProjectBuilder (withName "New Name")
Right updatedProject <- todoist config (updateProject update projectId)
Error Handling
All operations return Either TodoistError a. The TodoistError type includes:
BadRequest - Invalid request parameters
Unauthorized - Invalid or missing API token
Forbidden - Insufficient permissions
NotFound - Resource doesn't exist
HttpError String - Other HTTP errors
Example:
result <- todoist config (getProject projectId)
case result of
Left BadRequest -> putStrLn "Invalid project ID"
Left Unauthorized -> putStrLn "Check your API token"
Left NotFound -> putStrLn "Project not found"
Left (HttpError msg) -> putStrLn $ "HTTP error: " ++ msg
Right project -> print project
Documentation
Full API documentation is available on Hackage.
For details on the Todoist REST API, see the official documentation.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Acknowledgments
This library is a labor of love to the Haskell community and to the Todoist app. It is an unofficial SDK and is not affiliated with or endorsed by Doist.