Skip to main content

AppleScript Support

BusyCal includes an AppleScript automation API. You can list accounts and calendars, search events and tasks by date or keyword, check free/busy availability, and create, update, or delete items, all from a script. Changes made through the API stay in sync with the BusyCal UI and your calendar accounts.

note

BusyCal's AppleScript support is primarily data-oriented. You can read and write calendar data from scripts. The one exception is open item, which reveals a specific event or task in the BusyCal window.

Requires BusyCal version 2026.1.3 or later.

tip

If you use Claude Desktop, you can also control BusyCal through natural language using the BusyCal MCP extension. It uses the same automation engine under the hood.

Getting Started​

Open Script Editor (in Applications > Utilities) and paste any of the examples below to try them out.

All commands are called inside a tell application "BusyCal" block. Each command uses labeled parameters and returns a record or list of records.

tell application "BusyCal"
set results to query events given searchText:"review", startDate:(current date), endDate:((current date) + (7 * days)), mode:"uiConsistent", fetchLimit:20
end tell

You can also search across multiple item types at once with query items:

tell application "BusyCal"
set rangeStart to current date
set rangeEnd to rangeStart + (3 * days)
set results to query items given searchText:"review", startDate:rangeStart, endDate:rangeEnd, itemTypes:"event, task, journal", searchFields:"title", mode:"uiConsistent", fetchLimit:25
end tell

To check free/busy availability within your working hours:

tell application "BusyCal"
set dayStart to current date
set freeBusyDays to query availability given startDate:dayStart, endDate:(dayStart + (2 * days)), kind:"both", minimumDurationMinutes:30, respectWorkingHours:true
end tell

To find your next 60-minute free slot:

tell application "BusyCal"
set nextSlot to find next available given minimumDurationMinutes:60, respectWorkingHours:true
end tell

You can also run scripts from Terminal using osascript:

osascript -e 'tell application "BusyCal" to list accounts'

For multi-line scripts, save the script to a file (e.g. myscript.applescript) and run:

osascript myscript.applescript
Parameter labels

Parameter names are flexible: startDate, start date, and start_date all work. This guide uses camelCase (e.g. startDate) for consistency.

Command Summary​

Utility commands:

  • get URL: open a BusyCal URL command

Read commands:

Navigation:

  • open item: reveal an event or task in the BusyCal window

Reference:

Write commands:

Utility​

get URL​

Opens a busycal://... URL, just like clicking a BusyCal link.

ArgumentTypeRequiredDescription
Direct parameterStringYesThe URL to open, for example busycal://new or other supported BusyCal URL actions.

Identity Fields​

Query and create commands return records with the following identity fields:

FieldDescription
idUnique identifier for this occurrence. Use this value as itemID in update, move, defer, complete, uncomplete, and delete commands.
seriesUIDShared identifier across all occurrences of a recurring item.
occurrenceDateThe date of this particular occurrence.
managedObjectURIInternal object reference.
note

Because id and isCompleted are AppleScript reserved words, wrap them in pipes when reading from a record: |id| of someRecord, |isCompleted| of someRecord.

Accounts and Calendars​

list accounts​

Returns a list of account records.

By default, BusyCal returns only accounts that are currently enabled. Set includeDisabled:true if you also want disabled accounts.

ArgumentTypeRequiredDescription
includeDisabledBooleanNoInclude disabled accounts. Default: false.

Returned fields: id, title, serviceName, serviceType, isEnabled, isPendingAddition

list calendars​

Returns a list of calendar records, optionally filtered to a single account.

By default, BusyCal only returns calendars whose owning account is currently enabled. Set includeDisabledAccounts:true if you also want calendars from disabled accounts.

ArgumentTypeRequiredDescription
accountIDStringNoLimit results to calendars belonging to this account.
includeDisabledAccountsBooleanNoInclude calendars from disabled accounts. Default: false.
includeWebDAVBooleanNoInclude WebDAV subscription calendars. Default: true.
includeContactCalendarsBooleanNoInclude birthday and anniversary calendars. Default: true.
includeHolidayCalendarsBooleanNoInclude holiday calendars. Default: true.

Returned fields: calendarID, accountID, title, isSubscribed, supportsEvents, supportsTasks, isDefaultForEvents, isDefaultForTasks, isWritable, color

list calendar sets​

Returns your saved Calendar Sets, with the built-in All Calendars set first and the rest in the order you arranged them. The command takes no arguments.

Returned fields: id, title, calendarIDs, sortOrder

The calendarIDs values match the calendarID values from list calendars, so you can use a set to narrow a later query to that set's calendars. See List your Calendar Sets.

On the Mac, a Calendar Set is a Smart Filter saved with its Remember calendar set (visible calendars) option turned on. Smart Filters saved without this option are not offered as Calendar Sets to Siri, Shortcuts, the Focus filter, AppleScript, or the Claude Desktop extension. See Smart Filters.

Querying Items​

query items​

Searches across events, tasks, journals, graphics, and stickies in a single call.

ArgumentTypeRequiredDescription
searchTextStringNoText to search for. Omit or pass an empty string to list items by date only.
searchFieldsStringNoComma-separated fields to search: "title", "notes", "tags". When omitted, BusyCal searches all text fields.
calendarIDStringNoLimit results to a specific calendar. Use the calendarID value from list calendars.
startDateDate or StringNoStart of date range. Returns items on or after this date. Accepts AppleScript dates or strings like "2026-03-14", "2026-03-14 15:30", or "2026-03-14T15:30:00".
endDateDate or StringNoEnd of date range. If startDate is omitted, BusyCal uses the current time as the start. Day-only strings like "2026-03-14" include the full day.
itemTypesStringNoComma-separated types to include: "event", "task", "journal", "graphic", "sticky". When omitted, all types are returned.
modeStringNo"uiConsistent" (default) or "rawExhaustive". See Query Modes.
fetchLimitNumberNoMaximum number of results. Set a positive number to limit results, or 0 for unlimited. When both searchText and endDate are omitted, the default limit is 100. Other queries have no default cap.

Returned fields: id, seriesUID, type, title, calendarID, occurrenceDate, managedObjectURI, startDate, endDate, dueDate, location, locationURL, url, tags, myTags, notes, alarms, travelTime, isCompleted, priority, isStarred, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, startTimeZoneIdentifier, endTimeZoneIdentifier, dueTimeZoneIdentifier

