---
title: "openemail.calendar"
description: "Every method in this namespace: its signature, its parameters, what it returns and an example."
url: "https://openemail.uk/docs/sdk/reference/calendar"
area: "SDK"
category: "Reference"
---

# openemail.calendar

Every method in this namespace: its signature, its parameters, what it returns and an example.

## Methods

Calendar events found in mail, expanded into occurrences.

### `calendar.listEvents()`

List one page of event occurrences inside a time window

```ts
listEvents(options: CalendarRangeOptions): Promise<Page<CalendarOccurrenceResource>>
```

Expands every event in the workspace into its occurrences between `from` and `to` and returns one page of them, sorted by start time and then by event id. A recurring series gives one row per instance in the window, with its excluded dates left out, and every row points back to its event through `eventId`. The window is required because a repeating series has no end to list up to. Follow `nextCursor` while `hasMore` is true, with the same `from`, `to` and `timezone`, to read every occurrence, or let `listAllEvents` and `iterateEvents` do that walk.

`to` must be after `from` and at most 366 days later. A missing `from` or `to` is a 422 `invalid_parameter`, while a value that does not parse, a reversed or overlong window, or an unknown zone is a 400 `invalid_parameter` naming the field. Both bounds take a `Date` or an ISO 8601 string, and the SDK converts a `Date` with `toISOString()`.

`timezone` is an IANA zone and defaults to `UTC` on the server. It is used to expand any event stored without a zone of its own, which is what decides the day an all day event lands on.

Scopes: `calendar:read`.

**Parameters**

- `options.from` (`Date | string`, required): Start of the window.
- `options.to` (`Date | string`, required): End of the window, after `from` and at most 366 days later.
- `options.timezone` (`string`): IANA zone such as `Europe/London`, at most 64 characters. Defaults to `UTC`.
- `options.limit` (`number`): Occurrences per page, a whole number from 1 to 100. The server defaults to 25.
- `options.cursor` (`string`): The `nextCursor` of the previous page, sent with the same window. It is opaque, so never build one yourself.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client's API key for this call only.

**Returns**

`Page<CalendarOccurrenceResource>` with `items`, `hasMore` and `nextCursor`. Each item has `eventId`, `uid`, `start`, `end`, `allDay`, `summary`, `location`, `status`, `transparency`, `isOrganizer`, `organizerEmail`, `recurring`, `myPartstat` and `attendeeCount`.

**Example**

```ts
const from = new Date()
const to = new Date(from.getTime() + 7 * 24 * 60 * 60 * 1000)

const page = await openemail.calendar.listEvents({ from, to, timezone: 'Europe/London', limit: 50 })

for (const occurrence of page.items) {
    console.log(occurrence.start, occurrence.summary, occurrence.myPartstat ?? 'not invited')
}

console.log(page.hasMore, page.nextCursor)
```

**Notes**

- `myPartstat` is the response recorded for any address this workspace holds on its own domains, or for the addresses a narrowed key covers, and null when none of them is an attendee. A workspace with no address on its own domains matches no attendee, so every row reads null.
- A narrowed key only sees occurrences whose organiser is one of the addresses it covers or where one of them is an attendee.
- Cancelled events are not filtered out. Check `status` for `CANCELLED`.
- Nothing in the window is dropped. A series repeats at most once a day, so one series gives at most one row per day of the window, and every one of them is reachable through the pages.
- The cursor holds the start time and event id of the last row, so an event deleted or moved between pages never breaks the walk. A cursor this list did not hand out is a 400 `invalid_cursor`.

