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

# client.Calendar

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

## Methods

Calendar events found in mail or put on the calendar here, expanded into occurrences, with invitations, changes, cancellations and answers sent by email.

### `Calendar.ListEventsAsync`

List one page of event occurrences inside a time window

```csharp
Task<Page> ListEventsAsync(
    DateTimeOffset from,
    DateTimeOffset to,
    string? timezone = null,
    int? limit = null,
    string? cursor = null,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

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 `ListAllEventsAsync` and `IterateEventsAsync` 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 `DateTimeOffset` or an ISO 8601 string, and the SDK sends a `DateTimeOffset` as an ISO 8601 instant in UTC.

`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**

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

**Returns**

A `Page` of occurrence objects with `items`, `hasMore` and `nextCursor`. Each item has `eventId`, `uid`, `start`, `end`, `allDay`, `summary`, `location`, `status`, `transparency`, `isOrganizer`, `organizerEmail`, `recurring`, `myPartstat` and `attendeeCount`.

**Example**

```csharp
var today = new DateTimeOffset(DateTime.UtcNow.Date, TimeSpan.Zero);

var page = await client.Calendar.ListEventsAsync(today, today.AddDays(7), timezone: "Europe/London", limit: 50);

foreach (var occurrence in page)
{
    Console.WriteLine($"{occurrence["start"]} {occurrence["summary"]} {occurrence["myPartstat"]?.ToString() ?? "not invited"}");
}

if (page.HasMore)
{
    Console.WriteLine($"More from cursor {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); TypeScript [`calendar.listEvents()`](https://openemail.uk/docs/sdk/reference/calendar#listEvents); Python [`calendar.list_events()`](https://openemail.uk/docs/python/reference/calendar#listEvents); Ruby [`calendar.list_events`](https://openemail.uk/docs/ruby/reference/calendar#listEvents); PHP [`calendar->listEvents`](https://openemail.uk/docs/php/reference/calendar#listEvents); Go [`Calendar.ListEvents`](https://openemail.uk/docs/go/reference/calendar#listEvents); Java [`calendar().listEvents`](https://openemail.uk/docs/java/reference/calendar#listEvents); CLI [`openemail calendar list-events`](https://openemail.uk/docs/cli/reference/calendar#calendar-list-events).

### `Calendar.ListAllEventsAsync`

Collect every occurrence inside a time window into one object

```csharp
Task<IReadOnlyList<JsonObject>> ListAllEventsAsync(
    DateTimeOffset from,
    DateTimeOffset to,
    string? timezone = null,
    int? limit = null,
    string? cursor = null,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Walks every page of `ListEventsAsync` for one window and returns 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 `IterateEventsAsync` when you can stop early.

Scopes: `calendar:read`.

**Parameters**

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

**Returns**

A list of occurrence objects holding every occurrence in the window, earliest first.

**Example**

```csharp
using OpenEmail.Constants;

var occurrences = await client.Calendar.ListAllEventsAsync(DateTimeOffset.Parse("2026-10-01T00:00:00Z"), DateTimeOffset.Parse("2026-11-01T00:00:00Z"), limit: 100);

foreach (var occurrence in occurrences)
{
    if ((string?)occurrence["transparency"] == CalendarTransparencies.Opaque)
    {
        Console.WriteLine($"Busy {occurrence["start"]} to {occurrence["end"]}");
    }
}
```

**Notes**

- If any page fails the call throws and the occurrences already fetched are discarded.

Also available in: API [`GET /calendar/events`](https://openemail.uk/docs/api/reference/calendar#get-calendar-events); TypeScript [`calendar.listAllEvents()`](https://openemail.uk/docs/sdk/reference/calendar#listAllEvents); Python [`calendar.list_all_events()`](https://openemail.uk/docs/python/reference/calendar#listAllEvents); Ruby [`calendar.list_all_events`](https://openemail.uk/docs/ruby/reference/calendar#listAllEvents); PHP [`calendar->listAllEvents`](https://openemail.uk/docs/php/reference/calendar#listAllEvents); Go [`Calendar.ListAllEvents`](https://openemail.uk/docs/go/reference/calendar#listAllEvents); Java [`calendar().listAllEvents`](https://openemail.uk/docs/java/reference/calendar#listAllEvents).

### `Calendar.IterateEventsAsync`

Stream the occurrences inside a time window one at a time

```csharp
IAsyncEnumerable<JsonObject> IterateEventsAsync(
    DateTimeOffset from,
    DateTimeOffset to,
    string? timezone = null,
    int? limit = null,
    string? cursor = null,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Returns an `IAsyncEnumerable<JsonObject>` 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 carries no `nextCursor`, or when the server repeats a cursor.

Scopes: `calendar:read`.

**Parameters**

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

**Returns**

An `IAsyncEnumerable<JsonObject>` that yields one occurrence per step.

**Example**

```csharp
var from = DateTimeOffset.UtcNow;
var to = DateTimeOffset.UtcNow.AddDays(30);

await foreach (var occurrence in client.Calendar.IterateEventsAsync(from, to))
{
    if (occurrence["myPartstat"] is not null)
    {
        Console.WriteLine($"Next meeting: {occurrence["start"]} {occurrence["summary"]}");

        break;
    }
}
```

**Notes**

- A page request that fails throws out of the `foreach` loop, after every occurrence of the pages before it has been yielded.

Also available in: API [`GET /calendar/events`](https://openemail.uk/docs/api/reference/calendar#get-calendar-events); TypeScript [`calendar.iterateEvents()`](https://openemail.uk/docs/sdk/reference/calendar#iterateEvents); Python [`calendar.iterate_events()`](https://openemail.uk/docs/python/reference/calendar#iterateEvents); Ruby [`calendar.iterate_events`](https://openemail.uk/docs/ruby/reference/calendar#iterateEvents); PHP [`calendar->iterateEvents`](https://openemail.uk/docs/php/reference/calendar#iterateEvents); Go [`Calendar.IterateEvents`](https://openemail.uk/docs/go/reference/calendar#iterateEvents); Java [`calendar().iterateEvents`](https://openemail.uk/docs/java/reference/calendar#iterateEvents).

### `Calendar.GetEventAsync`

Read an event with its attendees

```csharp
Task<JsonObject> GetEventAsync(
    string id,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

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, each an object with `minutesBefore` and `action`. The raw iCalendar source is not included, and `GetEventIcsAsync` 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.
- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A `JsonObject` 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**

```csharp
var entry = await client.Calendar.GetEventAsync("cal_3f9a1c7e5b2d48a06c1e9f4b");

Console.WriteLine($"{entry["summary"]}, {entry["recurrence"]?.ToString() ?? "one off"}");

foreach (var attendee in entry["attendees"]?.AsArray() ?? [])
{
    Console.WriteLine($"{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); TypeScript [`calendar.getEvent()`](https://openemail.uk/docs/sdk/reference/calendar#getEvent); Python [`calendar.get_event()`](https://openemail.uk/docs/python/reference/calendar#getEvent); Ruby [`calendar.get_event`](https://openemail.uk/docs/ruby/reference/calendar#getEvent); PHP [`calendar->getEvent`](https://openemail.uk/docs/php/reference/calendar#getEvent); Go [`Calendar.GetEvent`](https://openemail.uk/docs/go/reference/calendar#getEvent); Java [`calendar().getEvent`](https://openemail.uk/docs/java/reference/calendar#getEvent); CLI [`openemail calendar get-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-get-event).

### `Calendar.GetEventIcsAsync`

Download an event as an iCalendar document

```csharp
Task<string> GetEventIcsAsync(
    string id,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Returns 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.
- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

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

**Example**

```csharp
var ics = (await client.Calendar.GetEventIcsAsync("cal_8d3f0a2b9c4e41f7a6b5c2d1"));

Console.WriteLine(ics);
```

**Notes**

- Errors still come back as JSON and throw `OpenEmailApiException`, so a failure never comes back as a string.
- Timed events are written in the zone they were created in, as local times with a `TZID` and a `VTIMEZONE` that carries the rules of that zone, and all day events as `DATE` values.
- A narrowed key gets a 404 on the same terms as `GetEventAsync`.

Also available in: API [`GET /calendar/events/{id}/ics`](https://openemail.uk/docs/api/reference/calendar#get-calendar-events-id-ics); TypeScript [`calendar.getEventIcs()`](https://openemail.uk/docs/sdk/reference/calendar#getEventIcs); Python [`calendar.get_event_ics()`](https://openemail.uk/docs/python/reference/calendar#getEventIcs); Ruby [`calendar.get_event_ics`](https://openemail.uk/docs/ruby/reference/calendar#getEventIcs); PHP [`calendar->getEventIcs`](https://openemail.uk/docs/php/reference/calendar#getEventIcs); Go [`Calendar.GetEventIcs`](https://openemail.uk/docs/go/reference/calendar#getEventIcs); Java [`calendar().getEventIcs`](https://openemail.uk/docs/java/reference/calendar#getEventIcs); CLI [`openemail calendar get-event-ics`](https://openemail.uk/docs/cli/reference/calendar#calendar-get-event-ics).

### `Calendar.CreateEventAsync`

Put an event on the calendar

```csharp
Task<JsonObject> CreateEventAsync(
    IReadOnlyDictionary<string, object?> body,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Creates an event, as New event on the Calendar page of the app does. `start` and `end` take a `DateTimeOffset` or ISO 8601 with a zone, and the SDK sends a `DateTimeOffset` as a UTC instant. `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**

- `summary` (`string`, required): The title, 1 to 255 characters.
- `start` (`DateTimeOffset or string`, required): When it starts, a `DateTimeOffset` or ISO 8601 with a zone. A `DateTimeOffset` is sent as an instant in UTC.
- `end` (`DateTimeOffset or string`, required): When it ends, after `start` and at most two years later.
- `description` (`string`): Notes about the event, up to 8,000 characters.
- `location` (`string`): Where it is: a room, an address or a link.
- `url` (`string`): A link that belongs to the event.
- `allDay` (`bool`): Whether it fills whole days. `start` and `end` then mark the days.
- `timezone` (`string`): The IANA zone the event belongs to, `UTC` unless you say.
- `recurrence` (`string`): An RRULE such as `FREQ=WEEKLY;BYDAY=MO` when it repeats.
- `attendees` (`list`): Up to 200 people to invite, each a dictionary with `email` and, if you like, `name` and `optional`.
- `reminders` (`list`): Up to five reminders, each a dictionary with `minutesBefore` and, if you like, `action` (`DISPLAY`, `EMAIL` or `AUDIO`, as in `OpenEmail\Constants\CalendarReminderActions`).
- `transparency` (`string`): `OPAQUE` shows you busy and `TRANSPARENT` free, as in `OpenEmail\Constants\CalendarTransparencies`.
- `visibility` (`string`): `PUBLIC`, `PRIVATE` or `CONFIDENTIAL`, as in `OpenEmail\Constants\CalendarVisibilities`.
- `sendInvites` (`bool`): False keeps the change to your own calendar, and sends nothing.
- `from` (`string`): The address the invitations come from. It has to be one the key may send as.
- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A `JsonObject` for the new event, shaped like the one `Calendar.GetEventAsync` returns, with its attendees and their responses.

**Example**

```csharp
var entry = await client.Calendar.CreateEventAsync(new Body
{
    ["summary"] = "Planning",
    ["start"] = DateTimeOffset.Parse("2026-10-12 09:00Z"),
    ["end"] = DateTimeOffset.Parse("2026-10-12 09:30Z"),
    ["timezone"] = "Europe/London",
    ["attendees"] = new[] { new Body { ["email"] = "ada@example.com", ["name"] = "Ada Lovelace" } },
    ["reminders"] = new[] { new Body { ["minutesBefore"] = 10 } },
    ["from"] = "team@acme.com",
});

Console.WriteLine($"{entry["id"]} {entry["start"]}");
```

**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); TypeScript [`calendar.createEvent()`](https://openemail.uk/docs/sdk/reference/calendar#createEvent); Python [`calendar.create_event()`](https://openemail.uk/docs/python/reference/calendar#createEvent); Ruby [`calendar.create_event`](https://openemail.uk/docs/ruby/reference/calendar#createEvent); PHP [`calendar->createEvent`](https://openemail.uk/docs/php/reference/calendar#createEvent); Go [`Calendar.CreateEvent`](https://openemail.uk/docs/go/reference/calendar#createEvent); Java [`calendar().createEvent`](https://openemail.uk/docs/java/reference/calendar#createEvent); CLI [`openemail calendar create-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-create-event).

### `Calendar.UpdateEventAsync`

Change an event

```csharp
Task<JsonObject> UpdateEventAsync(
    string id,
    IReadOnlyDictionary<string, object?> patch,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

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.
- `summary` (`string`): The title, 1 to 255 characters.
- `start` (`DateTimeOffset or string`): When it starts, a `DateTimeOffset` or ISO 8601 with a zone. A `DateTimeOffset` is sent as an instant in UTC.
- `end` (`DateTimeOffset or string`): When it ends, after `start` and at most two years later.
- `description` (`string`): Notes about the event, up to 8,000 characters.
- `location` (`string`): Where it is: a room, an address or a link.
- `url` (`string`): A link that belongs to the event.
- `allDay` (`bool`): Whether it fills whole days. `start` and `end` then mark the days.
- `timezone` (`string`): The IANA zone the event belongs to, `UTC` unless you say.
- `recurrence` (`string`): An RRULE such as `FREQ=WEEKLY;BYDAY=MO` when it repeats.
- `attendees` (`list`): Up to 200 people to invite, each a dictionary with `email` and, if you like, `name` and `optional`. It replaces the whole list.
- `reminders` (`list`): Up to five reminders, each a dictionary with `minutesBefore` and, if you like, `action` (`DISPLAY`, `EMAIL` or `AUDIO`, as in `OpenEmail\Constants\CalendarReminderActions`).
- `transparency` (`string`): `OPAQUE` shows you busy and `TRANSPARENT` free, as in `OpenEmail\Constants\CalendarTransparencies`.
- `visibility` (`string`): `PUBLIC`, `PRIVATE` or `CONFIDENTIAL`, as in `OpenEmail\Constants\CalendarVisibilities`.
- `sendInvites` (`bool`): False keeps the change to your own calendar, and sends nothing.
- `from` (`string`): The address the invitations come from. It has to be one the key may send as.
- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A `JsonObject` for the event as it stands after the change, shaped like the one `Calendar.GetEventAsync` returns, with `sequence` one higher.

**Example**

```csharp
var entry = await client.Calendar.UpdateEventAsync("cal_3f9a1c7e5b2d48a06c1e9f4b", new Body
{
    ["start"] = "2026-10-12T10:00:00Z",
    ["end"] = "2026-10-12T10:30:00Z",
    ["from"] = "team@acme.com",
});

Console.WriteLine($"{entry["start"]}, sequence {entry["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 `RespondToEventAsync` 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); TypeScript [`calendar.updateEvent()`](https://openemail.uk/docs/sdk/reference/calendar#updateEvent); Python [`calendar.update_event()`](https://openemail.uk/docs/python/reference/calendar#updateEvent); Ruby [`calendar.update_event`](https://openemail.uk/docs/ruby/reference/calendar#updateEvent); PHP [`calendar->updateEvent`](https://openemail.uk/docs/php/reference/calendar#updateEvent); Go [`Calendar.UpdateEvent`](https://openemail.uk/docs/go/reference/calendar#updateEvent); Java [`calendar().updateEvent`](https://openemail.uk/docs/java/reference/calendar#updateEvent); CLI [`openemail calendar update-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-update-event).

### `Calendar.DeleteEventAsync`

Remove an event from the calendar

```csharp
Task<JsonObject> DeleteEventAsync(
    string id,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

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 `CancelEventAsync` instead.

Scopes: `calendar:write`.

**Parameters**

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

**Returns**

A `JsonObject` with `object`, `id` and `deleted` set to true.

**Example**

```csharp
var deleted = await client.Calendar.DeleteEventAsync("cal_3f9a1c7e5b2d48a06c1e9f4b");

Console.WriteLine($"{deleted["id"]} is off the calendar");
```

**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); TypeScript [`calendar.deleteEvent()`](https://openemail.uk/docs/sdk/reference/calendar#deleteEvent); Python [`calendar.delete_event()`](https://openemail.uk/docs/python/reference/calendar#deleteEvent); Ruby [`calendar.delete_event`](https://openemail.uk/docs/ruby/reference/calendar#deleteEvent); PHP [`calendar->deleteEvent`](https://openemail.uk/docs/php/reference/calendar#deleteEvent); Go [`Calendar.DeleteEvent`](https://openemail.uk/docs/go/reference/calendar#deleteEvent); Java [`calendar().deleteEvent`](https://openemail.uk/docs/java/reference/calendar#deleteEvent); CLI [`openemail calendar delete-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-delete-event).

### `Calendar.CancelEventAsync`

Call off a meeting

```csharp
Task<JsonObject> CancelEventAsync(
    string id,
    IReadOnlyDictionary<string, object?>? body = null,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

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.
- `from` (`string`): The address the cancellation comes from. It has to be one the key may send as.
- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A `JsonObject` for the event, shaped like the one `Calendar.GetEventAsync` returns, with `status` set to `CANCELLED`.

**Example**

```csharp
var entry = await client.Calendar.CancelEventAsync("cal_3f9a1c7e5b2d48a06c1e9f4b", body: new Body { ["from"] = "team@acme.com" });

Console.WriteLine($"{entry["summary"]} is now {entry["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); TypeScript [`calendar.cancelEvent()`](https://openemail.uk/docs/sdk/reference/calendar#cancelEvent); Python [`calendar.cancel_event()`](https://openemail.uk/docs/python/reference/calendar#cancelEvent); Ruby [`calendar.cancel_event`](https://openemail.uk/docs/ruby/reference/calendar#cancelEvent); PHP [`calendar->cancelEvent`](https://openemail.uk/docs/php/reference/calendar#cancelEvent); Go [`Calendar.CancelEvent`](https://openemail.uk/docs/go/reference/calendar#cancelEvent); Java [`calendar().cancelEvent`](https://openemail.uk/docs/java/reference/calendar#cancelEvent); CLI [`openemail calendar cancel-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-cancel-event).

### `Calendar.RespondToEventAsync`

Answer an invitation

```csharp
Task<JsonObject> RespondToEventAsync(
    string id,
    IReadOnlyDictionary<string, object?> body,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

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.
- `response` (`string`, required): `ACCEPTED`, `DECLINED` or `TENTATIVE`, as in `OpenEmail\Constants\CalendarResponses`.
- `respondingAs` (`string`): Which of your invited addresses answers, when there is more than one.
- `proposedStart` (`DateTimeOffset or string`): A new start to propose with your answer, a `DateTimeOffset` or ISO 8601 with a zone, sent together with `proposedEnd`. A `DateTimeOffset` is sent as an instant in UTC.
- `proposedEnd` (`DateTimeOffset or string`): The end of the proposed time.
- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A `JsonObject` for the event, shaped like the one `Calendar.GetEventAsync` returns, with your answer on your attendee row.

**Example**

```csharp
using OpenEmail.Constants;

var entry = await client.Calendar.RespondToEventAsync("cal_3f9a1c7e5b2d48a06c1e9f4b", new Body { ["response"] = CalendarResponses.Accepted });

foreach (var attendee in entry["attendees"]?.AsArray() ?? [])
{
    Console.WriteLine($"{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`.
- With `proposedStart` and `proposedEnd` the organiser gets a counter-proposal, as Propose a new time sends from Google Calendar and Outlook, and decides whether to move the meeting.

Also available in: API [`POST /calendar/events/{id}/respond`](https://openemail.uk/docs/api/reference/calendar#post-calendar-events-id-respond); TypeScript [`calendar.respondToEvent()`](https://openemail.uk/docs/sdk/reference/calendar#respondToEvent); Python [`calendar.respond_to_event()`](https://openemail.uk/docs/python/reference/calendar#respondToEvent); Ruby [`calendar.respond_to_event`](https://openemail.uk/docs/ruby/reference/calendar#respondToEvent); PHP [`calendar->respondToEvent`](https://openemail.uk/docs/php/reference/calendar#respondToEvent); Go [`Calendar.RespondToEvent`](https://openemail.uk/docs/go/reference/calendar#respondToEvent); Java [`calendar().respondToEvent`](https://openemail.uk/docs/java/reference/calendar#respondToEvent); CLI [`openemail calendar respond-to-event`](https://openemail.uk/docs/cli/reference/calendar#calendar-respond-to-event).

### `Calendar.DeclineProposalAsync`

Decline a proposed time

```csharp
Task<JsonObject> DeclineProposalAsync(
    string id,
    IReadOnlyDictionary<string, object?> body,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Keeps the original time of a meeting you organise after a guest proposed another one, as Keep the original does on the event in the app. The guest is told by email from `from`, or from the organising address, so it needs `emails:send` as well. To take the proposed time instead, change the event with `Calendar.UpdateEventAsync`.

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

**Parameters**

- `id` (`string`, required): Event id, the `eventId` of an occurrence, such as `cal_` followed by 24 hex characters.
- `attendee` (`string`, required): The guest whose proposal you decline, by email address.
- `from` (`string`): The address the answer is sent from, when it is not the organising address.
- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A `JsonObject` for the event, shaped like the one `Calendar.GetEventAsync` returns, with the guest's `proposedStart` and `proposedEnd` cleared.

**Example**

```csharp
var entry = await client.Calendar.DeclineProposalAsync("cal_3f9a1c7e5b2d48a06c1e9f4b", new Body { ["attendee"] = "ada@example.com" });

Console.WriteLine($"{entry["summary"]} keeps its time");
```

**Notes**

- A guest who has not proposed a new time is refused with 404 `proposal_not_found`, and an event somebody else organises with 403 `not_organizer`.

Also available in: API [`POST /calendar/events/{id}/decline-proposal`](https://openemail.uk/docs/api/reference/calendar#post-calendar-events-id-decline-proposal); TypeScript [`calendar.declineProposal()`](https://openemail.uk/docs/sdk/reference/calendar#declineProposal); Python [`calendar.decline_proposal()`](https://openemail.uk/docs/python/reference/calendar#declineProposal); Ruby [`calendar.decline_proposal`](https://openemail.uk/docs/ruby/reference/calendar#declineProposal); PHP [`calendar->declineProposal`](https://openemail.uk/docs/php/reference/calendar#declineProposal); Go [`Calendar.DeclineProposal`](https://openemail.uk/docs/go/reference/calendar#declineProposal); Java [`calendar().declineProposal`](https://openemail.uk/docs/java/reference/calendar#declineProposal); CLI [`openemail calendar decline-proposal`](https://openemail.uk/docs/cli/reference/calendar#calendar-decline-proposal).

### `Calendar.ListFeedsAsync`

List calendar links

```csharp
Task<IReadOnlyList<JsonObject>> ListFeedsAsync(
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Returns the secret links that publish your calendar to other calendar apps, as the Share tab of the calendar settings in the app lists them. Anyone with a link can read what it shows, so treat `url` and `webcalUrl` like passwords.

Scopes: `calendar:read`.

**Parameters**

- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A list of `JsonObject` items, each with `id`, `name`, `mode`, `url`, `webcalUrl` and when calendar apps last read it.

**Example**

```csharp
foreach (var feed in (await client.Calendar.ListFeedsAsync()))
{
    Console.WriteLine($"{feed["name"]} ({feed["mode"]}): {feed["url"]}");
}
```

**Notes**

- A key limited to particular addresses or domains is refused with 422 `capability_unsupported`, because a link covers the whole calendar.

Also available in: API [`GET /calendar/feeds`](https://openemail.uk/docs/api/reference/calendar#get-calendar-feeds); TypeScript [`calendar.listFeeds()`](https://openemail.uk/docs/sdk/reference/calendar#listFeeds); Python [`calendar.list_feeds()`](https://openemail.uk/docs/python/reference/calendar#listFeeds); Ruby [`calendar.list_feeds`](https://openemail.uk/docs/ruby/reference/calendar#listFeeds); PHP [`calendar->listFeeds`](https://openemail.uk/docs/php/reference/calendar#listFeeds); Go [`Calendar.ListFeeds`](https://openemail.uk/docs/go/reference/calendar#listFeeds); Java [`calendar().listFeeds`](https://openemail.uk/docs/java/reference/calendar#listFeeds); CLI [`openemail calendar list-feeds`](https://openemail.uk/docs/cli/reference/calendar#calendar-list-feeds).

### `Calendar.CreateFeedAsync`

Create a calendar link

```csharp
Task<JsonObject> CreateFeedAsync(
    IReadOnlyDictionary<string, object?> body,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Makes a secret link that publishes your calendar as an iCalendar feed, for Google Calendar (From URL), Outlook (Subscribe from web) or Apple Calendar (New subscription). `full` shows every detail, `busy` only the times you are busy. The calendar app reads the link again on its own schedule, usually every few hours.

Scopes: `calendar:write`.

**Parameters**

- `name` (`string`, required): The name calendar apps show for the calendar, up to 100 characters.
- `mode` (`string`, required): `full` for every detail, `busy` for busy times only, as in `OpenEmail\Constants\CalendarFeedModes`.
- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A `JsonObject` for the link with the new `url` and `webcalUrl`.

**Example**

```csharp
using OpenEmail.Constants;

var feed = await client.Calendar.CreateFeedAsync(new Body { ["name"] = "Work (busy)", ["mode"] = CalendarFeedModes.Busy });

Console.WriteLine($"Paste into Google Calendar: {feed["url"]}");
```

**Notes**

- One person may keep 10 links. One more is refused with 409 `calendar_feed_limit`.

Also available in: API [`POST /calendar/feeds`](https://openemail.uk/docs/api/reference/calendar#post-calendar-feeds); TypeScript [`calendar.createFeed()`](https://openemail.uk/docs/sdk/reference/calendar#createFeed); Python [`calendar.create_feed()`](https://openemail.uk/docs/python/reference/calendar#createFeed); Ruby [`calendar.create_feed`](https://openemail.uk/docs/ruby/reference/calendar#createFeed); PHP [`calendar->createFeed`](https://openemail.uk/docs/php/reference/calendar#createFeed); Go [`Calendar.CreateFeed`](https://openemail.uk/docs/go/reference/calendar#createFeed); Java [`calendar().createFeed`](https://openemail.uk/docs/java/reference/calendar#createFeed); CLI [`openemail calendar create-feed`](https://openemail.uk/docs/cli/reference/calendar#calendar-create-feed).

### `Calendar.ResetFeedAsync`

Reset a calendar link

```csharp
Task<JsonObject> ResetFeedAsync(
    string id,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Replaces the secret in a calendar link, so the old address stops working everywhere it was shared. Paste the new address into the calendar apps you still want to update.

Scopes: `calendar:write`.

**Parameters**

- `id` (`string`, required): Calendar link id, `cfd_` followed by 24 hex characters.
- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A `JsonObject` for the link with its new `url` and `webcalUrl`.

**Example**

```csharp
var feed = await client.Calendar.ResetFeedAsync("cfd_3f9a1c7e5b2d48a06c1e9f4b");

Console.WriteLine($"{feed["url"]}");
```

**Notes**

- A link that is not yours is a 404 `calendar_feed_not_found`.

Also available in: API [`POST /calendar/feeds/{id}/reset`](https://openemail.uk/docs/api/reference/calendar#post-calendar-feeds-id-reset); TypeScript [`calendar.resetFeed()`](https://openemail.uk/docs/sdk/reference/calendar#resetFeed); Python [`calendar.reset_feed()`](https://openemail.uk/docs/python/reference/calendar#resetFeed); Ruby [`calendar.reset_feed`](https://openemail.uk/docs/ruby/reference/calendar#resetFeed); PHP [`calendar->resetFeed`](https://openemail.uk/docs/php/reference/calendar#resetFeed); Go [`Calendar.ResetFeed`](https://openemail.uk/docs/go/reference/calendar#resetFeed); Java [`calendar().resetFeed`](https://openemail.uk/docs/java/reference/calendar#resetFeed); CLI [`openemail calendar reset-feed`](https://openemail.uk/docs/cli/reference/calendar#calendar-reset-feed).

### `Calendar.DeleteFeedAsync`

Delete a calendar link

```csharp
Task<JsonObject> DeleteFeedAsync(
    string id,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Turns a calendar link off for good. Calendar apps that subscribed to it stop getting updates.

Scopes: `calendar:write`.

**Parameters**

- `id` (`string`, required): Calendar link id, `cfd_` followed by 24 hex characters.
- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A `JsonObject` with `object`, `id` and `deleted` set to true.

**Example**

```csharp
var deleted = await client.Calendar.DeleteFeedAsync("cfd_3f9a1c7e5b2d48a06c1e9f4b");

Console.WriteLine($"{deleted["id"]}");
```

**Notes**

- A link that is not yours is a 404 `calendar_feed_not_found`.

Also available in: API [`DELETE /calendar/feeds/{id}`](https://openemail.uk/docs/api/reference/calendar#delete-calendar-feeds-id); TypeScript [`calendar.deleteFeed()`](https://openemail.uk/docs/sdk/reference/calendar#deleteFeed); Python [`calendar.delete_feed()`](https://openemail.uk/docs/python/reference/calendar#deleteFeed); Ruby [`calendar.delete_feed`](https://openemail.uk/docs/ruby/reference/calendar#deleteFeed); PHP [`calendar->deleteFeed`](https://openemail.uk/docs/php/reference/calendar#deleteFeed); Go [`Calendar.DeleteFeed`](https://openemail.uk/docs/go/reference/calendar#deleteFeed); Java [`calendar().deleteFeed`](https://openemail.uk/docs/java/reference/calendar#deleteFeed); CLI [`openemail calendar delete-feed`](https://openemail.uk/docs/cli/reference/calendar#calendar-delete-feed).

### `Calendar.ListSubscriptionsAsync`

List calendar subscriptions

```csharp
Task<IReadOnlyList<JsonObject>> ListSubscriptionsAsync(
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Returns the other calendars you subscribe to by link, such as a Google or Outlook calendar address, a team rota or public holidays, as Other calendars in the app lists them. Their events show on your calendar, read-only.

Scopes: `calendar:read`.

**Parameters**

- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A list of `JsonObject` items, each with `name`, `url`, `color`, `status`, `lastError` and how many events it holds.

**Example**

```csharp
foreach (var subscription in (await client.Calendar.ListSubscriptionsAsync()))
{
    Console.WriteLine($"{subscription["name"]}: {subscription["status"]}, {subscription["eventCount"]} events");
}
```

**Notes**

- `status` is `failing` while reads fail and are retried, and `stopped` after ten failures in a row until you refresh it.

Also available in: API [`GET /calendar/subscriptions`](https://openemail.uk/docs/api/reference/calendar#get-calendar-subscriptions); TypeScript [`calendar.listSubscriptions()`](https://openemail.uk/docs/sdk/reference/calendar#listSubscriptions); Python [`calendar.list_subscriptions()`](https://openemail.uk/docs/python/reference/calendar#listSubscriptions); Ruby [`calendar.list_subscriptions`](https://openemail.uk/docs/ruby/reference/calendar#listSubscriptions); PHP [`calendar->listSubscriptions`](https://openemail.uk/docs/php/reference/calendar#listSubscriptions); Go [`Calendar.ListSubscriptions`](https://openemail.uk/docs/go/reference/calendar#listSubscriptions); Java [`calendar().listSubscriptions`](https://openemail.uk/docs/java/reference/calendar#listSubscriptions); CLI [`openemail calendar list-subscriptions`](https://openemail.uk/docs/cli/reference/calendar#calendar-list-subscriptions).

### `Calendar.CreateSubscriptionAsync`

Subscribe to a calendar

```csharp
Task<JsonObject> CreateSubscriptionAsync(
    IReadOnlyDictionary<string, object?> body,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Adds a calendar by its iCalendar link, an `https`, `http` or `webcal` address, such as the secret address of a Google calendar or a published Outlook calendar. It is read at once, then again about every half hour. `name` defaults to the name the calendar gives itself.

Scopes: `calendar:write`.

**Parameters**

- `url` (`string`, required): The calendar link, `https`, `http` or `webcal`.
- `name` (`string`): The name to show, up to 100 characters.
- `color` (`string`): The colour of its events, as in `OpenEmail\Constants\CalendarColors`.
- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A `JsonObject` for the subscription with its first events read.

**Example**

```csharp
using OpenEmail.Constants;

var holidays = await client.Calendar.CreateSubscriptionAsync(new Body { ["url"] = "webcal://example.com/holidays.ics", ["color"] = CalendarColors.Green });

Console.WriteLine($"{holidays["name"]}: {holidays["eventCount"]} events");
```

**Notes**

- An address that does not answer with a calendar is refused with 422 `calendar_subscription_unreadable`, and one you already subscribe to with 409 `calendar_subscription_exists`.
- One person may subscribe to 20 calendars.

Also available in: API [`POST /calendar/subscriptions`](https://openemail.uk/docs/api/reference/calendar#post-calendar-subscriptions); TypeScript [`calendar.createSubscription()`](https://openemail.uk/docs/sdk/reference/calendar#createSubscription); Python [`calendar.create_subscription()`](https://openemail.uk/docs/python/reference/calendar#createSubscription); Ruby [`calendar.create_subscription`](https://openemail.uk/docs/ruby/reference/calendar#createSubscription); PHP [`calendar->createSubscription`](https://openemail.uk/docs/php/reference/calendar#createSubscription); Go [`Calendar.CreateSubscription`](https://openemail.uk/docs/go/reference/calendar#createSubscription); Java [`calendar().createSubscription`](https://openemail.uk/docs/java/reference/calendar#createSubscription); CLI [`openemail calendar create-subscription`](https://openemail.uk/docs/cli/reference/calendar#calendar-create-subscription).

### `Calendar.UpdateSubscriptionAsync`

Change a calendar subscription

```csharp
Task<JsonObject> UpdateSubscriptionAsync(
    string id,
    IReadOnlyDictionary<string, object?> patch,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Renames a subscription or gives it another colour.

Scopes: `calendar:write`.

**Parameters**

- `id` (`string`, required): Subscription id, `csb_` followed by 24 hex characters.
- `name` (`string`): The new name, up to 100 characters.
- `color` (`string`): The new colour, as in `OpenEmail\Constants\CalendarColors`.
- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A `JsonObject` for the subscription as it stands now.

**Example**

```csharp
using OpenEmail.Constants;

var subscription = await client.Calendar.UpdateSubscriptionAsync("csb_3f9a1c7e5b2d48a06c1e9f4b", new Body { ["name"] = "Team rota", ["color"] = CalendarColors.Purple });

Console.WriteLine($"{subscription["name"]}");
```

**Notes**

- A subscription that is not yours is a 404 `calendar_subscription_not_found`.

Also available in: API [`PATCH /calendar/subscriptions/{id}`](https://openemail.uk/docs/api/reference/calendar#patch-calendar-subscriptions-id); TypeScript [`calendar.updateSubscription()`](https://openemail.uk/docs/sdk/reference/calendar#updateSubscription); Python [`calendar.update_subscription()`](https://openemail.uk/docs/python/reference/calendar#updateSubscription); Ruby [`calendar.update_subscription`](https://openemail.uk/docs/ruby/reference/calendar#updateSubscription); PHP [`calendar->updateSubscription`](https://openemail.uk/docs/php/reference/calendar#updateSubscription); Go [`Calendar.UpdateSubscription`](https://openemail.uk/docs/go/reference/calendar#updateSubscription); Java [`calendar().updateSubscription`](https://openemail.uk/docs/java/reference/calendar#updateSubscription); CLI [`openemail calendar update-subscription`](https://openemail.uk/docs/cli/reference/calendar#calendar-update-subscription).

### `Calendar.RefreshSubscriptionAsync`

Refresh a calendar subscription

```csharp
Task<JsonObject> RefreshSubscriptionAsync(
    string id,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Reads a subscribed calendar again now instead of waiting for the next half-hourly read, and restarts one that stopped after repeated failures.

Scopes: `calendar:write`.

**Parameters**

- `id` (`string`, required): Subscription id, `csb_` followed by 24 hex characters.
- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A `JsonObject` for the subscription after the read, with `status` and `lastError`.

**Example**

```csharp
var subscription = await client.Calendar.RefreshSubscriptionAsync("csb_3f9a1c7e5b2d48a06c1e9f4b");

if (subscription["lastError"] is not null)
{
    Console.WriteLine($"Could not read it: {subscription["lastError"]}");
}
```

**Notes**

- A calendar read less than a minute ago is refused with 429 `calendar_subscription_refresh_too_soon`.

Also available in: API [`POST /calendar/subscriptions/{id}/refresh`](https://openemail.uk/docs/api/reference/calendar#post-calendar-subscriptions-id-refresh); TypeScript [`calendar.refreshSubscription()`](https://openemail.uk/docs/sdk/reference/calendar#refreshSubscription); Python [`calendar.refresh_subscription()`](https://openemail.uk/docs/python/reference/calendar#refreshSubscription); Ruby [`calendar.refresh_subscription`](https://openemail.uk/docs/ruby/reference/calendar#refreshSubscription); PHP [`calendar->refreshSubscription`](https://openemail.uk/docs/php/reference/calendar#refreshSubscription); Go [`Calendar.RefreshSubscription`](https://openemail.uk/docs/go/reference/calendar#refreshSubscription); Java [`calendar().refreshSubscription`](https://openemail.uk/docs/java/reference/calendar#refreshSubscription); CLI [`openemail calendar refresh-subscription`](https://openemail.uk/docs/cli/reference/calendar#calendar-refresh-subscription).

### `Calendar.DeleteSubscriptionAsync`

Unsubscribe from a calendar

```csharp
Task<JsonObject> DeleteSubscriptionAsync(
    string id,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Removes a subscription and every event it brought onto your calendar.

Scopes: `calendar:write`.

**Parameters**

- `id` (`string`, required): Subscription id, `csb_` followed by 24 hex characters.
- `apiKey` (`string?`): Overrides the client's API key for this call only.
- `cancellationToken` (`CancellationToken`): Cancels the request.

**Returns**

A `JsonObject` with `object`, `id` and `deleted` set to true.

**Example**

```csharp
var deleted = await client.Calendar.DeleteSubscriptionAsync("csb_3f9a1c7e5b2d48a06c1e9f4b");

Console.WriteLine($"{deleted["id"]}");
```

**Notes**

- A subscription that is not yours is a 404 `calendar_subscription_not_found`.

Also available in: API [`DELETE /calendar/subscriptions/{id}`](https://openemail.uk/docs/api/reference/calendar#delete-calendar-subscriptions-id); TypeScript [`calendar.deleteSubscription()`](https://openemail.uk/docs/sdk/reference/calendar#deleteSubscription); Python [`calendar.delete_subscription()`](https://openemail.uk/docs/python/reference/calendar#deleteSubscription); Ruby [`calendar.delete_subscription`](https://openemail.uk/docs/ruby/reference/calendar#deleteSubscription); PHP [`calendar->deleteSubscription`](https://openemail.uk/docs/php/reference/calendar#deleteSubscription); Go [`Calendar.DeleteSubscription`](https://openemail.uk/docs/go/reference/calendar#deleteSubscription); Java [`calendar().deleteSubscription`](https://openemail.uk/docs/java/reference/calendar#deleteSubscription); CLI [`openemail calendar delete-subscription`](https://openemail.uk/docs/cli/reference/calendar#calendar-delete-subscription).

### `Calendar.ImportIcsAsync`

Import a calendar file

```csharp
Task<JsonObject> ImportIcsAsync(
    Stream data,
    string? filename = null,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Sends an iCalendar (.ics) file as the request body and queues it. Its events become events of your own that you can edit, and nobody on them is sent anything, now or when you change them later, unless you ask for that on the change. Poll `GetImportAsync` until `status` is `completed`.

The file may be 10 MB and hold 25,000 events. Several calendars in one file are merged, and the report keeps their names. An event whose UID is already in the workspace is skipped and counted, never overwritten, so sending the same file twice adds nothing.

`data` is a readable `Stream`, and it goes out as `text/calendar`.

Scopes: `calendar:write`.

**Parameters**

- `data` (`Stream`, required): The .ics file: a readable `Stream`.
- `filename` (`string?`): The name to show for the file in the list of imports, at most 255 characters.
- `cancellationToken` (`CancellationToken`): Cancels the request.
- `apiKey` (`string?`): Overrides the client API key for this call only.

**Returns**

A `JsonObject` with `status: queued`.

**Example**

```csharp
await using var file = File.OpenRead("calendar.ics");

var import = await client.Calendar.ImportIcsAsync(file, filename: "calendar.ics");

Console.WriteLine($"{import["id"]} is {import["status"]}");
```

**Notes**

- A body that is not an iCalendar file is a 400 `not_a_calendar_file`, an empty one a 400 `item_import_empty`, and one holding more than 25,000 events a 400 `too_many_events`.
- A file over 10 MB is a 413 `item_import_too_large`.
- To send text you already hold, such as what `GetEventIcsAsync` returns, encode it first with `new TextEncoder().encode(text)`.
- An upload that outlasts the client `Timeout` fails as a network error, so raise it on the client for a large file on a slow connection.
- Not retried automatically.

Also available in: API [`POST /calendar/imports`](https://openemail.uk/docs/api/reference/calendar#post-calendar-imports); TypeScript [`calendar.importIcs()`](https://openemail.uk/docs/sdk/reference/calendar#importIcs); Python [`calendar.import_ics()`](https://openemail.uk/docs/python/reference/calendar#importIcs); Ruby [`calendar.import_ics`](https://openemail.uk/docs/ruby/reference/calendar#importIcs); PHP [`calendar->importIcs`](https://openemail.uk/docs/php/reference/calendar#importIcs); Go [`Calendar.ImportIcs`](https://openemail.uk/docs/go/reference/calendar#importIcs); Java [`calendar().importIcs`](https://openemail.uk/docs/java/reference/calendar#importIcs); CLI [`openemail calendar import-ics`](https://openemail.uk/docs/cli/reference/calendar#calendar-import-ics).

### `Calendar.ListImportsAsync`

List calendar imports

```csharp
Task<Page> ListImportsAsync(
    int? limit = null,
    string? cursor = null,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Returns one page of the calendar files and calendars brought in, newest first, each with what it added and what it left out. Beside the files sent with `ImportIcsAsync` it lists the calendars that came with a mailbox import, and those carry the id of that import in `parentImportId`.

Paging is keyset. `limit:` takes 1 to 100 and defaults to 25, and `nextCursor` goes back as `cursor:` while `hasMore` is true. `ListAllImportsAsync` and `IterateImportsAsync` do that walk for you.

Scopes: `calendar:read`.

**Parameters**

- `limit` (`int?`): Imports per page, a whole number from 1 to 100. The server defaults to 25.
- `cursor` (`string?`): The `nextCursor` from the previous page. Never build one yourself.
- `cancellationToken` (`CancellationToken`): Cancels the request.
- `apiKey` (`string?`): Overrides the client API key for this call only.

**Returns**

A `Page` with `items`, `hasMore` and `nextCursor`.

**Example**

```csharp
var page = await client.Calendar.ListImportsAsync(limit: 10);

foreach (var import in page)
{
    Console.WriteLine($"{import["id"]} {import["source"]} {import["status"]}: {import["counts"]?["imported"]} events added");
}
```

**Notes**

- `source` is `file` for an upload. `takeout`, `caldav` and `microsoft` came with a mailbox import.
- A cursor that names no import is a 400 `invalid_cursor`.
- A GET is retried automatically on network failure and on 408, 500, 502, 503 and 504 responses, up to the client `MaxRetries`, and on a 429 only when it carries a `Retry-After` of a minute or less.

Also available in: API [`GET /calendar/imports`](https://openemail.uk/docs/api/reference/calendar#get-calendar-imports); TypeScript [`calendar.listImports()`](https://openemail.uk/docs/sdk/reference/calendar#listImports); Python [`calendar.list_imports()`](https://openemail.uk/docs/python/reference/calendar#listImports); Ruby [`calendar.list_imports`](https://openemail.uk/docs/ruby/reference/calendar#listImports); PHP [`calendar->listImports`](https://openemail.uk/docs/php/reference/calendar#listImports); Go [`Calendar.ListImports`](https://openemail.uk/docs/go/reference/calendar#listImports); Java [`calendar().listImports`](https://openemail.uk/docs/java/reference/calendar#listImports); CLI [`openemail calendar list-imports`](https://openemail.uk/docs/cli/reference/calendar#calendar-list-imports).

### `Calendar.ListAllImportsAsync`

Collect every calendar import into one list

```csharp
Task<IReadOnlyList<JsonObject>> ListAllImportsAsync(
    int? limit = null,
    string? cursor = null,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Follows `nextCursor` from page to page and returns every calendar import, newest first.

Everything is held in memory before the call returns. Prefer `IterateImportsAsync` when you can stop early. `limit` sets the page size of each request, not the total.

Scopes: `calendar:read`.

**Parameters**

- `limit` (`int?`): Page size per request, from 1 to 100, defaulting to 25 on the server.
- `cursor` (`string?`): A cursor from an earlier page to start after.
- `cancellationToken` (`CancellationToken`): Cancels the request in flight and rejects the whole walk.
- `apiKey` (`string?`): Overrides the client API key for every page of this walk.

**Returns**

A list of `JsonObject` items holding every import across all pages.

**Example**

```csharp
var imports = await client.Calendar.ListAllImportsAsync();

Console.WriteLine($"{imports.Count} calendar imports, {imports.Count(import => (bool?)import["undoable"] == true)} of them can be undone");
```

**Notes**

- A failure on any page rejects the whole call, and the imports already fetched are discarded.

Also available in: API [`GET /calendar/imports`](https://openemail.uk/docs/api/reference/calendar#get-calendar-imports); TypeScript [`calendar.listAllImports()`](https://openemail.uk/docs/sdk/reference/calendar#listAllImports); Python [`calendar.list_all_imports()`](https://openemail.uk/docs/python/reference/calendar#listAllImports); Ruby [`calendar.list_all_imports`](https://openemail.uk/docs/ruby/reference/calendar#listAllImports); PHP [`calendar->listAllImports`](https://openemail.uk/docs/php/reference/calendar#listAllImports); Go [`Calendar.ListAllImports`](https://openemail.uk/docs/go/reference/calendar#listAllImports); Java [`calendar().listAllImports`](https://openemail.uk/docs/java/reference/calendar#listAllImports).

### `Calendar.IterateImportsAsync`

Stream calendar imports one at a time

```csharp
IAsyncEnumerable<JsonObject> IterateImportsAsync(
    int? limit = null,
    string? cursor = null,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Returns an `IAsyncEnumerable<JsonObject>` that yields calendar imports one by one, newest first, and requests the next page only once the current one is drained. Nothing is fetched until you consume it, and breaking out of the loop stops the requests.

Scopes: `calendar:read`.

**Parameters**

- `limit` (`int?`): Page size per request, from 1 to 100, defaulting to 25 on the server.
- `cursor` (`string?`): A cursor from an earlier page to start after.
- `cancellationToken` (`CancellationToken`): Cancels the request in flight and rejects the whole walk.
- `apiKey` (`string?`): Overrides the client API key for every page of this walk.

**Returns**

An `IAsyncEnumerable<JsonObject>` yielding one import per step.

**Example**

```csharp
await foreach (var import in client.Calendar.IterateImportsAsync(limit: 50))
{
    if ((string?)import["status"] == "failed")
    {
        Console.WriteLine($"{import["id"]} failed");

        break;
    }
}
```

**Notes**

- The generator is lazy, so an abandoned loop costs only the pages you consumed.

Also available in: API [`GET /calendar/imports`](https://openemail.uk/docs/api/reference/calendar#get-calendar-imports); TypeScript [`calendar.iterateImports()`](https://openemail.uk/docs/sdk/reference/calendar#iterateImports); Python [`calendar.iterate_imports()`](https://openemail.uk/docs/python/reference/calendar#iterateImports); Ruby [`calendar.iterate_imports`](https://openemail.uk/docs/ruby/reference/calendar#iterateImports); PHP [`calendar->iterateImports`](https://openemail.uk/docs/php/reference/calendar#iterateImports); Go [`Calendar.IterateImports`](https://openemail.uk/docs/go/reference/calendar#iterateImports); Java [`calendar().iterateImports`](https://openemail.uk/docs/java/reference/calendar#iterateImports).

### `Calendar.GetImportAsync`

Get a calendar import

```csharp
Task<JsonObject> GetImportAsync(
    string id,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Returns one calendar import with its counts and its report: the calendars it read, how many events it left out and why, and up to 20 of the events it left out. Poll it after `ImportIcsAsync` until `status` is `completed` or `failed`.

`counts.imported` is what was added and `counts.skipped` what was left out, which `report.skipped` breaks down by reason: `uid-taken` for an event that was already here, `no-start` for one with no start time, `invalid` for one that could not be read and `empty` for a file or calendar that held no events.

Scopes: `calendar:read`.

**Parameters**

- `id` (`string`, required): Calendar import id, `iimp_` followed by 24 hex characters.
- `cancellationToken` (`CancellationToken`): Cancels the request.
- `apiKey` (`string?`): Overrides the client API key for this call only.

**Returns**

A `JsonObject` with `status`, `counts`, `report`, `undoable` and when it started, finished and was undone.

**Example**

```csharp
var import = await client.Calendar.GetImportAsync("iimp_3f9a1c07d2b84e6a9c5b1f20");

Console.WriteLine($"{import["status"]}: {import["counts"]?["imported"]} added, {import["counts"]?["skipped"]} left out");
```

**Notes**

- An id that names no calendar import is a 404 `item_import_not_found`.
- A GET is retried automatically on network failure and on 408, 500, 502, 503 and 504 responses, up to the client `MaxRetries`, and on a 429 only when it carries a `Retry-After` of a minute or less.

Also available in: API [`GET /calendar/imports/{id}`](https://openemail.uk/docs/api/reference/calendar#get-calendar-imports-id); TypeScript [`calendar.getImport()`](https://openemail.uk/docs/sdk/reference/calendar#getImport); Python [`calendar.get_import()`](https://openemail.uk/docs/python/reference/calendar#getImport); Ruby [`calendar.get_import`](https://openemail.uk/docs/ruby/reference/calendar#getImport); PHP [`calendar->getImport`](https://openemail.uk/docs/php/reference/calendar#getImport); Go [`Calendar.GetImport`](https://openemail.uk/docs/go/reference/calendar#getImport); Java [`calendar().getImport`](https://openemail.uk/docs/java/reference/calendar#getImport); CLI [`openemail calendar get-import`](https://openemail.uk/docs/cli/reference/calendar#calendar-get-import).

### `Calendar.UndoImportAsync`

Remove what a calendar import added

```csharp
Task<JsonObject> UndoImportAsync(
    string id,
    string? apiKey = null,
    CancellationToken cancellationToken = default)
```

Deletes every event the import added, changed since or not, and marks the import `undone`. Nobody is told. Events that were skipped because they were already here are not touched.

Scopes: `calendar:write`.

**Parameters**

- `id` (`string`, required): Calendar import id, `iimp_` followed by 24 hex characters.
- `cancellationToken` (`CancellationToken`): Cancels the request.
- `apiKey` (`string?`): Overrides the client API key for this call only.

**Returns**

A `JsonObject` with `status: undone` and `undoneAt` set.

**Example**

```csharp
try
{
    var undone = await client.Calendar.UndoImportAsync("iimp_3f9a1c07d2b84e6a9c5b1f20");

    Console.WriteLine($"{undone["id"]} is {undone["status"]} since {undone["undoneAt"]}");
}
catch (OpenEmailApiException error) when (error.IsConflict)
{
    Console.WriteLine("That import is still running or was already removed");
}
```

**Notes**

- An import that is still running, or was already removed, is a 409 `item_import_not_undoable`. `undoable` on the import says whether the call would remove anything.
- An id that names no calendar import is a 404 `item_import_not_found`.
- Not retried automatically.

Also available in: API [`POST /calendar/imports/{id}/undo`](https://openemail.uk/docs/api/reference/calendar#post-calendar-imports-id-undo); TypeScript [`calendar.undoImport()`](https://openemail.uk/docs/sdk/reference/calendar#undoImport); Python [`calendar.undo_import()`](https://openemail.uk/docs/python/reference/calendar#undoImport); Ruby [`calendar.undo_import`](https://openemail.uk/docs/ruby/reference/calendar#undoImport); PHP [`calendar->undoImport`](https://openemail.uk/docs/php/reference/calendar#undoImport); Go [`Calendar.UndoImport`](https://openemail.uk/docs/go/reference/calendar#undoImport); Java [`calendar().undoImport`](https://openemail.uk/docs/java/reference/calendar#undoImport); CLI [`openemail calendar undo-import`](https://openemail.uk/docs/cli/reference/calendar#calendar-undo-import).