The type field tells you what kind of item each record is (e.g. "event", "task", "journal"). Fields like dueDate, startDate, endDate, location, locationURL, url, tags, myTags, notes, travelTime, and isCompleted are only present when they apply to that item type. travelTime is present on event records only (see Travel Time). isFloating tells you whether BusyCal currently treats the item as floating time. For fixed-time items, BusyCal returns per-field timezone identifiers. See Timezone Fields.

query events​

Returns a list of event records matching the given criteria.

ArgumentTypeRequiredDescription
searchTextStringNoText to search for. Omit or pass an empty string to list events by date only.
searchFieldsStringNoComma-separated fields to search: "title", "notes", "tags". When omitted, BusyCal searches all text fields.
calendarIDStringNoLimit results to a specific calendar.
startDateDate or StringNoStart of date range. Returns events on or after this date. Accepts AppleScript dates or strings like "2026-03-14".
endDateDate or StringNoEnd of date range. If startDate is omitted, BusyCal uses the current time as the start. Day-only strings include the full day.
modeStringNo"uiConsistent" (default) or "rawExhaustive". See Query Modes.
fetchLimitNumberNoMaximum number of results. Set a positive number to limit results, or 0 for unlimited. When both searchText and endDate are omitted, the default limit is 100. Other queries have no default cap.

Returned fields: id, seriesUID, type, title, calendarID, occurrenceDate, managedObjectURI, startDate, endDate, location, locationURL, url, tags, myTags, notes, alarms, travelTime, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, startTimeZoneIdentifier, endTimeZoneIdentifier

query tasks​

Returns a list of task records matching the given criteria. Takes the same arguments as query events.

Returned fields: id, seriesUID, type, title, calendarID, occurrenceDate, managedObjectURI, dueDate, location, locationURL, url, tags, myTags, notes, alarms, isCompleted, priority, isStarred, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, dueTimeZoneIdentifier

Availability​

These commands return free/busy time slots instead of individual items. They accept the same date formats as the query commands.

When respectWorkingHours is true (the default), results are limited to:

  • your configured working days
  • your Day starts at and Day ends at times (set in BusyCal > Settings > General)

When calendarIDs is omitted, BusyCal checks availability across all accounts, using:

  • event-capable calendars only
  • holiday and WebDAV subscription calendars are excluded

If you pass specific calendarIDs, BusyCal uses exactly those calendars, even if they are holiday or subscription calendars.

query availability​

Returns one record per day showing free and/or busy time slots.

ArgumentTypeRequiredDescription
startDateDate or StringNoStart of the range. Defaults to today. Accepts AppleScript dates or strings like "2026-03-14" or "2026-03-14T15:30:00".
endDateDate or StringNoEnd of the range. Defaults to the same day as startDate. Day-only strings like "2026-03-14" include the full day.
calendarIDsStringNoComma-separated calendar identifiers from list calendars. When omitted, BusyCal uses its default availability scope.
kindStringNoWhat to return: "free" (default), "busy", or "both".
minimumDurationMinutesNumberNoOnly return free slots at least this many minutes long. Does not filter busy slots.
respectWorkingHoursBooleanNoWhen true (default), limits results to your configured working days and hours.
busyTypesStringNoComma-separated states to treat as busy: "busy", "tentative", "outOfOffice", "workingElsewhere", "unknown". When omitted, all non-free states count as busy.
modeStringNo"uiConsistent" (default) or "rawExhaustive". See Query Modes.

Each day record contains:

FieldDescription
dateThe start of this day. Interpret in the returned timeZoneIdentifier.
timeZoneIdentifierBusyCal's active timezone identifier. Use this to interpret all dates in the record.
windowStartDateStart of the working/available window for this day.
windowEndDateEnd of the working/available window for this day.
isWorkingDaytrue if this day is a configured working day.
busySpansList of busy time slots (each with startDate, endDate, durationMinutes, timeZoneIdentifier).
freeSpansList of free time slots (each with startDate, endDate, durationMinutes, timeZoneIdentifier).

Example:

tell application "BusyCal"
set freeBusyDays to query availability given startDate:"2026-03-17", endDate:"2026-03-19", kind:"both", minimumDurationMinutes:30, respectWorkingHours:true
end tell

find next available​

Finds the first free time slot that is long enough, or returns missing value if none exists in the range.

ArgumentTypeRequiredDescription
startDateDate or StringNoStart searching from this time. Defaults to now. Accepts the same date formats as query availability.
endDateDate or StringNoStop searching at this time. When omitted, the search runs through the end of the seventh calendar day counted from the start day. Day-only strings like "2026-03-14" include the full day.
calendarIDsStringNoComma-separated calendar identifiers from list calendars. When omitted, BusyCal uses its default availability scope.
minimumDurationMinutesNumberNoMinimum length of the free slot in minutes. Default: 30.
respectWorkingHoursBooleanNoWhen true (default), only looks within your configured working days and hours.
busyTypesStringNoComma-separated states to treat as busy: "busy", "tentative", "outOfOffice", "workingElsewhere", "unknown". When omitted, all non-free states count as busy.
modeStringNo"uiConsistent" (default) or "rawExhaustive". See Query Modes.

Returned fields: startDate, endDate, durationMinutes, timeZoneIdentifier

Example:

tell application "BusyCal"
set nextSlot to find next available given startDate:"2026-03-17 09:00", endDate:"2026-03-20 18:00", minimumDurationMinutes:60, respectWorkingHours:true
end tell

Query Modes​

ModeBehavior
uiConsistentReturns items matching what you see in BusyCal - respects checked/unchecked calendars and visibility settings. This is the default.
rawExhaustiveReturns all items regardless of visibility, including unchecked calendars, disabled accounts, hidden events, declined meetings, and canceled events.

Date Filtering​

All query and availability commands support flexible date ranges:

  • Start date only: query commands return items on or after that date. query availability ends at the following midnight, and find next available searches through the end of the seventh calendar day counted from the start day.
  • End date only: query commands start at the current time, and so does find next available. query availability starts at the beginning of today.
  • Both start and end: returns only what falls within that window.

