Todoist
Task management backend for Inertia Mill.
Goals
A persistent, structured task store supporting hierarchical tasks, labels, priorities, and due dates — so Inertia Mill has somewhere to write decomposed subtasks and track which tasks have been processed across nightly runs.
Effectiveness
Adequate for reading and writing tasks. The API surface we use (fetch all tasks, add subtask, apply label, update schedule, close task) works reliably. The real limitation: it's a consumer product with API restrictions that only become visible when you hit them in production.
What made it effective
- Cursor pagination on
getTasks()handles large task lists cleanly — ~590 tasks page without any special handling beyond a loop. - Label-based idempotency is a clean pattern: the
triviallabel marks processed tasks; removing it re-queues a task for the next run. - Forcing all Todoist-specific code behind the
TaskStoreport (todoist-adapter.ts) meant we isolated the 403 failure to one file rather than having it ripple through the domain logic.
Bonus utility
The td CLI's JSON mode is useful for quick one-off task inspection and diagnosis without writing any code.
Friction / pain points / surprises
moveTasks returns HTTP 403 on the free tier. promoteToHeadroom creates a new section and moves the task into it — addSection succeeds, moveTasks fails with 403 Forbidden. This is a plan restriction, not documented anywhere obvious. The feature is silently non-functional; tasks that should be promoted fail every run, forever.
Priority integers are inverted. Todoist's priority 4 (integer) displays as P1 ("urgent"). Priority 1 displays as P4 ("low"). Every consumer of the priority field has to hold this inversion in mind. Obvious in retrospect; not obvious when first reading the API response.
parentId not parent_id. The SDK and CLI both use camelCase, unlike typical REST APIs. Easy to trip on when reading raw JSON.