# Toado > A task list that takes responsibility for a task AFTER you write it down. > Most task apps are built for capture and do nothing about the aftermath. ## The idea Toado classifies a backlog into four species and raises bounded triage questions: - alive real work, still needed - ghost probably already done but never ticked (makes the whole list lie) - dead the window to do it has passed - not-task a bookmark or reading item wearing a task costume Judgement is DETERMINISTIC (auditable rules). A model only writes the human-readable explanation; it never decides, deletes, or reclassifies. ## Opinionated limits - at most 3 tasks may be priority "now" (an inflatable priority is meaningless) - at most 5 open triage questions (a triage queue can become a backlog) - after 3 unanswered nudges a task is parked, not nagged forever ## API OpenAPI: https://toado.co/openapi.json MCP: POST https://toado.co/mcp (JSON-RPC 2.0, same auth, identical argument names) Auth: Authorization: Bearer , keys are prefixed tdo_ Scopes: tasks:read, tasks:write, nudges:read, nudges:write, jobs:write ## Endpoints GET /v1/tasks?state=&species=&project= POST /v1/tasks {"text":"Call bank tomorrow at 3pm p1 %email #Finance"} GET /v1/board the species board with counts GET /v1/tasks/{id} PATCH /v1/tasks/{id} DELETE /v1/tasks/{id} POST /v1/tasks/{id}/complete recurring tasks roll forward, check "recurred" GET /v1/focus the ONE next thing, not a list GET /v1/suggestions a shortlist of at most 3, each with a reason GET /v1/search?q= GET /v1/projects POST /v1/projects GET /v1/nudges open triage questions POST /v1/nudges/{id}/answer {"answer":"Yes, mark done"} POST /v1/nudges/{nudgeId}/snooze {"minutes":60} hide the question until then; "not now", not "no" POST /v1/sweep re-run the decay engine (free, no AI) GET /v1/tasks/{taskId}/jobs background agent jobs on a task POST /v1/tasks/{taskId}/jobs {"capability":"research"} needs jobs:write POST /v1/tasks/bulk one change to many tasks, caps reported not exceeded GET /v1/myday today's commitments, capped at 7, plus carry-over GET /v1/myday includes "plan" (capacity vs committed, overBy, defer suggestions) and "morningPlan" when the day is empty POST /v1/automations/compile {"description":"every morning move overdue to today"} -> compiled spec or an honest refusal; LLM compiles, never runs POST /v1/automations {"name","minuteOfDayUtc","actionKind"} cap 10 GET /v1/automations automations + recentRuns (run history is public) GET /v1/favourites starred projects/labels/filters POST /v1/favourites/toggle {"kind":"project|label|filter","refId"} idempotent GET /v1/calendar?weeks=1 scheduled work as OCCURRENCES (see the note below) GET /v1/templates POST /v1/templates/{templateId}/use create tasks from a template GET /v1/upcoming?days=14 tasks by day, plus overdue and UNDATED GET /v1/settings the time zone to send back, and autonomy level GET /v1/activity?taskId= what changed and who changed it, newest first Quick-add syntax accepted by POST /v1/tasks {"text": "..."}: tomorrow 3pm | every monday | every! 3 days dates and recurrence p1 p2 p3 p4 OR !!1 !!2 !!3 !!4 priority (both spellings; !!4 = none, as Todoist) %label #project /section {deadline} metadata @label still parses; % is the current form (Todoist is migrating @ -> % by end 2026) // description everything after this is the description, NOT parsed for 30m duration Send tzOffsetMinutes or dates resolve in UTC. POST /v1/tasks/bulk/undo reverse a bulk change, using its "prev" array ## Three things that will bite you if you assume otherwise 0. GET /v1/tasks is PAGED. It returns at most "limit" rows (default 50) plus "nextCursor". Pass that back as ?cursor= to get the next page, and stop when it is null. "count" is the size of THIS page, not a total. Ignore the cursor and you silently see only the first page. Cursors are opaque -- do not build them by hand -- and are tied to the filter that made them, so changing state/species/projectId mid-walk is refused with a 400. 1. /v1/calendar returns OCCURRENCES, not tasks. A recurring task appears on every day it falls, so items.length is NOT a task count. 2. Send tzOffsetMinutes on anything that resolves a date (POST /v1/tasks, /v1/myday, /v1/calendar, template use). JS convention, so India is -330. Without it the server resolves against UTC: "day 3 at 09:00" lands hours off, and in a negative-offset zone it lands on the WRONG DAY. 3. A cap refusal is a normal RESULT, not an error. POST /v1/tasks/bulk returns {"ok":false,"count":0,"reason":"All 3 urgent slots are taken.","allowed":0} -- read the "allowed" field and retry with that many rather than bisecting. /v2 serves the same contract, kept working for older callers.