Skip to main content

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.

note

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.

Privacy and passwords

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.

IdentifierUsed for
task_idTasks, projects, checklists, and sub-tasks.
list_idNormal 2Do lists.
list_group_idList Groups. query_tasks can use this to query the group's dynamic Smart Filter.
smart_list_idSaved Smart Lists.
tag_idTags.
tag_group_idTag Groups.
location_idLocations.
collection_idNamespaced collection identity emitted on lists, List Groups, and Smart Lists: focus:<name>, list:<uid>, list-group:<uid>, or smart-list:<uid>.
destination_idNamespaced task-creation destination: list:<uid> or parent-task:<uid>.
alarm_idStable 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

CommandArgumentsReturns
list_listsOptional include_archived boolean.lists, item_count.
list_list_groupsNone.list_groups, item_count.
list_smart_listsNone.smart_lists, item_count.
list_tagsOptional tag_group_id.tags, item_count. Tags include paused.
list_tag_groupsNone.tag_groups, item_count.
list_locationsNone.locations, item_count.
get_default_task_destinationNone.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

CommandArgumentsReturns
get_taskRequired task_id.task.
query_tasksOptional 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_subtasksRequired task_id; optional paused_filter, sort, include_completed (default: true).tasks, item_count, parent_task_id.
selected_tasksNone.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:

ValueBehavior
hideDefault. Hide tasks paused by a paused tag.
includeInclude paused and unpaused tasks.
onlyReturn only tasks paused by a paused tag.

sort is an object with:

KeyValues
fieldtitle, due_date, start_date, priority, duration_seconds, created, modified, completed.
directionOptional. 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

CommandArgumentsReturns
create_taskRequired title and either list_id or parent_task_id; optional task_type, subtasks, and task write fields.task, and subtasks when supplied.
update_taskRequired task_id; optional task_type and task write fields.task.
update_task_actionRequired task_id, action.task.
delete_taskExactly one of task_id or task_ids; optional recursive boolean. Default: true.Single: task_id, deleted; batch: task_ids, deleted, item_count.
duplicate_taskRequired task_id.task.
move_taskExactly one of task_id or task_ids; exactly one of list_id or parent_task_id.Single: task; batch: tasks, item_count.
complete_taskExactly one of task_id or task_ids; optional recursive boolean. Default: true.Single: task; batch: tasks, item_count.
uncomplete_taskExactly 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:

FieldTypeNotes
titleStringRequired for create_task; optional for update_task.
notesStringReplaces notes. Use an empty string to clear notes; omit the field to leave notes unchanged.
starredBooleanReplaces the starred state.
due_dateString or missing valueyyyy-MM-dd. missing value clears the due date and due time.
due_timeString or missing valueHH:mm. Requires a due date.
start_dateString or missing valueyyyy-MM-dd.
start_timeString or missing valueHH:mm. Requires a start date.
duration_secondsIntegerMust be from 0 through 2147483647.
priorityStringnone, low, medium, or high.
tag_updateRecordSee Tags and Locations.
location_updateRecordSee Tags and Locations.
alarmsListOmitted means unchanged. Empty list clears. Non-empty list replaces all sound alarms. A task may have at most 10 total alarms.
recurrenceRecordOmitted means unchanged. {frequency:"none"} clears.
actionRecordReplaces 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:

KeyValues
operationset, append, or remove.
tag_idsFor tag_update, a list of tag_id strings.
location_idsFor 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.

CommandArgumentsReturns
pause_tagRequired tag_id.tag.
unpause_tagRequired 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.

TypePayload
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:

KeyTypeNotes
sound_nameStringOptional. Omit to use 2Do's normal/default alarm sound.
triggerRecordRequired. 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.

KeyValues
frequencyRequired. none, daily, weekly, monthly, yearly, weekdays, or custom.
based_onRequired unless frequency is none. due_date or completion.
unitRequired for custom. day, week, month, or year.
intervalRequired for custom. Must be between 1 and 999.
weekdaysOptional for weekly. Values: sunday, monday, tuesday, wednesday, thursday, friday, saturday. Write this label as `
weekdayRequired with ordinal for monthly ordinal weekday recurrence.
ordinalRequired with weekday for monthly ordinal weekday recurrence. Supports 1 through 5, or -1 for the last matching weekday.
end_ruleOptional. {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

These commands require the Mac app UI.

CommandArgumentsReturns
select_listRequired list_id.selected, list_id.
select_list_groupRequired list_group_id.selected, list_group_id.
select_smart_listRequired smart_list_id.selected, smart_list_id.
select_focusRequired focus: all, today, starred, scheduled, or done.selected, collection_id.
open_taskRequired task_id.opened, task_id.

Returned Records

Task records include:

FieldNotes
task_id, title, notes, list_id, task_typeStable identity and core fields.
list_title, collection_id, list_group_idThe owning list's name and namespaced collection identity, plus its List Group when it belongs to one.
parent_task_idPresent when the task is a child task.
child_task_idsPresent for projects or checklists with children.
child_task_destination_idPresent 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, pausedCompletion state, starred state, and paused-by-tag state.
due_date, due_time, start_date, start_time, duration_secondsDate and duration fields when present.
prioritynone, low, medium, or high.
tags, locationsFull tag and location records with stable IDs.
action, alarms, recurrenceStructured action, sound alarms, and recurrence records. recurrence is missing value when not repeating.

Other records:

RecordFields
Listlist_id, collection_id, title, archived, editable, optional list_group_id.
List Grouplist_group_id, collection_id, title, type (collect, focus, all_lists, smart_lists, shared_lists, or any), collapsed.
Smart Listsmart_list_id, collection_id, title, smart_search.
Tagtag_id, title, paused, optional tag_group_id.
Tag Grouptag_group_id, title.
Locationlocation_id, title, address.
Default destinationdestination_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:

CodeMeaning
invalid_argumentsUnknown command, unknown argument key, missing required value, malformed date/time, or unknown enum value.
not_foundA supplied item was not found, was deleted, or belongs to a password-protected list that is still locked and hidden from automation.
edit_rejected2Do could not apply the requested model change.
automation_unavailableThe command requires Mac UI state that is not available.
operation_failedA lower-level save or delete operation failed.
access_denied2Do 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_id and list_id; there is no generic id field.
  • Query with paused_filter:"include" when you want Claude or a script to reason about hidden paused-tag work.
  • Use update_task_action when your script only needs to set, replace, or clear a task action.
  • Use list_subtasks with a returned task_id to inspect a project or checklist before modifying its children.