# Calendar – API reference

> Appointments, including repeating ones.

Appointments, including repeating ones.

## List appointments in a time range

**`GET https://api.lifebutler.io/v1/workspaces/{workspaceId}/events`** · scope `calendar:read` · MCP tool `list_events`

Every appointment that overlaps [from, to], with repeating series expanded into single occurrences, sorted by start. Default range: today and the next 30 days; at most 366 days and 1000 occurrences.

### Path

| Field | Type | Description |
|---|---|---|
| `workspaceId` | string | Workspace id (from list_workspaces). |

### Query

| Field | Type | Description |
|---|---|---|
| `from` | string | ISO 8601 or YYYY-MM-DD (default: start of today, Berlin). |
| `to` | string | ISO 8601 or YYYY-MM-DD; a plain date includes that whole day (default: from + 30 days). |

### Request

```bash
curl "https://api.lifebutler.io/v1/workspaces/f8Hn3wQz0a/events" \
  -H "Authorization: Bearer $LIFEBUTLER_KEY"
```

### Response `200`

| Field | Type | Description |
|---|---|---|
| `data` | array of Occurrence |  |
| `data[].eventId` | string |  |
| `data[].title` | string |  |
| `data[].allDay` | boolean |  |
| `data[].start` | string |  |
| `data[].end` | string |  |
| `data[].location` | string |  |
| `data[].notes` | string |  |
| `data[].attendeeIds` | array of string |  |
| `data[].recurring` | boolean |  |
| `data[].readOnly` | boolean |  |
| `truncated` | boolean |  |

```json
{
  "data": [
    {
      "eventId": "Ev7Kp2Lq9X",
      "title": "Elternabend 3b",
      "allDay": false,
      "start": "2026-11-20T19:30:00+01:00",
      "end": "2026-11-20T21:00:00+01:00",
      "location": "Grundschule Am Park",
      "notes": "",
      "attendeeIds": [
        "string"
      ],
      "recurring": false,
      "readOnly": false
    }
  ],
  "truncated": false
}
```

## Create an appointment

**`POST https://api.lifebutler.io/v1/workspaces/{workspaceId}/events`** · scope `calendar:write` · MCP tool `create_event`

Creates an appointment, optionally repeating. Attendees get the usual push notification; linked Google calendars sync it like one made in the app.

### Path

| Field | Type | Description |
|---|---|---|
| `workspaceId` | string | Workspace id (from list_workspaces). |

### Body

| Field | Type | Description |
|---|---|---|
| `title` *(required)* | string |  |
| `allDay` | boolean | Default false. |
| `start` *(required)* | string | Timed: ISO 8601 ("2026-11-20T19:30:00+01:00"; without offset = Berlin time). All-day: "YYYY-MM-DD". |
| `end` | string | Timed: ISO 8601, after start (default start + 1 h). All-day: last day YYYY-MM-DD, inclusive (default: the start day). |
| `location` | string |  |
| `notes` | string |  |
| `attendeeIds` | array of string | Member ids (list_members). |
| `recurrence` | Recurrence or null |  |
| `recurrence.frequency` *(required)* | `daily` \| `weekly` \| `monthly` \| `yearly` |  |
| `recurrence.interval` | integer | Every n days/weeks/months/years. Default: `1`. |
| `recurrence.until` | string or null | Last day (YYYY-MM-DD) an occurrence may start on; null or missing: forever. |

### Request

```bash
curl -X POST "https://api.lifebutler.io/v1/workspaces/f8Hn3wQz0a/events" \
  -H "Authorization: Bearer $LIFEBUTLER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Elternabend 3b", "start": "2026-11-20T19:30:00+01:00", "end": "2026-11-20T21:00:00+01:00"}'
```

### Response `201`

