Unofficial Haskell SDK for the Todoist REST API.
TodoistSDK provides a type-safe, tagless-final interface to the Todoist REST API. It includes comprehensive coverage of Projects, Tasks, Comments, Sections, and Labels endpoints with both real HTTP and tracing interpreters for testing. The library uses mtl-style type classes for operation definitions and provides a clean builder pattern for request construction.
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 afor 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)
Working with Comments
-- 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 parametersUnauthorized- Invalid or missing API tokenForbidden- Insufficient permissionsNotFound- Resource doesn't existHttpError 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.