You can pass native AppleScript dates or date strings in these formats: "2026-03-14", "2026-03-14 15:30", "2026-03-14T15:30:00", "2026-03-14 15:30:00", "2026-03-14T15:30:00-05:00", "2026-03-14T15:30:00Z", or "2026-03-14 15:30:00 +0400". A day-only endDate like "2026-03-14" includes the entire day.

For create, move, and defer commands, BusyCal interprets date strings this way:

  • Strings without a time zone (for example "2026-03-17T14:00:00") are treated as local wall-clock times in BusyCal's active app time zone. If you have overridden the app's time zone in BusyCal, that override is used. Otherwise BusyCal uses your current local time zone.
  • Strings with Z or an explicit offset (for example "2026-03-17T14:00:00+04:00" or "2026-03-17 14:00:00 +0400") are treated as fixed-time timestamps. BusyCal preserves that absolute instant instead of reinterpreting it as floating time.
  • Native AppleScript date values also preserve the exact instant AppleScript resolved before handing it to BusyCal.
  • For a single create event call, keep startDate and endDate in the same style. In other words, don't mix a native AppleScript date or explicit-offset timestamp with an unzoned local-time string in the same request.
  • Returned event and task records include isFloating and, for fixed-time items, per-field timezone identifiers so your script can tell which behavior BusyCal applied. See Timezone Fields for details.

Relative Alarms​

BusyCal's automation commands support relative reminders via the alarms parameter:

  • Create commands (create event, create task):
    • omit alarms to keep BusyCal's normal default reminders
    • pass a list such as {-60, -15} to create exactly those reminders instead
  • Update commands (update event, update task):
    • omit alarms to leave reminders unchanged
    • pass {} to clear all reminders
    • pass a non-empty list such as {-30} to add those reminder offsets if they are not already present

Values are signed relative minutes:

  • -15: 15 minutes before
  • -1440: 1 day before

BusyCal continues to respect the capabilities of the underlying account. If a service cannot store a requested reminder remotely, BusyCal automatically falls back to a local reminder instead.

Travel Time​

An event can carry a travel time, the number of minutes you need to get there before it starts. Both the travelTime argument and the travelTime field in returned records are whole minutes. Every event record includes travelTime, and the value is 0 when the event has no travel time. Task records do not include it.

  • create event: omit travelTime for no travel time, or pass a positive number of minutes.
  • update event: omit travelTime to leave it unchanged, pass 0 to remove it, or pass a positive number of minutes to set it. Negative values are rejected. For a recurring event, scope applies the same way it does to the other update fields.

Travel time applies to timed events only. All-day events reject it, and so do events on Apple Calendar accounts, because that account type does not store travel time. In both cases the command returns an error and the event is left as it was. Setting travel time does not move the event's start time and does not change its reminders.

Requires BusyCal version 2026.3.2 or later.

tell application "BusyCal"
set matches to query events given searchText:"Client call", startDate:(current date), fetchLimit:1

if (count of matches) > 0 then
set firstMatch to item 1 of matches

-- Give the event 30 minutes of travel time
set updatedEvent to update event given itemID:(|id| of firstMatch), scope:"all", travelTime:30
log "Travel time: " & (travelTime of updatedEvent) -- 30
end if
end tell

The starting location and the Time to Leave alert are set in BusyCal itself, not through AppleScript. See Locations, Maps & Travel Time for how BusyCal calculates travel time.

Timezone Fields​

Returned records include timezone metadata so your script can tell whether BusyCal stored an item as floating time or as a fixed-time instant with a specific timezone.

FieldPresent onDescription
isFloatingAll itemstrue when BusyCal treats the item as floating (local wall-clock) time.
occurrenceTimeZoneIdentifierAll itemsTimezone for the occurrence date, when stored.
startTimeZoneIdentifierEventsTimezone for the start date, when stored.
endTimeZoneIdentifierEventsTimezone for the end date, when stored.
dueTimeZoneIdentifierTasksTimezone for the due date, when stored.
timeZoneIdentifierAll itemsCompatibility alias. Older scripts may use this; new scripts should use the per-field identifiers above.

An event whose start and end are in different timezones (for example a flight departing in one timezone and arriving in another) will return different values for startTimeZoneIdentifier and endTimeZoneIdentifier. UTC timestamps return "GMT" as the timezone identifier.

Creating Events and Tasks​

create natural language item​

Creates an event, task, or journal by passing natural-language text through BusyCal's quick-entry parser, the same parser used by the Quick Entry window in BusyCal. Returns the created item record.

You can call this command using either AppleScript name:

  • create natural language item
  • quick add item
ArgumentTypeRequiredDescription
textStringYesNatural-language quick-entry text, such as "Lunch with John tomorrow at 2pm" or "Finish report Friday".
itemTypeStringYesThe type of item to create: "event", "task", or "journal".
calendarIDStringNoTarget calendar. If omitted, BusyCal uses its default calendar for the requested item type.
notesStringNoNotes to attach to the item after parsing.

Returned fields: Same as the item's query result: id, seriesUID, type, title, calendarID, occurrenceDate, managedObjectURI, plus date, alarm, and timezone fields appropriate to the item type.

create event​

Creates a new event and returns the created event record.

ArgumentTypeRequiredDescription
titleStringYesEvent title.
startDateDate or StringYesEvent start date and time. A date-only value like "2026-04-14" creates an all-day event. Accepts AppleScript dates or unzoned wall-clock strings. For string inputs in a specific named time zone, omit any embedded offset and pair the value with startTimeZoneIdentifier.
endDateDate or StringNoEvent end date and time. If omitted, BusyCal uses a default duration. For all-day events, a date-only value is the inclusive final day, so startDate:"2026-04-14" with endDate:"2026-04-19" runs through April 19. Accepts AppleScript dates or unzoned wall-clock strings. For string inputs in a specific named time zone, omit any embedded offset and pair the value with endTimeZoneIdentifier.
startTimeZoneIdentifierStringNoIANA time zone identifier for the timed startDate, such as "America/Los_Angeles". Omit to use BusyCal's current app time zone.
endTimeZoneIdentifierStringNoIANA time zone identifier for the timed endDate, such as "America/New_York". Omit to use BusyCal's current app time zone.
calendarIDStringNoTarget calendar. If omitted, uses the default calendar.
allDayBooleanNoSet to true for an all-day event. For all-day events, prefer date-only startDate/endDate values.
locationStringNoEvent location, such as "Conference Room B" or "1 Apple Park Way, Cupertino, CA 95014".
alarmsList of NumbersNoRelative reminder minutes, such as {-60, -15}. Only include when reminders were explicitly requested. Omit to keep BusyCal's default reminders.
travelTimeIntegerNoMinutes of travel time before the event starts. Omit for no travel time. Timed events only. See Travel Time.
notesStringNoEvent notes.

