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.

Context Vars: google.calendar.api-root

build-event alias 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; see event-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-id to 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 alias 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 alias 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 alias 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 alias 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 alias 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 alias 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 alias 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 alias 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 alias 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 alias 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 alias 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).