Also available in: API [`GET /calendar/events`](https://openemail.uk/docs/api/reference/calendar#get-calendar-events); CLI [`openemail calendar list-events`](https://openemail.uk/docs/cli/reference/calendar#calendar-list-events).

### `calendar.listAllEvents()`

Collect every occurrence inside a time window into one array

```ts
listAllEvents(options: CalendarRangeOptions): Promise<Array<CalendarOccurrenceResource>>
```

Walks every page of `listEvents` for one window and resolves with all of its occurrences, sorted by start time and then by event id. It follows `nextCursor` until `hasMore` is false, sending the same `from`, `to` and `timezone` with each request.

A window of up to 366 days of a busy calendar can hold thousands of occurrences, so prefer `iterateEvents` when you can stop early.

Scopes: `calendar:read`.

**Parameters**

- `options.from` (`Date | string`, required): Start of the window.
- `options.to` (`Date | string`, required): End of the window, after `from` and at most 366 days later.
- `options.timezone` (`string`): IANA zone such as `Europe/London`, at most 64 characters. Defaults to `UTC`.
- `options.limit` (`number`): Page size for each request, 1 to 100. The server defaults to 25.
- `options.cursor` (`string`): Starts the walk from this cursor instead of the start of the window.
- `options.signal` (`AbortSignal`): Cancels the request in flight and the walk with it.
- `options.apiKey` (`string`): Overrides the client API key for every page of this walk.

**Returns**

`Array<CalendarOccurrenceResource>` holding every occurrence in the window, earliest first.

**Example**

```ts
const occurrences = await openemail.calendar.listAllEvents({
    from: '2026-10-01T00:00:00Z',
    to: '2026-11-01T00:00:00Z',
    limit: 100
})

const busy = occurrences.filter((occurrence) => occurrence.transparency === 'OPAQUE')

console.log(`${busy.length} busy blocks in October`)
```

**Notes**

- If any page fails the promise rejects and the occurrences already fetched are discarded.

Also available in: API [`GET /calendar/events`](https://openemail.uk/docs/api/reference/calendar#get-calendar-events).

### `calendar.iterateEvents()`

Stream the occurrences inside a time window one at a time

```ts
iterateEvents(options: CalendarRangeOptions): AsyncGenerator<CalendarOccurrenceResource, void, undefined>
```

Returns an async generator over the occurrences in one window that yields them individually, earliest first, and fetches the next page only when the current one is drained. Nothing is requested until you consume it, and breaking out of the loop stops further requests, which makes it the way to find the next free slot or the next meeting without reading the whole window.

The walk ends when `hasMore` is false, when a page comes back empty, or when the server repeats a cursor.

Scopes: `calendar:read`.

**Parameters**

- `options.from` (`Date | string`, required): Start of the window.
- `options.to` (`Date | string`, required): End of the window, after `from` and at most 366 days later.
- `options.timezone` (`string`): IANA zone such as `Europe/London`, at most 64 characters. Defaults to `UTC`.
- `options.limit` (`number`): Page size per request, 1 to 100. The server defaults to 25.
- `options.cursor` (`string`): Starts the walk from this cursor instead of the start of the window.
- `options.signal` (`AbortSignal`): Cancels the request in flight and ends the iteration.
- `options.apiKey` (`string`): Overrides the client API key for every page of this walk.

**Returns**

`AsyncGenerator<CalendarOccurrenceResource, void, undefined>` yielding one occurrence per step.

**Example**

```ts
const from = new Date()
const to = new Date(from.getTime() + 30 * 24 * 60 * 60 * 1000)

for await (const occurrence of openemail.calendar.iterateEvents({ from, to })) {
    if (occurrence.myPartstat === null) continue
    console.log('next meeting', occurrence.start, occurrence.summary)
    break
}
```

**Notes**

- An aborted `options.signal` rejects the pending page request, which throws out of the `for await` loop.

Also available in: API [`GET /calendar/events`](https://openemail.uk/docs/api/reference/calendar#get-calendar-events).

### `calendar.getEvent()`

Read an event with its attendees

```ts
getEvent(id: string, options?: RequestScope): Promise<CalendarEventResource>
```

Returns a stored event with its attendees and their responses. What the server understood from the iCalendar data is structured here: `recurrence` holds the `RRULE` text, `exdates` the excluded instances, `organizer` the organiser and `alarms` the reminders as `{ minutesBefore, action }`. The raw iCalendar source is not included, and `getEventIcs` serves the event as a document.

`id` is the event id, the `eventId` on an occurrence, and not the iCalendar `uid`. A recurring series is one event however many occurrences it has, so `start` and `end` describe its first instance. `messageId` and `threadId` link the event to mail in the mailbox when there is any.

Attendees come back in the order they were added, each with `partstat`, `role`, `rsvp`, `cutype` and `respondedAt`. A narrowed key gets a 404 unless one of the addresses it covers organises the event or is on the attendee list, and a key that holds a whole domain covers every address on it.

Scopes: `calendar:read`.

**Parameters**

- `id` (`string`, required): Event id, the `eventId` of an occurrence, such as `cal_` followed by 24 hex characters.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client's API key for this call only.

**Returns**

`CalendarEventResource` with `id`, `uid`, `sequence`, `summary`, `description`, `location`, `url`, `start`, `end`, `allDay`, `timezone`, `recurrence`, `exdates`, `status`, `transparency`, `visibility`, `organizer`, `isOrganizer`, `source`, `messageId`, `threadId`, `alarms`, `attendees`, `createdAt` and `updatedAt`.

**Example**

```ts
const { items: [first] } = await openemail.calendar.listEvents({ from: '2026-09-14T00:00:00Z', to: '2026-09-21T00:00:00Z' })

if (first) {
    const event = await openemail.calendar.getEvent(first.eventId)
    console.log(event.summary, event.recurrence ?? 'one off')
    console.log(event.attendees.map((attendee) => `${attendee.email}: ${attendee.partstat}`))
}
```

**Notes**

- `status`, `transparency` and `visibility` carry iCalendar values, and default to `CONFIRMED`, `OPAQUE` and `PUBLIC`.
- An attendee's `partstat` defaults to `NEEDS-ACTION` and `role` to `REQ-PARTICIPANT` until a response is recorded.

Also available in: API [`GET /calendar/events/{id}`](https://openemail.uk/docs/api/reference/calendar#get-calendar-events-id); CLI [`openemail calendar get-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-get-event).

### `calendar.getEventIcs()`

Download an event as an iCalendar document

```ts
getEventIcs(id: string, options?: RequestScope): Promise<string>
```

Resolves with the event serialised as an `.ics` document, as a raw string rather than parsed JSON. The server sends it as `text/calendar; charset=utf-8` with a download filename built from the event's `uid`, and the SDK requests that type and returns the body untouched.

The document declares `METHOD:PUBLISH`, and the content type carries no `method` parameter. That is deliberate: a `REQUEST` document makes a calendar client offer accept and decline and reply to the organiser, and this endpoint has no authority to invite anyone. Import it to show what the event currently is, not to send an invitation.

It holds one `VEVENT` with the attendees and their participation status, the recurrence rule, excluded dates and alarms. Lines end in CRLF and long lines are folded. `DTSTAMP` is the time of the request, so two downloads of an unchanged event differ on that line.

Scopes: `calendar:read`.

**Parameters**

- `id` (`string`, required): Event id, the `eventId` of an occurrence.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client's API key for this call only.

**Returns**

`string`, the iCalendar text from `BEGIN:VCALENDAR` to `END:VCALENDAR`.

**Example**

```ts
const ics = await openemail.calendar.getEventIcs('cal_8d3f0a2b9c4e41f7a6b5c2d1')
const summary = ics.split('\r\n').find((line) => line.startsWith('SUMMARY:'))

console.log(summary)
```

**Notes**

- Errors still come back as JSON and throw `OpenEmailApiError`, so a failure never resolves as a string.
- Timed events are written as UTC instants and all day events as `DATE` values, never as local times with a `TZID`.
- A narrowed key gets a 404 on the same terms as `getEvent`.

Also available in: API [`GET /calendar/events/{id}/ics`](https://openemail.uk/docs/api/reference/calendar#get-calendar-events-id-ics); CLI [`openemail calendar get-event-ics`](https://openemail.uk/docs/cli/reference/calendar#calendar-get-event-ics).

### `calendar.createEvent()`

Put an event on the calendar

```ts
createEvent(body: CalendarEventCreate, options?: RequestScope): Promise<CalendarEventResource>
```

Creates an event, as New event on the Calendar page of the app does. `start` and `end` take a `Date` or ISO 8601 with a zone, and `timezone` is the IANA zone the event belongs to, which decides what an all-day event means.

With `attendees`, an invitation goes to each of them by email from `from`, which has to be an address the key may send as, so the call also needs `emails:send`. Send `sendInvites: false` to keep it to your own calendar.

Scopes: `calendar:write`.

**Parameters**

- `body.summary` (`string`, required): The title, 1 to 255 characters.
- `body.start` (`Date | string`, required): When it starts, a `Date` or ISO 8601 with a zone.
- `body.end` (`Date | string`, required): When it ends, after `start` and at most two years later.
- `body.description` (`string`): Notes about the event, up to 8,000 characters.
- `body.location` (`string`): Where it is: a room, an address or a link.
- `body.url` (`string`): A link that belongs to the event.
- `body.allDay` (`boolean`): Whether it fills whole days. `start` and `end` then mark the days.
- `body.timezone` (`string`): The IANA zone the event belongs to, `UTC` unless you say.
- `body.recurrence` (`string`): An RRULE such as `FREQ=WEEKLY;BYDAY=MO` when it repeats.
- `body.attendees` (`Array<CalendarAttendeeInput>`): Up to 200 people to invite, each `{ email, name?, optional? }`.
- `body.reminders` (`Array<CalendarReminderInput>`): Up to five reminders, each `{ minutesBefore, action? }`.
- `body.transparency` (`CalendarTransparency`): `OPAQUE` shows you busy and `TRANSPARENT` free.
- `body.visibility` (`CalendarVisibility`): `PUBLIC`, `PRIVATE` or `CONFIDENTIAL`.
- `body.sendInvites` (`boolean`): False keeps the change to your own calendar, and sends nothing.
- `body.from` (`string`): The address the invitations come from. It has to be one the key may send as.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client's API key for this call only.

**Returns**

`CalendarEventResource` for the new event, with its attendees and their responses.

**Example**

```ts
const event = await openemail.calendar.createEvent({
    summary: 'Planning',
    start: '2026-10-12T09:00:00Z',
    end: '2026-10-12T09:30:00Z',
    attendees: [{ email: 'ada@example.com' }],
    from: 'team@acme.com'
})

console.log(event.id, event.attendees.length)
```

**Notes**

- The SDK does not retry it, because a second call makes a second event and invites everybody again.
- A key that may not send as `from` is refused with 403 `from_address_forbidden`, and an event with attendees and no `from` with 422 `invalid_parameter`. Invitations without `emails:send` are a 403 `insufficient_scope`.

Also available in: API [`POST /calendar/events`](https://openemail.uk/docs/api/reference/calendar#post-calendar-events); CLI [`openemail calendar create-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-create-event).

### `calendar.updateEvent()`

Change an event

```ts
updateEvent(id: string, patch: CalendarEventPatch, options?: RequestScope): Promise<CalendarEventResource>
```

Changes an event you organise. A field you leave out keeps its value, and `attendees` replaces the whole list. Everyone invited gets the updated invitation by email from `from`, so a change to an event with attendees needs `emails:send` too, unless `sendInvites` is false.

Scopes: `calendar:write`.

**Parameters**

- `id` (`string`, required): Event id, the `eventId` of an occurrence, such as `cal_` followed by 24 hex characters.
- `patch.summary` (`string`): The title, 1 to 255 characters.
- `patch.start` (`Date | string`): When it starts, a `Date` or ISO 8601 with a zone.
- `patch.end` (`Date | string`): When it ends, after `start` and at most two years later.
- `patch.description` (`string`): Notes about the event, up to 8,000 characters.
- `patch.location` (`string`): Where it is: a room, an address or a link.
- `patch.url` (`string`): A link that belongs to the event.
- `patch.allDay` (`boolean`): Whether it fills whole days. `start` and `end` then mark the days.
- `patch.timezone` (`string`): The IANA zone the event belongs to, `UTC` unless you say.
- `patch.recurrence` (`string`): An RRULE such as `FREQ=WEEKLY;BYDAY=MO` when it repeats.
- `patch.attendees` (`Array<CalendarAttendeeInput>`): Up to 200 people to invite, each `{ email, name?, optional? }`. It replaces the whole list.
- `patch.reminders` (`Array<CalendarReminderInput>`): Up to five reminders, each `{ minutesBefore, action? }`.
- `patch.transparency` (`CalendarTransparency`): `OPAQUE` shows you busy and `TRANSPARENT` free.
- `patch.visibility` (`CalendarVisibility`): `PUBLIC`, `PRIVATE` or `CONFIDENTIAL`.
- `patch.sendInvites` (`boolean`): False keeps the change to your own calendar, and sends nothing.
- `patch.from` (`string`): The address the invitations come from. It has to be one the key may send as.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client's API key for this call only.

**Returns**

`CalendarEventResource` as it stands after the change, with `sequence` one higher.

**Example**

```ts
const event = await openemail.calendar.updateEvent('cal_3f9a1c7e5b2d48a06c1e9f4b', {
    start: '2026-10-12T10:00:00Z',
    end: '2026-10-12T10:30:00Z'
})

console.log(event.start, event.sequence)
```

**Notes**

- The SDK does not retry it, because a retry after a lost response sends the update to everybody again.
- An event organised by somebody else is refused with 403 `not_organizer`: answer it with `respondToEvent` instead. An event the key does not reach is a 404.

Also available in: API [`PATCH /calendar/events/{id}`](https://openemail.uk/docs/api/reference/calendar#patch-calendar-events-id); CLI [`openemail calendar update-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-update-event).

### `calendar.deleteEvent()`

Remove an event from the calendar

```ts
deleteEvent(id: string, options?: RequestScope): Promise<DeletedCalendarEventResource>
```

Removes an event for good, as Remove does in the app. Nobody else is told, so call off a meeting with others in it with `cancelEvent` instead.

Scopes: `calendar:write`.

**Parameters**

- `id` (`string`, required): Event id, the `eventId` of an occurrence, such as `cal_` followed by 24 hex characters.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client's API key for this call only.

**Returns**

`DeletedCalendarEventResource`: `{ object, id, deleted: true }`.

**Example**

```ts
await openemail.calendar.deleteEvent('cal_3f9a1c7e5b2d48a06c1e9f4b')
```

**Notes**

- An event organised by somebody else is refused with 403 `not_organizer`, and an event that is gone or that the key does not reach is a 404.

Also available in: API [`DELETE /calendar/events/{id}`](https://openemail.uk/docs/api/reference/calendar#delete-calendar-events-id); CLI [`openemail calendar delete-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-delete-event).

### `calendar.cancelEvent()`

Call off a meeting

```ts
cancelEvent(id: string, body?: CalendarEventCancel, options?: RequestScope): Promise<CalendarEventResource>
```

Cancels a meeting you organise, as Cancel meeting does in the app. It stays on the calendar with `status` `CANCELLED`, and everyone invited is told it is off by email from `from`, so a meeting with attendees also needs `emails:send`.

Scopes: `calendar:write`.

**Parameters**

- `id` (`string`, required): Event id, the `eventId` of an occurrence, such as `cal_` followed by 24 hex characters.
- `body.from` (`string`): The address the cancellation comes from. It has to be one the key may send as.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client's API key for this call only.

**Returns**

`CalendarEventResource` with `status` `CANCELLED`.

**Example**

```ts
const event = await openemail.calendar.cancelEvent('cal_3f9a1c7e5b2d48a06c1e9f4b', { from: 'team@acme.com' })

console.log(event.status)
```

**Notes**

- The SDK does not retry it, because a retry sends the cancellation again.
- An event organised by somebody else is refused with 403 `not_organizer`.

Also available in: API [`POST /calendar/events/{id}/cancel`](https://openemail.uk/docs/api/reference/calendar#post-calendar-events-id-cancel); CLI [`openemail calendar cancel-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-cancel-event).

### `calendar.respondToEvent()`

Answer an invitation

```ts
respondToEvent(id: string, body: CalendarEventAnswer, options?: RequestScope): Promise<CalendarEventResource>
```

Accepts, declines or tentatively accepts an invitation, as the buttons of an invitation do in the app. The answer goes to the organiser by email from the invited address the key may send as, or from `respondingAs` when the invitation went to more than one of yours, so it needs `emails:send` as well.

Scopes: `calendar:write`, `emails:send`.

**Parameters**

- `id` (`string`, required): Event id, the `eventId` of an occurrence, such as `cal_` followed by 24 hex characters.
- `body.response` (`CalendarResponse`, required): `ACCEPTED`, `DECLINED` or `TENTATIVE`.
- `body.respondingAs` (`string`): Which of your invited addresses answers, when there is more than one.
- `options.signal` (`AbortSignal`): Cancels the request.
- `options.apiKey` (`string`): Overrides the client's API key for this call only.

**Returns**

`CalendarEventResource` with your answer on your attendee row.

**Example**

```ts
const event = await openemail.calendar.respondToEvent('cal_3f9a1c7e5b2d48a06c1e9f4b', { response: 'ACCEPTED' })

console.log(event.attendees.map((attendee) => `${attendee.email}: ${attendee.partstat}`))
```

**Notes**

- An event that invited none of the addresses the key may send as is refused with 403 `not_invited`, and a cancelled meeting with 409 `event_cancelled`.
- When the organiser cannot be reached, nothing changes and the call is a 409 `reply_not_sent`.

Also available in: API [`POST /calendar/events/{id}/respond`](https://openemail.uk/docs/api/reference/calendar#post-calendar-events-id-respond); CLI [`openemail calendar respond-to-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-respond-to-event).