Returned fields: id, seriesUID, calendarID, managedObjectURI, occurrenceDate, title, startDate, endDate, location, locationURL, url, tags, myTags, notes, alarms, travelTime, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, startTimeZoneIdentifier, endTimeZoneIdentifier

create task​

Creates a new task and returns the created task record.

ArgumentTypeRequiredDescription
titleStringYesTask title.
dueDateDate or StringNoDue date and time. If omitted, creates an undated task. A date-only value like "2026-04-14" creates an all-day task due that day. Accepts AppleScript dates or unzoned wall-clock strings. For timed string inputs in a specific named time zone, omit any embedded offset and pair the value with dueTimeZoneIdentifier.
dueTimeZoneIdentifierStringNoIANA time zone identifier for the timed dueDate, such as "America/Los_Angeles". Requires dueDate. Omit to use BusyCal's current app time zone.
calendarIDStringNoTarget calendar. If omitted, uses the default calendar.
priorityStringNoTask priority: "none", "low", "medium", or "high". Omit to use BusyCal's default task priority. Use "none" to create the task with no priority.
isStarredBooleanNoBusyCal starred status. Set to true to star the task or false to leave it unstarred.
alarmsList of NumbersNoRelative reminder minutes, such as {-1440, -60}. Only include when reminders were explicitly requested. Omit to keep BusyCal's default reminders. Undated tasks cannot accept explicit alarms.
notesStringNoTask notes.

Returned fields: id, seriesUID, calendarID, managedObjectURI, occurrenceDate, title, dueDate, location, locationURL, url, tags, myTags, notes, alarms, isCompleted, priority, isStarred, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, dueTimeZoneIdentifier

create task calendar​

Creates a new task calendar and returns its calendar record. Pass the name as either name or title; one of the two is required, and it must be 1 to 32 characters.

There is no account argument. BusyCal creates the calendar on the remote account that owns your default task calendar, and refuses local accounts and accounts that cannot create calendars.

ArgumentTypeRequiredDescription
nameStringYesName for the new task calendar, 1 to 32 characters. title is accepted as an alias.

Returned fields: calendarID, accountID, title, isSubscribed, supportsEvents, supportsTasks, isDefaultForEvents, isDefaultForTasks, isWritable, color

See Create a task calendar.

Duplicating​

duplicate item​

Duplicates an event or task using BusyCal's copy-and-paste edit pipeline. Returns the duplicated item record.

Omit calendarID to duplicate onto the same calendar as the source item. For recurring items, the scope defaults to "one" (only the selected occurrence). move item uses the same default, and most other mutation commands default to "all".

ArgumentTypeRequiredDescription
itemIDStringYesThe item's id from a query or create result.
calendarIDStringNoDestination calendar. If omitted, duplicates onto the source calendar.
scopeStringNoRecurrence scope. Default: "one".

Returned fields: Same as query items: id, seriesUID, type, title, calendarID, occurrenceDate, managedObjectURI, plus date, location, URL, tag, alarm, travel time, and timezone fields appropriate to the item type.

Selection​

selected items​

Returns the events, tasks, or other items the user currently has selected in the BusyCal main window. Useful when you want to act on whatever the user is already looking at instead of re-running a query.

This command takes no arguments. The result list preserves the order the BusyCal main window reports, typically top-to-bottom for list views and selection order for the calendar grid. If nothing is selected, the result is an empty list.

Records use the same unified shape as query items, so you can use the same field accessors (title, type, calendarID, etc.) and pass the resulting id straight into update, move, defer, complete, duplicate, or delete commands.

Returned fields: Same as query items: id, seriesUID, type, title, calendarID, occurrenceDate, managedObjectURI, plus date, location, URL, tag, alarm, travel time, and timezone fields appropriate to the item type.

tell application "BusyCal"
set selectedRecords to selected items

if (count of selectedRecords) is 0 then
log "Nothing selected in BusyCal."
else
repeat with itemRecord in selectedRecords
log (type of itemRecord) & ": " & (title of itemRecord)
end repeat
end if
end tell

open item​

Reveals an event or task in the BusyCal window, selecting it and opening its Info Panel. Returns the revealed item record.

ArgumentTypeRequiredDescription
itemIDStringYesThe item's id from a query or create result.

Returned fields: Same as the item's query result (id, seriesUID, type, title, calendarID, occurrenceDate, managedObjectURI, plus date and timezone fields appropriate to the item type).

Updating and Moving​

move item​

Moves an event or task to another calendar. Returns the moved item record. The destination calendar must be writable and must support the item's type.

For recurring events, scope defaults to "one" (only the selected occurrence), unlike most commands which default to "all". Tasks always move as a single item, because recurring tasks never detach.

ArgumentTypeRequiredDescription
itemIDStringYesThe item's id from a query or create result.
calendarIDStringYesDestination calendar. Use the calendarID value from list calendars.
scopeStringNoRecurrence scope. Default: "one".

Returned fields: Same as query items. A moved occurrence can come back with a new id, so use the returned value for any further commands.

See Move an item to another calendar.

move event​

Reschedules an event to a new date and time. The event's original duration is preserved. Returns the updated event record.

ArgumentTypeRequiredDescription
itemIDStringYesThe event's id from a query or create result.
scopeStringNoRecurrence scope. Default: "all".
startDateDate or StringYesNew start date and time. Accepts AppleScript dates or unzoned wall-clock strings. For string inputs in a specific named time zone, omit any embedded offset and pair the value with startTimeZoneIdentifier.
startTimeZoneIdentifierStringNoIANA time zone identifier for the timed startDate, such as "America/Los_Angeles". Omit to use BusyCal's current app time zone.

Returned fields: id, seriesUID, calendarID, managedObjectURI, occurrenceDate, title, startDate, endDate, location, locationURL, url, tags, myTags, notes, travelTime, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, startTimeZoneIdentifier, endTimeZoneIdentifier

reschedule event​

