---
title: "Calendar"
description: "Every operation in this group: what it accepts, what it returns and the errors it can answer with."
url: "https://openemail.uk/docs/api/reference/calendar"
area: "API"
category: "Reference"
---

# Calendar

Every operation in this group: what it accepts, what it returns and the errors it can answer with.

## Operations

Events and invitations, as the Calendar page of the app shows them: list a window, read an event or the one a thread carries, put events on the calendar and change them, call off a meeting, and answer an invitation. A change that invites, updates or cancels attendees, and every answer, goes out by email, so it also needs `emails:send`.

### `GET /calendar/events`

List occurrences in a window

`from` and `to` are REQUIRED. Occurrences are expanded from recurrence rules, so a calendar has no meaningful unbounded listing. A default window would turn an infinite series into a question with no answer. At most a year apart.

Requires the `calendar:read` scope.

- Scopes: `calendar:read`.

**Query parameters**

- `from` (`string`, required, format `date-time`): Start of the window.
- `to` (`string`, required, format `date-time`): End of the window, after `from` and at most 366 days later.
- `timezone` (`string`, required): IANA zone. Decides what an all-day event means.
- `limit` (`integer`, at least 1, at most 100, default `25`): Rows per page, 1 to 100.
- `cursor` (`string`): The previous page's `nextCursor`, passed back as it came. It is opaque: it holds where the last row sat in this list's order, so a row deleted or edited between pages never breaks the walk, and the next page starts at the first row that sorts after it. A value this list did not hand out is a 400 `invalid_cursor`.

**Returns**

