Google Calendar API v3 bindings: events (list with every filter,
get, create with Google Meet links, update, patch, delete, quick add,
move, recurring instances), incremental sync with sync tokens and
push notifications, free/busy, calendars and the calendar list,
colors, and sharing (ACL). Credentials and scopes come from
google-core.
Namespaces
::google::calendar::events- list/list-all, get, create, update, patch, delete, quick-add, move, instances, event and Meet builders::google::calendar::sync-sync-events(sync tokens, 410 resync),watch-events,stop-channel, webhook parsing and verification::google::calendar::calendars- calendar list, calendars get and create, colors, free/busy::google::calendar::acl- list, share, unshare
Every function has an options arity taking an opts Map. Calendar
query parameters use the API's own names (timeMin, singleEvents,
sendUpdates, ...); credential keys (token, refresh-token,
service-account, subject, scopes, retry) are handled by
google-core and override context for that call. Calendar ids
(primary, team@group.calendar.google.com, ...) and event ids
are URL-encoded in every path.
Times. dateTime values are RFC 3339 with an offset
(2026-11-03T15:00:00-06:00) or Z; a timeZone (IANA, e.g.
America/Chicago) next to dateTime is needed for recurring events
and otherwise only labels the time. All-day events use date
(2026-11-03) with an exclusive end date. List filters (timeMin,
timeMax, updatedMin) must carry an offset.
Quick start
::events ::google::calendar::events
week ::events/list-all-events("primary", {timeMin: "2026-11-02T00:00:00Z", timeMax: "2026-11-09T00:00:00Z", singleEvents: true, orderBy: "startTime"})
::events/create-event("primary", ::events/build-event({
summary: "Design review",
start: "2026-11-03T15:00:00-06:00",
end: "2026-11-03T15:30:00-06:00",
attendees: ["ada@example.com"],
meet: true
}), {sendUpdates: "all"})
Values
ACL_READ_SCOPE
ACL_READ_SCOPE: Str "https://www.googleapis.com/auth/calendar.acls.readonly"
ACL_SCOPE
ACL_SCOPE: Str "https://www.googleapis.com/auth/calendar.acls"
API_ROOT
API_ROOT: Str "https://www.googleapis.com"
BASE_URL
BASE_URL: Str "https://www.googleapis.com/calendar/v3"
EVENTS_READ_SCOPE
EVENTS_READ_SCOPE: Str "https://www.googleapis.com/auth/calendar.events.readonly"
EVENTS_SCOPE
EVENTS_SCOPE: Str "https://www.googleapis.com/auth/calendar.events"
FREEBUSY_SCOPE
FREEBUSY_SCOPE: Str "https://www.googleapis.com/auth/calendar.freebusy"
READ_SCOPE
READ_SCOPE: Str "https://www.googleapis.com/auth/calendar.readonly"
SCOPE
SCOPE: Str "https://www.googleapis.com/auth/calendar"
Functions
api-root
fn (): Str
The API root, https://www.googleapis.com unless the google.calendar.api-root context variable points at a proxy or emulator.
google.calendar.api-root
build-event of ::google::calendar::events/build-event
fn (fields: Map): Map
An event body from friendlier fields (pure). Shorthands:
start,end- Str (date or RFC 3339) or EventDateTime Map; seeevent-time.time-zone(IANA) is applied to both.attendees- emails or attendee Maps (a single Str is one email)meet: true- request a Google Meet link (meet-request-idto set its request id)reminders-minutes- popup reminders, e.g.[10, 60], instead of the calendar's defaults
Every other field is passed through as the API's own (summary,
description, location, recurrence like
["RRULE:FREQ=WEEKLY;COUNT=10"], visibility, transparency,
colorId, guestsCanModify, extendedProperties, ...). Null
fields are dropped.
::google::calendar::events/build-event({
summary: "Standup",
start: "2026-11-02T09:00:00",
end: "2026-11-02T09:15:00",
time-zone: "America/Chicago",
recurrence: ["RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR"],
attendees: ["ada@example.com", {email: "bob@example.com", optional: true}],
meet: true,
reminders-minutes: [5]
})
calendar-path
fn (calendar-id: Str): Str
fn (calendar-id: Str, suffix: Str): Str
/calendars/{calendarId} plus suffix, with the id encoded.
compact
fn (body: Map): Map
Drop null values from a request body (Calendar bodies never carry JSON null for unset fields).
create-event of ::google::calendar::events/create-event
fn (calendar-id: Str, event: Map): Map
fn (calendar-id: Str, event: Map, opts: Map): Map
POST /calendars/{id}/events - create an event from an Event body
(build-event makes one): summary, start/end, attendees,
conferenceData, recurrence, reminders, ... Null fields are
dropped. Returns the event (with id, htmlLink, and hangoutLink
for Meet). Attendees are not emailed unless sendUpdates: "all".
Scope: calendar.events.
::google::calendar::events/create-event("primary", ::google::calendar::events/with-meet({
summary: "Kickoff",
start: {dateTime: "2026-11-05T10:00:00-05:00"},
end: {dateTime: "2026-11-05T10:45:00-05:00"},
attendees: [{email: "ada@example.com"}]
}), {sendUpdates: "all"})
creds
fn (opts: Map): Map
The google-core part of opts (credentials, scopes, retry), passed through to ::google::api.
delete-event of ::google::calendar::events/delete-event
fn (calendar-id: Str, event-id: Str): Bool
fn (calendar-id: Str, event-id: Str, opts: Map): Bool
DELETE /calendars/{id}/events/{eventId} - cancel and remove an event (deleting one instance of a recurring event cancels just that occurrence). Options: sendUpdates. Returns true. Scope: calendar.events.
done
fn (result: Result): Bool
true for a successful call whose response has no useful body (delete, stop), or the call's Err.
encode-id
fn (id: Str): Str
URL-encode an id for a path segment: "team@group.calendar.google.com" becomes team%40group.calendar.google.com; # in holiday calendar ids is encoded too.
event-path
fn (calendar-id: Str, event-id: Str): Str
fn (calendar-id: Str, event-id: Str, suffix: Str): Str
/calendars/{calendarId}/events/{eventId} plus suffix, both ids encoded.
get-event of ::google::calendar::events/get-event
fn (calendar-id: Str, event-id: Str): Map
fn (calendar-id: Str, event-id: Str, opts: Map): Map
GET /calendars/{id}/events/{eventId} - one event. Options: timeZone, maxAttendees, alwaysIncludeEmail. Scope: calendar.events.readonly.
json
fn (scope: Str, method: Str, full-url: Str, body: Any, opts: Map): Any
::google::api/json with this package's conventions: credential opts and a JSON (or empty) body.
list-all-events of ::google::calendar::events/list-all-events
fn (calendar-id: Str, opts: Map): Vec
fn (calendar-id: Str, opts: Map, limit: Int?): Vec
Follow nextPageToken and return every matching event, or at most
limit. Same options as list-events; uses maxResults: 2500 (or
limit when smaller) unless set. Scope: calendar.events.readonly.
today ::google::calendar::events/list-all-events("primary", {
timeMin: "2026-11-03T00:00:00-06:00", timeMax: "2026-11-04T00:00:00-06:00",
singleEvents: true, orderBy: "startTime"
})
list-calendars of ::google::calendar::calendars/list-calendars
fn (): Map
fn (opts: Map): Map
GET /users/me/calendarList - one page of the user's calendars: {items: [{id, summary, timeZone, accessRole, primary?, ...}], nextPageToken?, nextSyncToken?}. Options: minAccessRole (freeBusyReader, reader, writer, owner), showHidden, showDeleted, maxResults (max 250), pageToken, syncToken. Scope: calendar.readonly.
list-events of ::google::calendar::events/list-events
fn (calendar-id: Str): Map
fn (calendar-id: Str, opts: Map): Map
GET /calendars/{id}/events - one page of events: {items, nextPageToken?, nextSyncToken?, timeZone, updated, ...}.
Options (all of the API's): timeMin, timeMax (RFC 3339 with an
offset), q (text search), singleEvents (expand recurring events
into instances; required for orderBy: "startTime"), orderBy
(startTime, updated), maxResults (default 250, max 2500),
pageToken, showDeleted, showHiddenInvitations, updatedMin,
iCalUID, eventTypes (a Vec, e.g. ["default", "outOfOffice"]),
privateExtendedProperty / sharedExtendedProperty
("key=value", Vec for several), timeZone, maxAttendees,
alwaysIncludeEmail, syncToken (see ::google::calendar::sync).
Scope: calendar.events.readonly.
patch-event of ::google::calendar::events/patch-event
fn (calendar-id: Str, event-id: Str, changes: Map): Map
fn (calendar-id: Str, event-id: Str, changes: Map, opts: Map): Map
PATCH /calendars/{id}/events/{eventId} - change only the given fields (a Vec such as attendees is replaced whole). Scope: calendar.events.
query-free-busy of ::google::calendar::calendars/query-free-busy
fn (time-min: Str, time-max: Str, calendar-ids: Vec): Map
fn (time-min: Str, time-max: Str, calendar-ids: Vec, opts: Map): Map
POST /freeBusy - busy periods for calendars between time-min and
time-max (RFC 3339). Returns {calendars: {id: {busy: [{start, end}], errors?}}, groups?, timeMin, timeMax}; a calendar you cannot
see has errors instead of busy. Scope: calendar.freebusy.
fb ::google::calendar::calendars/query-free-busy("2026-11-03T13:00:00Z", "2026-11-03T22:00:00Z", ["ada@example.com", "bob@example.com"])
::google::calendar::calendars/busy-periods(fb, "ada@example.com")
query-params
fn (defaults: Map, opts: Map): Map
fn (defaults: Map, opts: Map, local-keys: Vec): Map
Build a call's query parameters: defaults, overridden by the
Calendar parameters in opts (credential keys and the function's own
local-keys are removed), with nulls dropped. Pass a key as null in
opts to drop a default.
quick-add of ::google::calendar::events/quick-add
fn (calendar-id: Str, text: Str): Map
fn (calendar-id: Str, text: Str, opts: Map): Map
POST /calendars/{id}/events/quickAdd - create an event from text, e.g. "Lunch with Sam tomorrow at noon" (parsed in the calendar's time zone; English works best). Options: sendUpdates. Scope: calendar.events.
sync-events of ::google::calendar::sync/sync-events
fn (calendar-id: Str, sync-token: Str?): EventSet
fn (calendar-id: Str, sync-token: Str?, opts: Map): EventSet
Everything that changed in a calendar since sync-token (null for a
first, full sync), as an EventSet. An expired token (410) triggers
a full sync unless opts.resync is false. Other options are
list-events options (e.g. singleEvents: true). Scope:
calendar.events.readonly.
::sync ::google::calendar::sync
::store ::hot::store
state ::store/Map({name: "calendar-sync"})
calendar-sync
meta {schedule: "every 10 minutes"}
fn (event) {
set ::sync/sync-events("primary", ::store/get(state, "sync-token"))
// set.full-sync: replace the local copy; else apply set.changed and drop set.cancelled-ids
::store/put(state, "sync-token", set.next-sync-token)
}
update-event of ::google::calendar::events/update-event
fn (calendar-id: Str, event-id: Str, event: Map): Map
fn (calendar-id: Str, event-id: Str, event: Map, opts: Map): Map
PUT /calendars/{id}/events/{eventId} - replace an event: fields you leave out are cleared, so start from get-event. Prefer patch-event to change a few fields. Scope: calendar.events.
url
fn (path: Str): Str
fn (path: Str, params: Map): Str
A Calendar v3 URL: /calendar/v3 + path with non-null params as a query string (Vec values repeat the key).