Changes an event's start, end, or all-day state in one call. Returns the updated event record. Send at least one of startDate, endDate, or allDay, and send every schedule field you want to change. Changing only the start also moves the end to preserve the duration, so supply an endDate when you want a particular length. Changing the all-day state applies BusyCal's normal conversion rules, including availability and default reminders.

Use this when you need to change an event's length or turn it into an all-day event. Use move event when you only want to shift the start and keep the same duration.

ArgumentTypeRequiredDescription
itemIDStringYesThe event's id from a query or create result.
scopeStringNoRecurrence scope. Default: "all".
startDateDate or StringNoNew start date and time. Accepts AppleScript dates or unzoned wall-clock strings. startDate and endDate must use the same style.
endDateDate or StringNoNew end date and time. On an all-day event, a date-only value is the inclusive final day, so "2026-04-19" runs through April 19. An end before the start is rejected.
allDayBooleanNoNew all-day state. Turning an all-day event into a timed one without new times keeps the day and applies BusyCal's default duration and floating-time preference.
startTimeZoneIdentifierStringNoIANA time zone identifier for a timed startDate, such as "America/Los_Angeles". Requires startDate, and is not accepted on all-day events.
endTimeZoneIdentifierStringNoIANA time zone identifier for a timed endDate, such as "America/New_York". Requires endDate, and is not accepted on all-day events.

While the event stays timed, omitting the time zone identifiers keeps its stored time style, so a floating event stays floating and a fixed-zone event keeps its zones. Passing startTimeZoneIdentifier or endTimeZoneIdentifier replaces the stored zone for that end of the event. Changing between timed and all day uses BusyCal's normal date and time defaults instead.

Returned fields: Same as move event. For an occurrence that still belongs to the series, the result is the detached occurrence, the new future series, or the master, according to scope. An occurrence you already edited separately is rescheduled in place and returned as itself, whatever scope you pass.

See Reschedule an event.

update event​

Updates an event's title, location, notes, URL, tags, reminders, or travel time. Returns the updated event record. You must provide at least one field to change.

ArgumentTypeRequiredDescription
itemIDStringYesThe event's id.
scopeStringNoRecurrence scope. Default: "all".
titleStringNoNew title.
locationStringNoNew location. Pass an empty string to clear it.
notesStringNoNew notes. Pass an empty string to clear it.
urlStringNoNew item URL. Pass an empty string to clear it. Omit to leave unchanged.
locationURLStringNoNew location URL. Pass an empty string to clear it. Omit to leave unchanged.
tagsStringNoComma-separated shared tags. The list you pass replaces the item's shared tags, so include every tag you want to keep. Pass an empty string to clear all shared tags. Omit to leave unchanged.
myTagsStringNoComma-separated My Tags. Pass an empty string to clear all My Tags. Omit to leave unchanged.
alarmsList of NumbersNoRelative reminder minutes. Pass {} to clear all reminders, omit to leave them unchanged, or pass a non-empty list to add those reminder offsets.
travelTimeIntegerNoNew travel time in minutes before the event starts. Pass 0 to remove it. Omit to leave unchanged. Timed events only. See Travel Time.

update task​

Updates a task's title, notes, URL, tags, reminders, priority, or BusyCal starred status. Returns the updated task record. You must provide at least one field to change.

ArgumentTypeRequiredDescription
itemIDStringYesThe task's id.
scopeStringNoRecurrence scope. Default: "all".
titleStringNoNew title.
priorityStringNoNew priority: "none", "low", "medium", or "high". Use "none" to clear priority. Omit to leave unchanged. Repeating tasks require scope:"one".
isStarredBooleanNoSet to true to star the task or false to unstar it. Omit to leave unchanged. Repeating tasks require scope:"one".
notesStringNoNew notes. Pass an empty string to clear it.
urlStringNoNew item URL. Pass an empty string to clear it. Omit to leave unchanged.
locationURLStringNoNew location URL. Pass an empty string to clear it. Omit to leave unchanged.
tagsStringNoComma-separated shared tags. The list you pass replaces the item's shared tags, so include every tag you want to keep. Pass an empty string to clear all shared tags. Omit to leave unchanged.
myTagsStringNoComma-separated My Tags. Pass an empty string to clear all My Tags. Omit to leave unchanged.
alarmsList of NumbersNoRelative reminder minutes. Pass {} to clear all reminders, omit to leave them unchanged, or pass a non-empty list to add those reminder offsets.

defer task​

Changes a task's due date. Returns the updated task record.

ArgumentTypeRequiredDescription
itemIDStringYesThe task's id.
scopeStringNoRecurrence scope. Default: "all".
dueDateDate or StringNoNew due date. Omit to make the task undated. Accepts AppleScript dates or unzoned wall-clock strings. For timed string inputs in a specific named time zone, omit any embedded offset and pair the value with dueTimeZoneIdentifier.
dueTimeZoneIdentifierStringNoIANA time zone identifier for the timed dueDate, such as "America/Los_Angeles". Requires dueDate. Omit to use BusyCal's current app time zone.

Returned fields: id, seriesUID, calendarID, managedObjectURI, occurrenceDate, title, dueDate, location, locationURL, url, tags, myTags, notes, isCompleted, priority, isStarred, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, dueTimeZoneIdentifier

complete task​

Marks a task as complete. Returns the updated task record.

ArgumentTypeRequiredDescription
itemIDStringYesThe task's id.
scopeStringNoRecurrence scope. Default: "all".
completedAtDateNoCompletion timestamp. If omitted, uses the current time.

uncomplete task​

Marks a completed task as not completed. Returns the updated task record.

You can call this command using either AppleScript name:

  • uncomplete task
  • reopen task
ArgumentTypeRequiredDescription
itemIDStringYesThe task's id.
scopeStringNoRecurrence scope. Default: "all".

Deleting​

delete event​

Deletes an event. Returns a record with the deleted item's id and deleted:true.

ArgumentTypeRequiredDescription
itemIDStringYesThe event's id.
scopeStringNoRecurrence scope. Default: "all".

delete task​

Deletes a task. Returns a record with the deleted item's id and deleted:true.

ArgumentTypeRequiredDescription
itemIDStringYesThe task's id.
scopeStringNoRecurrence scope. Default: "all".

Recurrence Scope​

Commands that modify or delete items accept a scope argument to control which occurrences are affected:

