todoist-sdk: Unofficial Haskell SDK for the Todoist REST API

[ library, mit, web ] [ Propose Tags ] [ Report a vulnerability ]

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.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

Versions [RSS] 0.1.2.1
Change log CHANGELOG.md
Dependencies aeson (>=2.2.3.0 && <2.3), base (>=4.7 && <5), bytestring (>=0.12.2.0 && <0.12.3), microlens (>=0.4.13 && <0.5), req (>=3.13.4 && <3.14), text (>=2.1.2 && <2.2), todoist-sdk, transformers (>=0.6.1.1 && <0.6.2.0) [details]
Tested with ghc ==9.10.2
License MIT
Copyright 2025 Sam S. Almahri
Author Sam S. Almahri
Maintainer sam.salmahri@gmail.com
Category Web
Home page https://github.com/samahri/TodoistSDK
Bug tracker https://github.com/samahri/TodoistSDK/issues
Source repo head: git clone https://github.com/samahri/TodoistSDK
Uploaded by samahri at 2025-11-17T21:49:58Z
Distributions
Downloads 7 total (7 in the last 30 days)
Rating 2.0 (votes: 1) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2025-11-17 [all 1 reports]

Readme for todoist-sdk-0.1.2.1

[back to package description]

TodoistSDK

Hackage

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)

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 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.