- `200`: A page of occurrences, sorted by start and then by event id, with `hasMore` and `nextCursor`. Send the same `from`, `to` and `timezone` with every `cursor`. Nothing in the window is dropped: a series repeats at most once a day, so it gives at most one occurrence per day of the window, and every one is reachable through the pages. The cursor holds the start and event id of the last row, so an event deleted or moved between pages never breaks the walk.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`calendar.listEvents()`](https://openemail.uk/docs/sdk/reference/calendar#listEvents), [`calendar.listAllEvents()`](https://openemail.uk/docs/sdk/reference/calendar#listAllEvents), [`calendar.iterateEvents()`](https://openemail.uk/docs/sdk/reference/calendar#iterateEvents); CLI [`openemail calendar list-events`](https://openemail.uk/docs/cli/reference/calendar#calendar-list-events); MCP [`listCalendarEvents`](https://openemail.uk/docs/mcp/tools/calendar#listCalendarEvents).

### `POST /calendar/events`

Create an event

Puts an event on the calendar, as New event on the Calendar page of the app does. `start` and `end` are ISO 8601 with a zone, and `timezone` is the IANA zone the event belongs to, UTC unless you say. `recurrence` takes an RRULE such as `FREQ=WEEKLY;BYDAY=MO`, and `reminders` the minutes before the start.

With attendees, an invitation goes to each of them from the address in `from`, which has to be one the key or app may send as, so the call also needs `emails:send`. `sendInvites: false` keeps the change to your own calendar.

Requires the `calendar:write` scope.

- Scopes: `calendar:write`.

**Request body**

- `summary` (`string`, required, 1 to 255 characters): The title, 1 to 255 characters.
- `description` (`string`, up to 8000 characters): Notes about the event, up to 8,000 characters.
- `location` (`string`, up to 255 characters): Where it is: a room, an address or a link.
- `url` (`string`, up to 2000 characters, format `uri`): A link that belongs to the event.
- `start` (`string`, required, format `date-time`): When it starts, a `Date` or ISO 8601 with a zone.
- `end` (`string`, required, format `date-time`): When it ends, after `start` and at most two years later.
- `allDay` (`boolean`): Whether it fills whole days. `start` and `end` then mark the days.
- `timezone` (`string`, 1 to 64 characters): The IANA zone the event belongs to, `UTC` unless you say.
- `recurrence` (`string`, up to 512 characters): An RRULE such as `FREQ=WEEKLY;BYDAY=MO` when it repeats.
- `attendees` (`object[]`, up to 200 items): Up to 200 people to invite, each `{ email, name?, optional? }`.
  - `email` (`string`, required, up to 320 characters, format `email`)
  - `name` (`string`, up to 128 characters)
  - `optional` (`boolean`)
- `reminders` (`object[]`, up to 5 items): Up to five reminders, each `{ minutesBefore, action? }`.
  - `minutesBefore` (`integer`, required, at least 0, at most 40320)
  - `action` (`string`, one of `"DISPLAY"`, `"EMAIL"`, `"AUDIO"`)
- `transparency` (`string`, one of `"OPAQUE"`, `"TRANSPARENT"`): `OPAQUE` shows you busy and `TRANSPARENT` free.
- `visibility` (`string`, one of `"PUBLIC"`, `"PRIVATE"`, `"CONFIDENTIAL"`): `PUBLIC`, `PRIVATE` or `CONFIDENTIAL`.
- `sendInvites` (`boolean`): False keeps the change to your own calendar, and sends nothing.
- `from` (`string`, up to 320 characters): The address the invitations come from. It has to be one the key may send as.

**Returns**

- `201` `CalendarEvent`: The event, with its attendees.

**Errors**

- `403`: `from_address_forbidden`: the key or app may not send as `from`. `insufficient_scope` when invitations would go out and `emails:send` is missing.
- The errors every operation can return: `400`, `401`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`calendar.createEvent()`](https://openemail.uk/docs/sdk/reference/calendar#createEvent); CLI [`openemail calendar create-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-create-event); MCP [`createCalendarEvent`](https://openemail.uk/docs/mcp/tools/calendar#createCalendarEvent).

### `GET /calendar/events/{id}`

Retrieve an event

The event with its attendees and their responses. `rawIcs` is omitted. Everything understood in it is already structured here, and `/ics` serves the document.

Requires the `calendar:read` scope.

- Scopes: `calendar:read`.

**Path parameters**

- `id` (`string`, required): Event id, the `eventId` of an occurrence, such as `cal_` followed by 24 hex characters.

**Returns**

- `200`: The event.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`calendar.getEvent()`](https://openemail.uk/docs/sdk/reference/calendar#getEvent); CLI [`openemail calendar get-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-get-event); MCP [`getCalendarEvent`](https://openemail.uk/docs/mcp/tools/calendar#getCalendarEvent).

### `PATCH /calendar/events/{id}`

Change an event

Changes an event you organise. A field left out keeps its value, and `attendees` replaces the whole list. Everyone invited gets the updated invitation.

With attendees, an invitation goes to each of them from the address in `from`, which has to be one the key or app may send as, so the call also needs `emails:send`. `sendInvites: false` keeps the change to your own calendar.

Requires the `calendar:write` scope.

- Scopes: `calendar:write`.

**Path parameters**

- `id` (`string`, required): The event id from `GET /calendar/events`.

**Request body**

- `summary` (`string`, 1 to 255 characters): The title, 1 to 255 characters.
- `description` (`string`, up to 8000 characters): Notes about the event, up to 8,000 characters.
- `location` (`string`, up to 255 characters): Where it is: a room, an address or a link.
- `url` (`string`, up to 2000 characters, format `uri`): A link that belongs to the event.
- `start` (`string`, format `date-time`): When it starts, a `Date` or ISO 8601 with a zone.
- `end` (`string`, format `date-time`): When it ends, after `start` and at most two years later.
- `allDay` (`boolean`): Whether it fills whole days. `start` and `end` then mark the days.
- `timezone` (`string`, 1 to 64 characters): The IANA zone the event belongs to, `UTC` unless you say.
- `recurrence` (`string`, up to 512 characters): An RRULE such as `FREQ=WEEKLY;BYDAY=MO` when it repeats.
- `attendees` (`object[]`, up to 200 items): Up to 200 people to invite, each `{ email, name?, optional? }`. It replaces the whole list.
  - `email` (`string`, required, up to 320 characters, format `email`)
  - `name` (`string`, up to 128 characters)
  - `optional` (`boolean`)
- `reminders` (`object[]`, up to 5 items): Up to five reminders, each `{ minutesBefore, action? }`.
  - `minutesBefore` (`integer`, required, at least 0, at most 40320)
  - `action` (`string`, one of `"DISPLAY"`, `"EMAIL"`, `"AUDIO"`)
- `transparency` (`string`, one of `"OPAQUE"`, `"TRANSPARENT"`): `OPAQUE` shows you busy and `TRANSPARENT` free.
- `visibility` (`string`, one of `"PUBLIC"`, `"PRIVATE"`, `"CONFIDENTIAL"`): `PUBLIC`, `PRIVATE` or `CONFIDENTIAL`.
- `sendInvites` (`boolean`): False keeps the change to your own calendar, and sends nothing.
- `from` (`string`, up to 320 characters): The address the invitations come from. It has to be one the key may send as.

**Returns**

- `200` `CalendarEvent`: The event as it stands now.

**Errors**

- `403`: `not_organizer`: the event was organised by somebody else, so you can only answer it. `from_address_forbidden`: the key or app may not send as `from`.
- `409`: `event_cancelled`: the meeting was called off.
- The errors every operation can return: `400`, `401`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`calendar.updateEvent()`](https://openemail.uk/docs/sdk/reference/calendar#updateEvent); CLI [`openemail calendar update-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-update-event); MCP [`updateCalendarEvent`](https://openemail.uk/docs/mcp/tools/calendar#updateCalendarEvent).

### `DELETE /calendar/events/{id}`

Remove an event

Removes an event from the calendar for good, as Remove does in the app. Nobody else is told: to call off a meeting with others in it, use `POST /calendar/events/{id}/cancel`.

Requires the `calendar:write` scope.

- Scopes: `calendar:write`.

**Path parameters**

- `id` (`string`, required): The event id from `GET /calendar/events`.

**Returns**

- `200` `DeletedCalendarEvent`: The event is gone.

**Errors**

- `403`: `not_organizer`: the event was organised by somebody else.
- The errors every operation can return: `400`, `401`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`calendar.deleteEvent()`](https://openemail.uk/docs/sdk/reference/calendar#deleteEvent); CLI [`openemail calendar delete-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-delete-event); MCP [`deleteCalendarEvent`](https://openemail.uk/docs/mcp/tools/calendar#deleteCalendarEvent).

### `GET /calendar/events/{id}/ics`

The event as an .ics document

Served as `text/calendar` with NO `METHOD` parameter. A METHOD makes a document actionable (a client seeing `method=REQUEST` draws accept/decline and can reply to the organiser), and this endpoint has no authority to invite anybody on the workspace behalf. It states what the event currently is.

Requires the `calendar:read` scope.

- Scopes: `calendar:read`.

**Path parameters**

- `id` (`string`, required): Event id, the `eventId` of an occurrence.

**Returns**

- `200` `string` (`text/calendar`): An iCalendar document.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`calendar.getEventIcs()`](https://openemail.uk/docs/sdk/reference/calendar#getEventIcs); CLI [`openemail calendar get-event-ics`](https://openemail.uk/docs/cli/reference/calendar#calendar-get-event-ics).

### `POST /calendar/events/{id}/cancel`

Cancel a meeting

Calls off 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, from the address in `from`, so a meeting with attendees also needs `emails:send`. The body is optional.

Requires the `calendar:write` scope.

- Scopes: `calendar:write`.

**Path parameters**

- `id` (`string`, required): The event id from `GET /calendar/events`.

**Request body**

- `from` (`string`, up to 320 characters): The address the cancellation comes from. It has to be one the key may send as.

**Returns**

- `200` `CalendarEvent`: The event, cancelled.

**Errors**

- `403`: `not_organizer`: the event was organised by somebody else. `from_address_forbidden`: the key or app may not send as `from`.
- The errors every operation can return: `400`, `401`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`calendar.cancelEvent()`](https://openemail.uk/docs/sdk/reference/calendar#cancelEvent); CLI [`openemail calendar cancel-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-cancel-event); MCP [`cancelCalendarEvent`](https://openemail.uk/docs/mcp/tools/calendar#cancelCalendarEvent).

### `POST /calendar/events/{id}/respond`

Answer an invitation

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

Requires the `calendar:write` and `emails:send` scopes.

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

**Path parameters**

- `id` (`string`, required): The event id from `GET /calendar/events`.

**Request body**

- `response` (`string`, required, one of `"ACCEPTED"`, `"DECLINED"`, `"TENTATIVE"`): `ACCEPTED`, `DECLINED` or `TENTATIVE`.
- `respondingAs` (`string`, up to 320 characters): Which of your invited addresses answers, when there is more than one.

**Returns**

- `200` `CalendarEvent`: The event, with your answer.

**Errors**

- `403`: `not_invited`: the event did not invite `respondingAs`, or any address the key or app may send as.
- `409`: `event_cancelled`: the meeting was called off. `reply_not_sent`: the answer could not be sent to the organiser, so nothing changed.
- The errors every operation can return: `400`, `401`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`calendar.respondToEvent()`](https://openemail.uk/docs/sdk/reference/calendar#respondToEvent); CLI [`openemail calendar respond-to-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-respond-to-event); MCP [`respondToCalendarEvent`](https://openemail.uk/docs/mcp/tools/calendar#respondToCalendarEvent).

### `GET /threads/{id}/event`

The event a thread carries

The calendar event that came with an invitation in the thread, as the invitation card of the reading pane shows it. A thread with no event, or one the key or app does not reach, is a 404. Answer it with `POST /calendar/events/{id}/respond`.

Requires the `calendar:read` scope.

- Scopes: `calendar:read`.

**Path parameters**

- `id` (`string`, required): The thread id.

**Returns**

- `200` `CalendarEvent`: The event.

**Errors**

- The errors every operation can return: `400`, `401`, `403`, `404`, `422`, `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`threads.getEvent()`](https://openemail.uk/docs/sdk/reference/threads#getEvent); CLI [`openemail threads get-event`](https://openemail.uk/docs/cli/reference/threads#threads-get-event); MCP [`getCalendarEvent`](https://openemail.uk/docs/mcp/tools/calendar#getCalendarEvent).

### Objects

#### `CalendarEvent`

`object`

- `object` (`string`, required, one of `"calendar_event"`)
- `id` (`string`, required)
- `uid` (`string`, required): The iCalendar UID every copy of the event shares.
- `sequence` (`integer`, at least 0)
- `summary` (`string`, nullable)
- `description` (`string`, nullable)
- `location` (`string`, nullable)
- `url` (`string`, nullable)
- `start` (`string`, required, format `date-time`)
- `end` (`string`, required, format `date-time`)
- `allDay` (`boolean`)
- `timezone` (`string`, nullable)
- `recurrence` (`string`, nullable): The RRULE, when it repeats.
- `exdates` (`string[]`)
- `status` (`string`, required): `CONFIRMED`, `TENTATIVE` or `CANCELLED`.
- `transparency` (`string`)
- `visibility` (`string`)
- `organizer` (`object`, nullable)
  - `email` (`string`)
  - `name` (`string`, nullable)
- `isOrganizer` (`boolean`): Whether you organise it, and so may change it.
- `source` (`string`)
- `messageId` (`string`, nullable)
- `threadId` (`string`, nullable)
- `alarms` (`object[]`)
  - `minutesBefore` (`integer`)
  - `action` (`string`)
- `attendees` (`object[]`, required)
  - `object` (`string`, one of `"calendar_attendee"`)
  - `email` (`string`)
  - `name` (`string`, nullable)
  - `partstat` (`string`): `NEEDS-ACTION`, `ACCEPTED`, `DECLINED` or `TENTATIVE`.
  - `role` (`string`)
  - `rsvp` (`boolean`)
  - `cutype` (`string`, nullable)
  - `respondedAt` (`string`, nullable, format `date-time`)
- `createdAt` (`string`, format `date-time`)
- `updatedAt` (`string`, format `date-time`)

#### `DeletedCalendarEvent`

`object`

- `object` (`string`, required, one of `"calendar_event"`)
- `id` (`string`, required)
- `deleted` (`boolean`, required, one of `true`)