ScopeMeaning
"one"This occurrence only.
"future"This and all future occurrences.
"all"All occurrences (the entire series).

Most mutation commands use "all" when you omit scope. duplicate item and move item use "one" instead, so check the command's scope rules before leaving the argument out.

For non-recurring items, the scope is ignored - the single item is always affected.

For a repeating task, changing priority or starred status requires scope:"one", which edits the one stored recurring task. Set the scope explicitly, because omitting it defaults to "all" and is rejected for these changes.

Examples​

List accounts and calendars​

tell application "BusyCal"
-- Get all enabled accounts
set accounts to list accounts

repeat with accountRecord in accounts
set accountID to |id| of accountRecord
set accountTitle to title of accountRecord

-- Get calendars for this account
set accountCalendars to list calendars given accountID:accountID

log "Account: " & accountTitle & " (" & (count of accountCalendars) & " calendars)"

repeat with cal in accountCalendars
log " - " & title of cal & " (events: " & supportsEvents of cal & ", tasks: " & supportsTasks of cal & ")"
end repeat
end repeat
end tell

Query events for the next 7 days​

tell application "BusyCal"
set today to current date
set weekOut to today + (7 * days)

-- Find events containing "review" in the next week
set matches to query events given searchText:"review", startDate:today, endDate:weekOut, mode:"uiConsistent", fetchLimit:50

repeat with evt in matches
log (title of evt) & " - " & (startDate of evt) & " to " & (endDate of evt)
end repeat
end tell

Query tasks for the next 14 days​

tell application "BusyCal"
set today to current date
set twoWeeksOut to today + (14 * days)

-- Find tasks containing "follow up" in the next two weeks
set matches to query tasks given searchText:"follow up", startDate:today, endDate:twoWeeksOut, mode:"uiConsistent", fetchLimit:50

repeat with t in matches
set status to "open"
if |isCompleted| of t then set status to "done"
log (title of t) & " - due: " & (dueDate of t) & " [" & status & "]"
end repeat
end tell

Create and move an event​

-- Set up a start time: tomorrow at 10:00 AM
set startDateValue to current date
set startDateValue to startDateValue + (1 * days)
set hours of startDateValue to 10
set minutes of startDateValue to 0
set seconds of startDateValue to 0

-- One hour duration
set endDateValue to startDateValue + (60 * minutes)

-- Where we want to move it: two days later
set movedStart to startDateValue + (2 * days)

tell application "BusyCal"
-- Create the event
set createdEvent to create event given title:"Team Standup", startDate:startDateValue, endDate:endDateValue, allDay:false, location:"Conference Room A", notes:"Weekly sync"

-- Move it two days later (duration is preserved automatically)
set movedEvent to move event given itemID:(|id| of createdEvent), scope:"all", startDate:movedStart

log "Event moved to: " & (startDate of movedEvent)
end tell

Create a cross-timezone event​

tell application "BusyCal"
-- Create a flight departing San Francisco at 9 AM Pacific and arriving in New York at 5 PM Eastern
set flightEvent to create event given title:"Flight SFO → JFK", startDate:"2026-04-14T09:00:00", startTimeZoneIdentifier:"America/Los_Angeles", endDate:"2026-04-14T17:00:00", endTimeZoneIdentifier:"America/New_York"

log "Start TZ: " & (startTimeZoneIdentifier of flightEvent) -- America/Los_Angeles
log "End TZ: " & (endTimeZoneIdentifier of flightEvent) -- America/New_York
end tell

Duplicate an event to another calendar​

tell application "BusyCal"
-- Find the event to duplicate
set matches to query events given searchText:"Team Standup", startDate:(current date), endDate:((current date) + (7 * days)), fetchLimit:1

if (count of matches) > 0 then
set sourceEvent to item 1 of matches
-- Duplicate just this occurrence onto the "Personal" calendar
set allCalendars to list calendars
set personalCalID to missing value
repeat with cal in allCalendars
if title of cal is "Personal" and supportsEvents of cal then
set personalCalID to calendarID of cal
exit repeat
end if
end repeat

if personalCalID is not missing value then
set duplicated to duplicate item given itemID:(|id| of sourceEvent), calendarID:personalCalID, scope:"one"
log "Duplicated: " & (title of duplicated) & " on calendar " & (calendarID of duplicated)
end if
end if
end tell

Update an event​

-- Create an event, then update its title, location, and tags
set startDateValue to current date
set startDateValue to startDateValue + (1 * days)
set hours of startDateValue to 14
set minutes of startDateValue to 0
set seconds of startDateValue to 0
set endDateValue to startDateValue + (45 * minutes)

tell application "BusyCal"
set createdEvent to create event given title:"Planning Meeting", startDate:startDateValue, endDate:endDateValue

-- Update the title, add a location, and set tags
set updatedEvent to update event given itemID:(|id| of createdEvent), scope:"all", title:"Q2 Planning Meeting", location:"Conference Room B", tags:"work, planning", url:"https://example.com/q2-agenda"

log "Updated: " & (title of updatedEvent) & " at " & (location of updatedEvent)
end tell

Create, complete, and uncomplete a task​

-- Set a due date: two days from now at 5:00 PM
set dueDateValue to current date
set dueDateValue to dueDateValue + (2 * days)
set hours of dueDateValue to 17
set minutes of dueDateValue to 0
set seconds of dueDateValue to 0

tell application "BusyCal"
-- Create the task
set createdTask to create task given title:"Review pull requests", dueDate:dueDateValue

-- Mark it complete
set completedTask to complete task given itemID:(|id| of createdTask), scope:"all", completedAt:(current date)
log "Completed: " & |isCompleted| of completedTask -- true

-- Mark as not completed
set uncompletedTask to uncomplete task given itemID:(|id| of completedTask), scope:"all"
log "Uncompleted: " & |isCompleted| of uncompletedTask -- false
end tell

Update and defer a task​

-- Due in 3 days
set dueDateValue to current date
set dueDateValue to dueDateValue + (3 * days)

tell application "BusyCal"
-- Create a high-priority starred task with notes
set createdTask to create task given title:"Write release notes", dueDate:dueDateValue, priority:"high", isStarred:true, notes:"Draft for v2.5"