| Field | Type | Description |
|---|---|---|
| `id` | string |  |
| `title` | string |  |
| `allDay` | boolean |  |
| `start` | string | ISO 8601, Europe/Berlin offset. First occurrence for series. |
| `end` | string |  |
| `startDate` | string | YYYY-MM-DD (Berlin) of start. |
| `endDate` | string | YYYY-MM-DD (Berlin) of end, inclusive. |
| `location` | string |  |
| `notes` | string |  |
| `attendeeIds` | array of string | Member ids. |
| `recurrence` | Recurrence or null |  |
| `recurrence.frequency` | `daily` \| `weekly` \| `monthly` \| `yearly` |  |
| `recurrence.interval` | integer | Every n days/weeks/months/years. Default: `1`. |
| `recurrence.until` | string or null | Last day (YYYY-MM-DD) an occurrence may start on; null or missing: forever. |
| `source` | `lifebutler` \| `google` \| `shared` | google: comes from a member's linked Google calendar; shared: a read-only copy shared from another workspace. |
| `readOnly` | boolean |  |
| `createdBy` | string or null |  |
| `createdAt` | string or null |  |

```json
{
  "id": "Ab3Cd5Ef7G",
  "title": "Elternabend 3b",
  "allDay": false,
  "start": "2026-11-20T19:30:00+01:00",
  "end": "2026-11-20T21:00:00+01:00",
  "startDate": "2026-11-20",
  "endDate": "2026-11-20",
  "location": "Grundschule Am Park",
  "notes": "",
  "attendeeIds": [
    "string"
  ],
  "recurrence": {
    "frequency": "daily",
    "interval": 1,
    "until": "2026-11-20"
  },
  "source": "lifebutler",
  "readOnly": false,
  "createdBy": "string",
  "createdAt": "2026-11-20T19:30:00+01:00"
}
```

## Get an appointment

**`GET https://api.lifebutler.io/v1/workspaces/{workspaceId}/events/{eventId}`** · scope `calendar:read` · MCP tool `get_event`

One appointment; for a series the series itself with its recurrence.

### Path

| Field | Type | Description |
|---|---|---|
| `workspaceId` | string | Workspace id (from list_workspaces). |
| `eventId` | string |  |

### Request

```bash
curl "https://api.lifebutler.io/v1/workspaces/f8Hn3wQz0a/events/Ev7Kp2Lq9X" \
  -H "Authorization: Bearer $LIFEBUTLER_KEY"
```

### Response `200`

| Field | Type | Description |
|---|---|---|
| `id` | string |  |
| `title` | string |  |
| `allDay` | boolean |  |
| `start` | string | ISO 8601, Europe/Berlin offset. First occurrence for series. |
| `end` | string |  |
| `startDate` | string | YYYY-MM-DD (Berlin) of start. |
| `endDate` | string | YYYY-MM-DD (Berlin) of end, inclusive. |
| `location` | string |  |
| `notes` | string |  |
| `attendeeIds` | array of string | Member ids. |
| `recurrence` | Recurrence or null |  |
| `recurrence.frequency` | `daily` \| `weekly` \| `monthly` \| `yearly` |  |
| `recurrence.interval` | integer | Every n days/weeks/months/years. Default: `1`. |
| `recurrence.until` | string or null | Last day (YYYY-MM-DD) an occurrence may start on; null or missing: forever. |
| `source` | `lifebutler` \| `google` \| `shared` | google: comes from a member's linked Google calendar; shared: a read-only copy shared from another workspace. |
| `readOnly` | boolean |  |
| `createdBy` | string or null |  |
| `createdAt` | string or null |  |

```json
{
  "id": "Ab3Cd5Ef7G",
  "title": "Elternabend 3b",
  "allDay": false,
  "start": "2026-11-20T19:30:00+01:00",
  "end": "2026-11-20T21:00:00+01:00",
  "startDate": "2026-11-20",
  "endDate": "2026-11-20",
  "location": "Grundschule Am Park",
  "notes": "",
  "attendeeIds": [
    "string"
  ],
  "recurrence": {
    "frequency": "daily",
    "interval": 1,
    "until": "2026-11-20"
  },
  "source": "lifebutler",
  "readOnly": false,
  "createdBy": "string",
  "createdAt": "2026-11-20T19:30:00+01:00"
}
```

## Change an appointment

**`PATCH https://api.lifebutler.io/v1/workspaces/{workspaceId}/events/{eventId}`** · scope `calendar:write` · MCP tool `update_event`

Changes only the fields you send. For a series this changes the whole series. Send recurrence: null to stop repeating. Read-only copies (source shared) answer 409.

