AppleScript Automation
2Do for Mac includes a stable AppleScript automation API for task-manager workflows. You can discover lists, list groups, Smart Lists, tags, tag groups, and locations; create and update tasks, projects, checklists, and sub-tasks; complete, duplicate, move, or delete tasks; pause and unpause tags; and ask 2Do to select or open items in the Mac app.
The same command executor powers AppleScript and the Claude MCP extension. AppleScript and MCP use the same canonical snake_case command names, argument keys, response fields, and validation rules. 2Do's native Shortcuts actions run on this engine too.
This is a strict API. AppleScript exposes only the documented command terms, and each command takes one optional arguments parameter. Unknown command names, unknown argument keys, and unknown enum values fail instead of being normalized. There are no aliases or alternate parameter names.
AppleScript follows the Mac app's current unlocked session. Unlock 2Do before running a script. A locked app returns access_denied; password-protected lists that are still locked are hidden and their tasks cannot be read or changed. 2Do checks the lock state again immediately before every write.
Getting Started
Open Script Editor from Applications > Utilities, then choose File > Open Dictionary... > 2Do to see the 2Do Automation Suite.
Every automation command is called inside a tell application "2Do" block:
tell application "2Do"
set listsResult to list_lists given arguments:{include_archived:false}
set firstList to item 1 of lists of listsResult
set createdResult to create_task given arguments:{list_id:(list_id of firstList), title:"Review project plan", due_date:"2026-07-02", due_time:"17:00", priority:"high"}
set createdTask to task of createdResult
open_task given arguments:{task_id:(task_id of createdTask)}
end tell
You can also run scripts from Terminal:
osascript -e 'tell application "2Do" to list_lists'
Stable Identifiers
Automation responses use type-specific identifiers. Do not construct these values yourself. Read them from discovery, query, or create responses and pass them back to later commands.
| Identifier | Used for |
|---|---|
task_id | Tasks, projects, checklists, and sub-tasks. |
list_id | Normal 2Do lists. |
list_group_id | List Groups. query_tasks can use this to query the group's dynamic Smart Filter. |
smart_list_id | Saved Smart Lists. |
tag_id | Tags. |
tag_group_id | Tag Groups. |
location_id | Locations. |
collection_id | Namespaced collection identity emitted on lists, List Groups, and Smart Lists: focus:<name>, list:<uid>, list-group:<uid>, or smart-list:<uid>. |
destination_id | Namespaced task-creation destination: list:<uid> or parent-task:<uid>. |
alarm_id | Stable identity on returned alarm records. |
Commands
The tables below list the canonical AppleScript and MCP command names. All commands return a record. Collection commands include both the collection and item_count. Task mutation commands return the updated task record.
Discovery
| Command | Arguments | Returns |
|---|---|---|
list_lists | Optional include_archived boolean. | lists, item_count. |
list_list_groups | None. | list_groups, item_count. |
list_smart_lists | None. | smart_lists, item_count. |
list_tags | Optional tag_group_id. | tags, item_count. Tags include paused. |
list_tag_groups | None. | tag_groups, item_count. |
list_locations | None. | locations, item_count. |
get_default_task_destination | None. | destination: the list new tasks go to by default, with destination_id, list_id, and title. Password-aware: it uses the same destination logic as task creation, so it always returns an editable normal list. |
Reading Tasks
| Command | Arguments | Returns |
|---|---|---|
get_task | Required task_id. | task. |
query_tasks | Optional target list_id, list_group_id, smart_list_id, or focus; optional search, due/start date windows, paused_filter, sort, offset, limit, include_completed (default: true). | tasks, item_count, total_count. |
list_subtasks | Required task_id; optional paused_filter, sort, include_completed (default: true). | tasks, item_count, parent_task_id. |
selected_tasks | None. | tasks, item_count. Requires the Mac app UI. |
query_tasks accepts at most one target identifier. When no target is supplied, 2Do queries the built-in All focus scope. focus accepts all, today, starred, scheduled, or done. Passing a list_group_id queries the same dynamic List Group task view shown when you click a List Group in the Mac sidebar. search must contain 1 through 256 characters and matches literal text only: a case-insensitive and diacritic-insensitive substring of title, notes, current tag titles, and current location titles, with no query-operator or boolean syntax. Date windows use inclusive yyyy-MM-dd fields: due_on_or_after, due_on_or_before, start_on_or_after, and start_on_or_before; tasks without the corresponding date never match a window. offset must be zero or greater. Because offset is an AppleScript command, write its record label as |offset| in AppleScript source. limit must be 1 or greater. total_count counts all matching tasks before offset/limit; item_count counts the returned tasks.
paused_filter values:
| Value | Behavior |
|---|---|
hide | Default. Hide tasks paused by a paused tag. |
include | Include paused and unpaused tasks. |
only | Return only tasks paused by a paused tag. |
sort is an object with:
| Key | Values |
|---|---|
field | title, due_date, start_date, priority, duration_seconds, created, modified, completed. |
direction | Optional. ascending or descending. Default: ascending. |
Example:
tell application "2Do"
set groupsResult to list_list_groups
set groupRecord to item 1 of list_groups of groupsResult
set tasksResult to query_tasks given arguments:{list_group_id:(list_group_id of groupRecord), search:"invoice", paused_filter:"include", sort:{field:"due_date", direction:"ascending"}, |offset|:0, limit:20}
set starredResult to query_tasks given arguments:{focus:"starred", search:"invoice", |offset|:0, limit:20}
end tell
Writing Tasks
| Command | Arguments | Returns |
|---|---|---|
create_task | Required title and either list_id or parent_task_id; optional task_type, subtasks, and task write fields. | task, and subtasks when supplied. |
update_task | Required task_id; optional task_type and task write fields. | task. |
update_task_action | Required task_id, action. | task. |
delete_task | Exactly one of task_id or task_ids; optional recursive boolean. Default: true. | Single: task_id, deleted; batch: task_ids, deleted, item_count. |
duplicate_task | Required task_id. | task. |
move_task | Exactly one of task_id or task_ids; exactly one of list_id or parent_task_id. | Single: task; batch: tasks, item_count. |
complete_task | Exactly one of task_id or task_ids; optional recursive boolean. Default: true. | Single: task; batch: tasks, item_count. |
uncomplete_task | Exactly one of task_id or task_ids; optional recursive boolean (default: true); optional update_parent boolean (default: false, requires recursive: true) to reconcile a completed parent after uncompleting a child. | Single: task; batch: tasks, item_count. |
task_type values are normal, project, and checklist.
Creating a task with parent_task_id creates a sub-task. If the parent is a normal task, 2Do converts it through the same project conversion path used by the UI, then creates the child under that project. subtasks is accepted only with list_id; it must contain 1 through 100 child records, and each child requires a non-empty title. Project children may use the task write fields. Checklist children cannot set due/start fields, duration, recurrence, or relative alarms because they inherit scheduling from the checklist; absolute sound alarms remain supported. task_ids batches are capped at 100 unique task IDs and are validated before any task is changed.
Completing a recurring task advances it to its next occurrence. Repeating the same complete_task call advances it again, so scripts must not retry recurring completion as an idempotent operation.
Task write fields:
| Field | Type | Notes |
|---|---|---|
title | String | Required for create_task; optional for update_task. |
notes | String | Replaces notes. Use an empty string to clear notes; omit the field to leave notes unchanged. |
starred | Boolean | Replaces the starred state. |
due_date | String or missing value | yyyy-MM-dd. missing value clears the due date and due time. |
due_time | String or missing value | HH:mm. Requires a due date. |
start_date | String or missing value | yyyy-MM-dd. |
start_time | String or missing value | HH:mm. Requires a start date. |
duration_seconds | Integer | Must be from 0 through 2147483647. |
priority | String | none, low, medium, or high. |
tag_update | Record | See Tags and Locations. |
location_update | Record | See Tags and Locations. |
alarms | List | Omitted means unchanged. Empty list clears. Non-empty list replaces all sound alarms. A task may have at most 10 total alarms. |
recurrence | Record | Omitted means unchanged. {frequency:"none"} clears. |
action | Record | Replaces the task action. |
Example:
tell application "2Do"
set listsResult to list_lists
set inboxList to item 1 of lists of listsResult
set created to create_task given arguments:{list_id:(list_id of inboxList), title:"Call printer repair", starred:true, notes:"Ask for warranty status", due_date:"2026-07-03", due_time:"09:30", duration_seconds:1800, priority:"medium", action:{type:"call", value:"+1 555 0100"}, subtasks:{{title:"Find receipt"}, {title:"Confirm serial number", starred:true}}}
complete_task given arguments:{task_ids:{task_id of task of created}, recursive:true}
end tell
Tags and Locations
tag_update and location_update use the same shape:
| Key | Values |
|---|---|
operation | set, append, or remove. |
tag_ids | For tag_update, a list of tag_id strings. |
location_ids | For location_update, a list of location_id strings. |
Use set with an empty list to clear all tags or locations.
tell application "2Do"
set tagsResult to list_tags
set waitingTag to item 1 of tags of tagsResult
set selectedResult to selected_tasks
repeat with taskRecord in tasks of selectedResult
update_task given arguments:{task_id:(task_id of taskRecord), tag_update:{operation:"append", tag_ids:{tag_id of waitingTag}}}
end repeat
end tell
Pausing Tags
Paused tags hide matching tasks from normal task views. Automation exposes the public field as paused; it does not expose the internal model name.
| Command | Arguments | Returns |
|---|---|---|
pause_tag | Required tag_id. | tag. |
unpause_tag | Required tag_id. | tag. |
Both commands are idempotent. Pausing an already paused tag returns the current tag record without creating a second action.
tell application "2Do"
set tagsResult to list_tags
set tagRecord to item 1 of tags of tagsResult
pause_tag given arguments:{tag_id:(tag_id of tagRecord)}
set pausedTasks to query_tasks given arguments:{paused_filter:"only"}
end tell
Actions
Task actions can be set during create_task, replaced during update_task, or changed directly with update_task_action.
| Type | Payload |
|---|---|
none | {type:"none"} clears the action. |
call | {type:"call", value:"+1 555 0100"} |
message | {type:"message", value:"+1 555 0100"} |
mail | {type:"mail", value:"person@example.com"} |
visit | {type:"visit", value:"1 Infinite Loop, Cupertino, CA"} |
url | {type:"url", value:"https://example.com"} |
google | {type:"google", value:"search text"} |
Automation uses 2Do's entered action variants. Contact-backed birthday and anniversary actions are not exposed in this API.
tell application "2Do"
update_task_action given arguments:{task_id:"TASK-UID-HERE", action:{type:"url", value:"https://www.2doapp.com"}}
end tell
Alarms
Automation exposes sound alarms only.
Alarm object:
| Key | Type | Notes |
|---|---|---|
sound_name | String | Optional. Omit to use 2Do's normal/default alarm sound. |
trigger | Record | Required. See below. |
Absolute trigger:
{kind:"absolute", date:"2026-07-03 09:15"}
Relative trigger:
{kind:"relative", seconds:-900, anchor:"task_time"}
Absolute alarm dates use floating local yyyy-MM-dd HH:mm values. Do not include timezone names, Z suffixes, or UTC offsets.
Relative alarms require the task to have a due date or start date after applying the same write payload. anchor is task_date or task_time, and seconds must be between -63071999 and 63071999. A task may have at most 10 total alarms, including existing non-sound alarms that automation preserves.
Example:
tell application "2Do"
update_task given arguments:{task_id:"TASK-UID-HERE", due_date:"2026-07-03", due_time:"09:30", alarms:{{trigger:{kind:"relative", seconds:-900, anchor:"task_time"}}}}
end tell
Recurrence
Recurrence replacement is supported in create_task and update_task.
| Key | Values |
|---|---|
frequency | Required. none, daily, weekly, monthly, yearly, weekdays, or custom. |
based_on | Required unless frequency is none. due_date or completion. |
unit | Required for custom. day, week, month, or year. |
interval | Required for custom. Must be between 1 and 999. |
weekdays | Optional for weekly. Values: sunday, monday, tuesday, wednesday, thursday, friday, saturday. Write this label as ` |
weekday | Required with ordinal for monthly ordinal weekday recurrence. |
ordinal | Required with weekday for monthly ordinal weekday recurrence. Supports 1 through 5, or -1 for the last matching weekday. |
end_rule | Optional. {type:"never"}, {type:"on_date", date:"yyyy-MM-dd"}, or {type:"after_count", occurrence_count:n} where n is 1 through 99. |
Recurrence requires the task to have a due date or start date after applying the same write payload, unless frequency is none to clear recurrence.
tell application "2Do"
update_task given arguments:{task_id:"TASK-UID-HERE", due_date:"2026-07-06", recurrence:{frequency:"weekly", based_on:"due_date", |weekdays|:{"monday", "wednesday", "friday"}, end_rule:{type:"after_count", occurrence_count:12}}}
end tell
Clear recurrence:
tell application "2Do"
update_task given arguments:{task_id:"TASK-UID-HERE", recurrence:{frequency:"none"}}
end tell
Navigation
These commands require the Mac app UI.
| Command | Arguments | Returns |
|---|---|---|
select_list | Required list_id. | selected, list_id. |
select_list_group | Required list_group_id. | selected, list_group_id. |
select_smart_list | Required smart_list_id. | selected, smart_list_id. |
select_focus | Required focus: all, today, starred, scheduled, or done. | selected, collection_id. |
open_task | Required task_id. | opened, task_id. |
Returned Records
Task records include:
| Field | Notes |
|---|---|
task_id, title, notes, list_id, task_type | Stable identity and core fields. |
list_title, collection_id, list_group_id | The owning list's name and namespaced collection identity, plus its List Group when it belongs to one. |
parent_task_id | Present when the task is a child task. |
child_task_ids | Present for projects or checklists with children. |
child_task_destination_id | Present on editable projects and checklists that can currently accept children: the task's namespaced identity as a child destination (parent-task:<uid>). Use the task's task_id as parent_task_id when creating or moving children. |
completed, starred, paused | Completion state, starred state, and paused-by-tag state. |
due_date, due_time, start_date, start_time, duration_seconds | Date and duration fields when present. |
priority | none, low, medium, or high. |
tags, locations | Full tag and location records with stable IDs. |
action, alarms, recurrence | Structured action, sound alarms, and recurrence records. recurrence is missing value when not repeating. |
Other records:
| Record | Fields |
|---|---|
| List | list_id, collection_id, title, archived, editable, optional list_group_id. |
| List Group | list_group_id, collection_id, title, type (collect, focus, all_lists, smart_lists, shared_lists, or any), collapsed. |
| Smart List | smart_list_id, collection_id, title, smart_search. |
| Tag | tag_id, title, paused, optional tag_group_id. |
| Tag Group | tag_group_id, title. |
| Location | location_id, title, address. |
| Default destination | destination_id, list_id, title (from get_default_task_destination). |
Errors
AppleScript errors use standard AppleScript error number -10000. The error message starts with the stable automation error code, such as:
| Code | Meaning |
|---|---|
invalid_arguments | Unknown command, unknown argument key, missing required value, malformed date/time, or unknown enum value. |
not_found | A supplied item was not found, was deleted, or belongs to a password-protected list that is still locked and hidden from automation. |
edit_rejected | 2Do could not apply the requested model change. |
automation_unavailable | The command requires Mac UI state that is not available. |
operation_failed | A lower-level save or delete operation failed. |
access_denied | 2Do is locked, or access was revoked while an in-flight write was being revalidated. |
tell application "2Do"
try
create_task given arguments:{title:"Missing list"}
on error errMsg number errNum
log "2Do automation failed (" & errNum & "): " & errMsg
end try
end tell
Tips
- Use command and argument names exactly as documented.
- Always pass type-specific identifiers such as
task_idandlist_id; there is no genericidfield. - Query with
paused_filter:"include"when you want Claude or a script to reason about hidden paused-tag work. - Use
update_task_actionwhen your script only needs to set, replace, or clear a task action. - Use
list_subtaskswith a returnedtask_idto inspect a project or checklist before modifying its children.