-- Update the notes, clear priority, and unstar it
set updatedTask to update task given itemID:(|id| of createdTask), scope:"all", priority:"none", isStarred:false, notes:"Draft for v2.5 - include migration steps"
log "Priority: " & (|priority| of updatedTask) -- none
log "Starred: " & (|isStarred| of updatedTask) -- false

-- Push the due date back by 2 more days
set deferredTask to defer task given itemID:(|id| of updatedTask), scope:"all", dueDate:(dueDateValue + (2 * days))

log "New due date: " & (dueDate of deferredTask)
end tell

Create an event on a specific calendar​

tell application "BusyCal"
-- Find the "Work" calendar
set allCalendars to list calendars
set workCalID to missing value

repeat with cal in allCalendars
if title of cal is "Work" and supportsEvents of cal then
set workCalID to calendarID of cal
exit repeat
end if
end repeat

if workCalID is missing value then
log "No 'Work' calendar found."
return
end if

-- Create an event on the Work calendar
set startDateValue to current date
set startDateValue to startDateValue + (1 * days)
set hours of startDateValue to 9
set minutes of startDateValue to 0
set seconds of startDateValue to 0

set createdEvent to create event given title:"Client call", startDate:startDateValue, calendarID:workCalID
log "Created on calendar: " & (calendarID of createdEvent)
end tell

Delete an event​

set startDateValue to current date
set startDateValue to startDateValue + (1 * days)
set hours of startDateValue to 16
set minutes of startDateValue to 0
set seconds of startDateValue to 0
set endDateValue to startDateValue + (30 * minutes)

tell application "BusyCal"
set createdEvent to create event given title:"Temporary event", startDate:startDateValue, endDate:endDateValue

-- Delete the event (all occurrences)
set deletedRecord to delete event given itemID:(|id| of createdEvent), scope:"all"
log "Deleted event id: " & |id| of deletedRecord
end tell

Delete a task​

set dueDateValue to current date
set dueDateValue to dueDateValue + (1 * days)

tell application "BusyCal"
set createdTask to create task given title:"Temporary task", dueDate:dueDateValue

-- Delete the task
set deletedRecord to delete task given itemID:(|id| of createdTask), scope:"all"
log "Deleted task id: " & |id| of deletedRecord
end tell

Work with recurring events​

When you query recurring events, each occurrence is returned as a separate record with its own id. Use the scope parameter to control whether a change applies to one occurrence, this and future occurrences, or the entire series.

tell application "BusyCal"
set today to current date
set twoWeeksOut to today + (14 * days)

-- Find all occurrences of "Daily Standup" in the next two weeks
set standups to query events given searchText:"Daily Standup", startDate:today, endDate:twoWeeksOut

-- Move only tomorrow's occurrence to 30 minutes later
if (count of standups) > 1 then
set tomorrow to item 2 of standups
set originalStart to startDate of tomorrow
set movedEvent to move event given itemID:(|id| of tomorrow), scope:"one", startDate:(originalStart + (30 * minutes))
log "Moved one occurrence to: " & (startDate of movedEvent)
end if

-- Update the title for all future occurrences starting from the third one
if (count of standups) > 2 then
set thirdOccurrence to item 3 of standups
set updatedEvent to update event given itemID:(|id| of thirdOccurrence), scope:"future", title:"Morning Standup"
log "Renamed future occurrences: " & (title of updatedEvent)
end if
end tell

Quick add with natural language​

tell application "BusyCal"
-- Create an event using natural language
set newEvent to create natural language item given text:"Lunch with Sarah tomorrow at 1pm", itemType:"event"
log "Created event: " & (title of newEvent) & " at " & (startDate of newEvent)

-- Create a task using natural language on a specific calendar
set allCalendars to list calendars
set workCalID to missing value
repeat with cal in allCalendars
if title of cal is "Work" and supportsTasks of cal then
set workCalID to calendarID of cal
exit repeat
end if
end repeat

if workCalID is not missing value then
set newTask to quick add item given text:"Finish expense report Friday", itemType:"task", calendarID:workCalID, notes:"Q1 expenses"
log "Created task: " & (title of newTask)
end if
end tell

Open an item in BusyCal​

tell application "BusyCal"
-- Find the next event containing "standup"
set matches to query events given searchText:"standup", startDate:(current date), fetchLimit:1

if (count of matches) > 0 then
set firstMatch to item 1 of matches
set revealed to open item given itemID:(|id| of firstMatch)
log "Revealed: " & (title of revealed)
end if
end tell

Read the current BusyCal selection​

When your script needs to act on what the user already has selected in the BusyCal window, ask for the selection directly instead of re-running a query. The returned records share the same shape as query items, so the id is ready to feed into any update, move, defer, or delete command.

tell application "BusyCal"
set selectedRecords to selected items

if (count of selectedRecords) is 0 then
log "Nothing selected in BusyCal."
return
end if

-- Replace the shared tags on every selected item with "reviewed", using its record id
repeat with itemRecord in selectedRecords
set itemKind to type of itemRecord
set itemID to |id| of itemRecord

if itemKind is "event" then
update event given itemID:itemID, scope:"one", tags:"reviewed"
else if itemKind is "task" then
update task given itemID:itemID, scope:"one", tags:"reviewed"
end if
end repeat
end tell

Find events by keyword and update them​

tell application "BusyCal"
set today to current date
set monthOut to today + (30 * days)

-- Find all events with "TBD" in the title
set matches to query events given searchText:"TBD", startDate:today, endDate:monthOut

repeat with evt in matches
-- Add a location to each one
update event given itemID:(|id| of evt), scope:"all", location:"Room 101"
end repeat

log "Updated " & (count of matches) & " events"
end tell

Query mixed item types​

tell application "BusyCal"
set rangeStart to current date
set rangeEnd to rangeStart + (3 * days)
set itemsFound to query items given searchText:"review", startDate:rangeStart, endDate:rangeEnd, itemTypes:"event, task, journal", searchFields:"title", mode:"uiConsistent", fetchLimit:25

repeat with itemRecord in itemsFound
set itemType to type of itemRecord
set itemTitle to title of itemRecord
log "- " & itemType & ": " & itemTitle
end repeat
end tell

Check availability for the next 3 days​

tell application "BusyCal"
set today to current date
set threeDaysOut to today + (3 * days)

