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.
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.
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 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:
list accounts— list calendar accountslist calendars— list calendars, optionally filtered by accountquery items— search across events, tasks, journals, graphics, and stickiesquery events— search and filter eventsquery tasks— search and filter tasksquery availability— get free/busy time slots for a date rangefind next available— find the next free time slot
Navigation:
open item— reveal an event or task in the BusyCal window
Reference:
Timezone Fields— per-field timezone metadata on returned records
Write commands:
create natural language item/quick add item— create an event, task, or journal using natural-language textcreate event— create a new eventcreate task— create a new taskmove event— reschedule an eventupdate event— change an event's title, location, notes, or remindersupdate task— change a task's title, notes, or remindersdefer task— change a task's due datecomplete task— mark a task as completeuncomplete task/reopen task— mark a completed task as not completeddelete event— delete an eventdelete task— delete a task
Utility
get URL
Opens a busycal://... URL, just like clicking a BusyCal link.
| Argument | Type | Required | Description |
|---|---|---|---|
| Direct parameter | String | Yes | The 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:
| Field | Description |
|---|---|
id | Unique identifier for this occurrence. Use this value as itemID in update, move, defer, complete, uncomplete, and delete commands. |
seriesUID | Shared identifier across all occurrences of a recurring item. |
occurrenceDate | The date of this particular occurrence. |
managedObjectURI | Internal object reference. |
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.
| Argument | Type | Required | Description |
|---|---|---|---|
includeDisabled | Boolean | No | Include 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.
| Argument | Type | Required | Description |
|---|---|---|---|
accountID | String | No | Limit results to calendars belonging to this account. |
includeDisabledAccounts | Boolean | No | Include calendars from disabled accounts. Default: false. |
includeWebDAV | Boolean | No | Include WebDAV subscription calendars. Default: true. |
includeContactCalendars | Boolean | No | Include birthday and anniversary calendars. Default: true. |
includeHolidayCalendars | Boolean | No | Include holiday calendars. Default: true. |
Returned fields: calendarID, accountID, title, isSubscribed, supportsEvents, supportsTasks
Querying Items
query items
Searches across events, tasks, journals, graphics, and stickies in a single call.
| Argument | Type | Required | Description |
|---|---|---|---|
searchText | String | No | Text to search for. Omit or pass an empty string to list items by date only. |
searchFields | String | No | Comma-separated fields to search: "title", "notes", "tags". When omitted, BusyCal searches all text fields. |
calendarID | String | No | Limit results to a specific calendar. Use the calendarID value from list calendars. |
startDate | Date or String | No | Start 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". |
endDate | Date or String | No | End 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. |
itemTypes | String | No | Comma-separated types to include: "event", "task", "journal", "graphic", "sticky". When omitted, all types are returned. |
mode | String | No | "uiConsistent" (default) or "rawExhaustive". See Query Modes. |
fetchLimit | Number | No | Maximum number of results. 0 for unlimited. Queries without searchText are capped to 100 results by default. |
Returned fields: id, seriesUID, type, title, calendarID, occurrenceDate, managedObjectURI, startDate, endDate, dueDate, location, alarms, isCompleted, 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, and isCompleted are only present when they apply to that item type. 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.
| Argument | Type | Required | Description |
|---|---|---|---|
searchText | String | No | Text to search for. Omit or pass an empty string to list events by date only. |
searchFields | String | No | Comma-separated fields to search: "title", "notes", "tags". When omitted, BusyCal searches all text fields. |
calendarID | String | No | Limit results to a specific calendar. |
startDate | Date or String | No | Start of date range. Returns events on or after this date. Accepts AppleScript dates or strings like "2026-03-14". |
endDate | Date or String | No | End of date range. If startDate is omitted, BusyCal uses the current time as the start. Day-only strings include the full day. |
mode | String | No | "uiConsistent" (default) or "rawExhaustive". See Query Modes. |
fetchLimit | Number | No | Maximum number of results. 0 for unlimited. Queries without searchText are capped to 100 results by default. |
Returned fields: id, seriesUID, type, title, calendarID, occurrenceDate, managedObjectURI, startDate, endDate, location, alarms, 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, alarms, isCompleted, 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 atandDay ends attimes (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.
| Argument | Type | Required | Description |
|---|---|---|---|
startDate | Date or String | No | Start of the range. Defaults to today. Accepts AppleScript dates or strings like "2026-03-14" or "2026-03-14T15:30:00". |
endDate | Date or String | No | End of the range. Defaults to the same day as startDate. Day-only strings like "2026-03-14" include the full day. |
calendarIDs | String | No | Comma-separated calendar identifiers from list calendars. When omitted, BusyCal uses its default availability scope. |
kind | String | No | What to return: "free" (default), "busy", or "both". |
minimumDurationMinutes | Number | No | Only return free slots at least this many minutes long. Does not filter busy slots. |
respectWorkingHours | Boolean | No | When true (default), limits results to your configured working days and hours. |
busyTypes | String | No | Comma-separated states to treat as busy: "busy", "tentative", "outOfOffice", "workingElsewhere", "unknown". When omitted, all non-free states count as busy. |
mode | String | No | "uiConsistent" (default) or "rawExhaustive". See Query Modes. |
Each day record contains:
| Field | Description |
|---|---|
date | The start of this day. Interpret in the returned timeZoneIdentifier. |
timeZoneIdentifier | BusyCal's active timezone identifier. Use this to interpret all dates in the record. |
windowStartDate | Start of the working/available window for this day. |
windowEndDate | End of the working/available window for this day. |
isWorkingDay | true if this day is a configured working day. |
busySpans | List of busy time slots (each with startDate, endDate, durationMinutes, timeZoneIdentifier). |
freeSpans | List 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.
| Argument | Type | Required | Description |
|---|---|---|---|
startDate | Date or String | No | Start searching from this time. Defaults to now. Accepts the same date formats as query availability. |
endDate | Date or String | No | Stop searching at this time. Defaults to 7 days after startDate. Day-only strings like "2026-03-14" include the full day. |
calendarIDs | String | No | Comma-separated calendar identifiers from list calendars. When omitted, BusyCal uses its default availability scope. |
minimumDurationMinutes | Number | No | Minimum length of the free slot in minutes. Default: 30. |
respectWorkingHours | Boolean | No | When true (default), only looks within your configured working days and hours. |
busyTypes | String | No | Comma-separated states to treat as busy: "busy", "tentative", "outOfOffice", "workingElsewhere", "unknown". When omitted, all non-free states count as busy. |
mode | String | No | "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
| Mode | Behavior |
|---|---|
uiConsistent | Returns items matching what you see in BusyCal - respects checked/unchecked calendars and visibility settings. This is the default. |
rawExhaustive | Returns 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 — returns items on or after that date. For
find next available, searches from that moment through its default 7-day window. - End date only — BusyCal uses the current time as the start.
- Both start and end — returns only items 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
Zor 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
datevalues also preserve the exact instant AppleScript resolved before handing it to BusyCal. - For a single
create eventcall, keepstartDateandendDatein the same style. In other words, don't mix a native AppleScriptdateor explicit-offset timestamp with an unzoned local-time string in the same request. - Returned event and task records include
isFloatingand, 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
alarmsto keep BusyCal's normal default reminders - pass a list such as
{-60, -15}to create exactly those reminders instead
- omit
- Update commands (
update event,update task):- omit
alarmsto 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
- omit
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.
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.
| Field | Present on | Description |
|---|---|---|
isFloating | All items | true when BusyCal treats the item as floating (local wall-clock) time. |
occurrenceTimeZoneIdentifier | All items | Timezone for the occurrence date, when stored. |
startTimeZoneIdentifier | Events | Timezone for the start date, when stored. |
endTimeZoneIdentifier | Events | Timezone for the end date, when stored. |
dueTimeZoneIdentifier | Tasks | Timezone for the due date, when stored. |
timeZoneIdentifier | All items | Compatibility 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 itemquick add item
| Argument | Type | Required | Description |
|---|---|---|---|
text | String | Yes | Natural-language quick-entry text, such as "Lunch with John tomorrow at 2pm" or "Finish report Friday". |
itemType | String | Yes | The type of item to create: "event", "task", or "journal". |
calendarID | String | No | Target calendar. If omitted, BusyCal uses its default calendar for the requested item type. |
notes | String | No | Notes 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.
| Argument | Type | Required | Description |
|---|---|---|---|
title | String | Yes | Event title. |
startDate | Date or String | Yes | Event start date and time. A date-only value like "2026-04-14" creates an all-day event. Accepts AppleScript dates, local wall-clock strings, or ISO 8601 strings with an explicit time zone offset. |
endDate | Date or String | No | Event 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, local wall-clock strings, or ISO 8601 strings with an explicit time zone offset. |
calendarID | String | No | Target calendar. If omitted, uses the default calendar. |
allDay | Boolean | No | Set to true for an all-day event. For all-day events, prefer date-only startDate/endDate values. |
location | String | No | Event location, such as "Conference Room B" or "1 Apple Park Way, Cupertino, CA 95014". |
alarms | List of Numbers | No | Relative reminder minutes, such as {-60, -15}. Only include when reminders were explicitly requested. Omit to keep BusyCal's default reminders. |
notes | String | No | Event notes. |
Returned fields: id, seriesUID, calendarID, managedObjectURI, occurrenceDate, title, startDate, endDate, location, alarms, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, startTimeZoneIdentifier, endTimeZoneIdentifier
create task
Creates a new task and returns the created task record.
| Argument | Type | Required | Description |
|---|---|---|---|
title | String | Yes | Task title. |
dueDate | Date or String | No | Due 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, local wall-clock strings, or ISO 8601 strings with an explicit time zone offset. |
calendarID | String | No | Target calendar. If omitted, uses the default calendar. |
alarms | List of Numbers | No | Relative 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. |
notes | String | No | Task notes. |
Returned fields: id, seriesUID, calendarID, managedObjectURI, occurrenceDate, title, dueDate, alarms, isCompleted, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, dueTimeZoneIdentifier
Navigation
open item
Reveals an event or task in the BusyCal window, selecting it and opening its Info Panel. Returns the revealed item record.
| Argument | Type | Required | Description |
|---|---|---|---|
itemID | String | Yes | The 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 event
Reschedules an event to a new date and time. The event's original duration is preserved. Returns the updated event record.
| Argument | Type | Required | Description |
|---|---|---|---|
itemID | String | Yes | The event's id from a query or create result. |
scope | String | No | Recurrence scope. Default: "all". |
startDate | Date or String | Yes | New start date and time. Accepts AppleScript dates, local wall-clock strings, or ISO 8601 strings with an explicit time zone offset. |
Returned fields: id, seriesUID, calendarID, managedObjectURI, occurrenceDate, title, startDate, endDate, location, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, startTimeZoneIdentifier, endTimeZoneIdentifier
update event
Updates an event's title, location, notes, or reminders. Returns the updated event record. You must provide at least one field to change.
| Argument | Type | Required | Description |
|---|---|---|---|
itemID | String | Yes | The event's id. |
scope | String | No | Recurrence scope. Default: "all". |
title | String | No | New title. |
location | String | No | New location. Pass an empty string to clear it. |
notes | String | No | New notes. Pass an empty string to clear it. |
alarms | List of Numbers | No | Relative reminder minutes. Pass {} to clear all reminders, omit to leave them unchanged, or pass a non-empty list to add those reminder offsets. |
update task
Updates a task's title, notes, or reminders. Returns the updated task record. You must provide at least one field to change.
| Argument | Type | Required | Description |
|---|---|---|---|
itemID | String | Yes | The task's id. |
scope | String | No | Recurrence scope. Default: "all". |
title | String | No | New title. |
notes | String | No | New notes. Pass an empty string to clear it. |
alarms | List of Numbers | No | Relative 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.
| Argument | Type | Required | Description |
|---|---|---|---|
itemID | String | Yes | The task's id. |
scope | String | No | Recurrence scope. Default: "all". |
dueDate | Date or String | No | New due date. Omit to make the task undated. Accepts AppleScript dates, local wall-clock strings, or ISO 8601 strings with an explicit time zone offset. |
Returned fields: id, seriesUID, calendarID, managedObjectURI, occurrenceDate, title, dueDate, isCompleted, isFloating, timeZoneIdentifier, occurrenceTimeZoneIdentifier, dueTimeZoneIdentifier
complete task
Marks a task as complete. Returns the updated task record.
| Argument | Type | Required | Description |
|---|---|---|---|
itemID | String | Yes | The task's id. |
scope | String | No | Recurrence scope. Default: "all". |
completedAt | Date | No | Completion 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 taskreopen task
| Argument | Type | Required | Description |
|---|---|---|---|
itemID | String | Yes | The task's id. |
scope | String | No | Recurrence scope. Default: "all". |
Deleting
delete event
Deletes an event. Returns a record with the deleted item's id and deleted:true.
| Argument | Type | Required | Description |
|---|---|---|---|
itemID | String | Yes | The event's id. |
scope | String | No | Recurrence scope. Default: "all". |
delete task
Deletes a task. Returns a record with the deleted item's id and deleted:true.
| Argument | Type | Required | Description |
|---|---|---|---|
itemID | String | Yes | The task's id. |
scope | String | No | Recurrence scope. Default: "all". |
Recurrence Scope
Commands that modify or delete items accept a scope argument to control which occurrences are affected:
| Scope | Meaning |
|---|---|
"one" | This occurrence only. |
"future" | This and all future occurrences. |
"all" | All occurrences (the entire series). This is the default. |
For non-recurring items, the scope is ignored - the single item is always affected.
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
Update an event
-- Create an event, then update its title and location
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 and add a location
set updatedEvent to update event given itemID:(|id| of createdEvent), scope:"all", title:"Q2 Planning Meeting", location:"Conference Room B"
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 task with notes
set createdTask to create task given title:"Write release notes", dueDate:dueDateValue, notes:"Draft for v2.5"
-- Update the notes
set updatedTask to update task given itemID:(|id| of createdTask), scope:"all", notes:"Draft for v2.5 - include migration steps"
-- 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
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
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,
endDateis beforestartDatewhen creating an event, ormove eventis called with anendDateargument. - Item not found — the
idyou 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
idreturned from query or create commands as theitemIDfor update, move, and delete commands. Don't construct IDs yourself. idandisCompletedare AppleScript reserved words — 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
calendarIDvalue fromlist calendars— use that when creating items on a specific calendar or filtering queries. - For recurring items, set
scopeto control which occurrences are affected. If you omit it,"all"is used (the entire series). - 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
supportsEventsorsupportsTasksfromlist calendars. - Every create and update command returns the resulting record, so you can chain operations by reading the
idfrom each result. - Queries without
searchTextare capped to 100 results by default. PassfetchLimit:0if you want unlimited results. - Use
searchFieldsto limit searches to specific fields like"title"or"notes"when you need more targeted matching.