### Path

| Field | Type | Description |
|---|---|---|
| `workspaceId` | string | Workspace id (from list_workspaces). |
| `eventId` | string |  |

### Body

| Field | Type | Description |
|---|---|---|
| `title` | string |  |
| `allDay` | boolean | Default false. |
| `start` | string | Timed: ISO 8601 ("2026-11-20T19:30:00+01:00"; without offset = Berlin time). All-day: "YYYY-MM-DD". |
| `end` | string | Timed: ISO 8601, after start (default start + 1 h). All-day: last day YYYY-MM-DD, inclusive (default: the start day). |
| `location` | string |  |
| `notes` | string |  |
| `attendeeIds` | array of string | Member ids (list_members). |
| `recurrence` | Recurrence or null |  |
| `recurrence.frequency` *(required)* | `daily` \| `weekly` \| `monthly` \| `yearly` |  |
| `recurrence.interval` | integer | Every n days/weeks/months/years. Default: `1`. |
| `recurrence.until` | string or null | Last day (YYYY-MM-DD) an occurrence may start on; null or missing: forever. |

### Request

```bash
curl -X PATCH "https://api.lifebutler.io/v1/workspaces/f8Hn3wQz0a/events/Ev7Kp2Lq9X" \
  -H "Authorization: Bearer $LIFEBUTLER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"end": "2026-11-20T21:00:00+01:00"}'
```

### Response `200`

| Field | Type | Description |
|---|---|---|
| `id` | string |  |
| `title` | string |  |
| `allDay` | boolean |  |
| `start` | string | ISO 8601, Europe/Berlin offset. First occurrence for series. |
| `end` | string |  |
| `startDate` | string | YYYY-MM-DD (Berlin) of start. |
| `endDate` | string | YYYY-MM-DD (Berlin) of end, inclusive. |
| `location` | string |  |
| `notes` | string |  |
| `attendeeIds` | array of string | Member ids. |
| `recurrence` | Recurrence or null |  |
| `recurrence.frequency` | `daily` \| `weekly` \| `monthly` \| `yearly` |  |
| `recurrence.interval` | integer | Every n days/weeks/months/years. Default: `1`. |
| `recurrence.until` | string or null | Last day (YYYY-MM-DD) an occurrence may start on; null or missing: forever. |
| `source` | `lifebutler` \| `google` \| `shared` | google: comes from a member's linked Google calendar; shared: a read-only copy shared from another workspace. |
| `readOnly` | boolean |  |
| `createdBy` | string or null |  |
| `createdAt` | string or null |  |

```json
{
  "id": "Ab3Cd5Ef7G",
  "title": "Elternabend 3b",
  "allDay": false,
  "start": "2026-11-20T19:30:00+01:00",
  "end": "2026-11-20T21:00:00+01:00",
  "startDate": "2026-11-20",
  "endDate": "2026-11-20",
  "location": "Grundschule Am Park",
  "notes": "",
  "attendeeIds": [
    "string"
  ],
  "recurrence": {
    "frequency": "daily",
    "interval": 1,
    "until": "2026-11-20"
  },
  "source": "lifebutler",
  "readOnly": false,
  "createdBy": "string",
  "createdAt": "2026-11-20T19:30:00+01:00"
}
```

## Delete an appointment

**`DELETE https://api.lifebutler.io/v1/workspaces/{workspaceId}/events/{eventId}`** · scope `calendar:write` · MCP tool `delete_event`

Deletes the appointment (for a series: all of it). Read-only copies answer 409.

### Path

| Field | Type | Description |
|---|---|---|
| `workspaceId` | string | Workspace id (from list_workspaces). |
| `eventId` | string |  |

### Request

```bash
curl -X DELETE "https://api.lifebutler.io/v1/workspaces/f8Hn3wQz0a/events/Ev7Kp2Lq9X" \
  -H "Authorization: Bearer $LIFEBUTLER_KEY"
```

### Response `200`

| Field | Type | Description |
|---|---|---|
| `id` | string |  |
| `deleted` | `true` |  |

```json
{
  "id": "Ab3Cd5Ef7G",
  "deleted": false
}
```