-- Get both free and busy slots, at least 30 minutes long, within working hours
set days to query availability given startDate:today, endDate:threeDaysOut, kind:"both", minimumDurationMinutes:30, respectWorkingHours:true

repeat with dayRecord in days
log "Date: " & (date of dayRecord) & " (working day: " & (isWorkingDay of dayRecord) & ")"

repeat with freeSlot in (freeSpans of dayRecord)
log " Free: " & (startDate of freeSlot) & " to " & (endDate of freeSlot) & " (" & (durationMinutes of freeSlot) & " min)"
end repeat

repeat with busySlot in (busySpans of dayRecord)
log " Busy: " & (startDate of busySlot) & " to " & (endDate of busySlot) & " (" & (durationMinutes of busySlot) & " min)"
end repeat
end repeat
end tell

Find the next free slot​

tell application "BusyCal"
-- Find the next 60-minute free slot within the next 5 working days
set today to current date
set fiveDaysOut to today + (5 * days)

set nextSlot to find next available given startDate:today, endDate:fiveDaysOut, minimumDurationMinutes:60, respectWorkingHours:true

if nextSlot is missing value then
log "No 60-minute slot available in the next 5 days."
else
log "Next free slot: " & (startDate of nextSlot) & " to " & (endDate of nextSlot) & " (" & (durationMinutes of nextSlot) & " min)"
end if
end tell

Create an event with explicit timezones​

When booking a flight or scheduling across timezones, you can pass ISO 8601 strings with explicit offsets. BusyCal preserves each timezone separately.

tell application "BusyCal"
-- Flight departing Los Angeles (Pacific) at 9 AM, arriving New York (Eastern) at 5 PM
set flight to create event given title:"Delta flight to New York", startDate:"2026-04-10T09:00:00-07:00", endDate:"2026-04-10T17:00:00-04:00", notes:"Seat 14A"

-- Check the stored timezones
log "Start TZ: " & (startTimeZoneIdentifier of flight)
log "End TZ: " & (endTimeZoneIdentifier of flight)
log "Floating: " & (isFloating of flight)
end tell

Date range filtering​

tell application "BusyCal"
-- Start date only: all events from now onward
set eventsAfterNow to query events given startDate:(current date), fetchLimit:20

-- End date only: tasks between now and 7 days out
set tasksThroughWeek to query tasks given endDate:((current date) + (7 * days)), fetchLimit:20

-- Both start and end: events in a 3-day window
set windowStart to current date
set windowEnd to windowStart + (3 * days)
set windowEvents to query events given startDate:windowStart, endDate:windowEnd, fetchLimit:20

-- You can also pass dates as strings
set isoResults to query items given searchText:"launch", startDate:"2026-03-14T09:00:00", endDate:"2026-03-14T18:00:00", itemTypes:"event, journal", fetchLimit:20
end tell

List your Calendar Sets​

tell application "BusyCal"
set theSets to list calendar sets
repeat with aSet in theSets
log (title of aSet) & " contains " & (count of (calendarIDs of aSet)) & " calendars"
end repeat
end tell

Create a task calendar​

tell application "BusyCal"
set newCalendar to create task calendar given name:"Home Projects"
log "Created " & (title of newCalendar) & " on account " & (accountID of newCalendar)
end tell

Move an item to another calendar​

tell application "BusyCal"
-- Find the task to move
set matches to query tasks given searchText:"Renew passport", startDate:(current date), endDate:((current date) + (30 * days)), fetchLimit:1

if (count of matches) > 0 then
set targetTask to item 1 of matches

-- Locate a task calendar named "Personal"
set personalCalID to missing value
repeat with cal in (list calendars)
if title of cal is "Personal" and supportsTasks of cal then
set personalCalID to calendarID of cal
exit repeat
end if
end repeat

if personalCalID is not missing value then
set movedTask to move item given itemID:(|id| of targetTask), calendarID:personalCalID
log "Moved to calendar " & (calendarID of movedTask)
end if
end if
end tell

Reschedule an event​

-- Turn a timed event into an all-day event that runs through April 19
tell application "BusyCal"
set matches to query events given searchText:"Conference", startDate:(current date), endDate:((current date) + (60 * days)), fetchLimit:1

if (count of matches) > 0 then
set theEvent to item 1 of matches
set updatedEvent to reschedule event given itemID:(|id| of theEvent), scope:"one", startDate:"2026-04-16", endDate:"2026-04-19", allDay:true
log "Now runs " & (startDate of updatedEvent) & " through " & (endDate of updatedEvent)
end if
end tell

Error Handling​

Commands report errors as standard AppleScript errors (error number -10000). Common errors include:

  • Invalid arguments: a required field is missing, the title is empty, endDate is before startDate when creating an event, or move event is called with an endDate argument.
  • Item not found: the id you passed does not match any existing item (it may have been deleted).
  • Edit rejected: the target calendar is read-only, the calendar doesn't support the item type, or BusyCal could not complete the change.
  • Not ready: BusyCal is still opening its database. Wait a moment and try again.

You can catch these with a standard try block:

tell application "BusyCal"
try
set result to create event given title:"", startDate:(current date)
on error errMsg
log "Error: " & errMsg
end try
end tell

Tips​

  • Always use the id returned from query or create commands as the itemID for update, move, and delete commands. Don't construct IDs yourself.
  • id and isCompleted are AppleScript reserved words, so wrap them in pipes when reading from a record: |id| of someRecord, |isCompleted| of someRecord.
  • Use the argument labels exactly as documented (e.g. startDate, searchText, calendarID, itemID).
  • Get the calendarID value from list calendars, then use it when creating items on a specific calendar or filtering queries.
  • For recurring items, set scope to control which occurrences are affected. Most mutation commands use "all" when you omit it, but duplicate item and move item use "one".
  • Use mode:"rawExhaustive" to include items from unchecked calendars, disabled accounts, and hidden events. The default "uiConsistent" mode only returns what's visible in BusyCal. See Query Modes.
  • Before creating an event or task on a specific calendar, check supportsEvents or supportsTasks from list calendars.
  • Every create and update command returns the resulting record, so you can chain operations by reading the id from each result.
  • When both searchText and endDate are omitted, the default limit is 100 results. Other queries have no default cap. Pass fetchLimit:0 if you want unlimited results.
  • Use searchFields to limit searches to specific fields like "title" or "notes" when you need more targeted matching.