So integrieren Sie die iCloud Calendar API in Ihre App

Autoren
Veröffentlicht am

In unserem vorherigen Artikel haben wir erläutert, wie Sie die Google Calendar API in Ihre App integrieren und dabei alle nötigen Schritte zur erfolgreichen Integration aufgezeigt.

Im Gegensatz zu Google Calendar ist die Integration des iCloud-Kalenders nicht so geradlinig, da es kaum Dokumentation gibt und Apple nicht genau darlegt, was Entwickler tun müssen, um iCloud Calendar in eine Anwendung einzubinden.

In diesem Artikel tauchen wir tief in die Integration der iCloud Calendar API ein – einschließlich Authentifizierung, unterstützter Funktionen, Einschränkungen und Tools, die die Integration vereinfachen.

Welche Protokolle und Standards verwendet Apple iCloud Calendar?

Apple iCloud Calendar nutzt den CalDAV-Standard (eine Erweiterung von WebDAV) für die Kalenderkommunikation.

Ereignisse werden im ICS- (iCalendar-) Format gespeichert – einem textbasierten Format für Kalenderdaten. Ihre Anwendung kann also via HTTP mit CalDAV-Anfragen und ICS-Daten mit iCloud-Kalendern kommunizieren.

Vorteil: Die Implementierung ist nicht an Apple-Geräte oder -Betriebssysteme gebunden und kann auf jedem Server unter jedem Betriebssystem eingesetzt werden.

Nachteil: iCloud stellt keine REST-API für Kalender bereit; CalDAV ist daher der einzige Weg, iCloud Calendar in Ihre App einzubinden.

Wir empfehlen eine reine Server-zu-Server-Integration – deshalb führt an CalDAV kein Weg vorbei.

Wie authentifiziert man sich bei iCloud?

Normalerweise legt man bei einer Plattform (z. B. Google Calendar) ein Entwicklerkonto an, erstellt eine App, konfiguriert Scopes, fügt Testnutzer hinzu, trägt App-Informationen ein und lässt alles genehmigen. Danach leitet man den Endnutzer über den OAuth-Dialog zur Einwilligung.

Bei Apple iCloud funktioniert das anders: iCloud bietet keinen standardisierten OAuth-Flow wie Google Calendar oder Outlook. Um den Kalender eines Nutzers zu verbinden, müssen Sie dessen Apple-ID über Basic Authentication (SSL) ansprechen. Da die meisten iCloud-Konten die Zwei-Faktor-Authentifizierung aktiviert haben, muss der Nutzer in seinen Apple-ID-Einstellungen ein app-spezifisches Passwort erstellen, statt sein Hauptpasswort zu nutzen.

Ihre Anwendung fragt also die iCloud-E-Mail/Apple-ID sowie dieses 16-stellige app-spezifische Passwort ab. Mit diesen Daten verbinden Sie sich mit dem iCloud-CalDAV-Dienst.

Beispiel, wie Ihre App E-Mail-Adresse und app-spezifisches Passwort abfragt.

How to integrate iCloud Calendar API into your app

Apple verlangt app-spezifische Passwörter, weil das Weitergeben des Hauptpassworts an Drittanbieter unsicher wäre.

Im HTTP-Header Ihrer Anfrage steht dann:

Authorization: Basic <app-specific-password-here>

Welche Methoden unterstützt die iCloud Calendar API?

Der iCloud-CalDAV-Dienst ist unter caldav.icloud.com erreichbar. Nach der Authentifizierung stehen diese Operationen bereit:

  • Auflisten der Kalender eines Nutzers

  • CRUD-Operationen für Ereignisse

  • Abrufen einzelner Ereignisse

Weitere Details finden Sie in RFC 4791. Nachfolgend eine Zusammenfassung der wichtigsten Punkte für Ihre iCloud-Integration.

Unterstützte HTTP-Verben

HTTP-Verb

Funktion in CalDAV

iCloud-Unterstützung

Besonderheiten

OPTIONS

Server-Fähigkeiten ermitteln

Gut zum Debuggen; zur Laufzeit meist nicht nötig

PROPFIND

Principals ermitteln, calendar-home-set abrufen, Kalender auflisten, Eigenschaften lesen

Authentifizierung erforderlich; Depth 0 oder 1

MKCALENDAR

Neue Kalender-Collection anlegen

Schreibrechte erforderlich; siehe Abschnitt 5.3.1

REPORT

Daten abfragen (calendar-query, calendar-multiget, free-busy-query)

Alle drei Reports werden unterstützt

PUT

Eine .ics-Ressource (Ereignis/Aufgabe) hochladen/ersetzen

Komplettes VCALENDAR senden; kein PATCH

DELETE

Ereignis oder Kalender löschen

Mit If-Match-ETag kombinieren

COPY / MOVE

Ereignisse zwischen Kalendern kopieren/verschieben

Gleiche Voraussetzungen wie PUT

GET

Eine .ics-Ressource abrufen

Liefert text/calendar und ETag

Eigenschaften einer Kalender-Collection

Eigenschaft

Zweck

iCloud-spezifische Hinweise

CALDAV:calendar-description

Menschlich lesbare Beschreibung

Vollständig unterstützt

CALDAV:calendar-timezone

Standard-Zeitzone für Abfragen

Unterstützt

CALDAV:supported-calendar-component-set

Akzeptierte Komponenten (VEVENT, VTODO)

Ereignisse und Aufgaben liegen in separaten Kalendern

CALDAV:supported-calendar-data

Erlaubte MIME/Version (text/calendar 2.0)

Standardwert bei iCloud

CALDAV:max-resource-size

Max. Größe pro Ereignis

iCloud-Limit ca. 20 MB

CALDAV:min/max-date-time, max-instances, max-attendees-per-instance

Diverse Serverlimits

Einhaltung verhindert 403/507-Fehler

Die OneCal Unified Calendar API steht kurz vor dem Start – tragen Sie sich in unsere Warteliste ein, um informiert zu werden und von Einführungsrabatten zu profitieren.

Welche Bibliotheken erleichtern die Integration?

CalDAV, ICS, XML und iCloud-Spezifika manuell umzusetzen ist aufwendig. Wir empfehlen:

Rolle im Stack

Was tsdav macht

Warum das für iCloud wichtig ist

CalDAV/WebDAV-Client

Bietet eine High-Level-TypeScript-API, die alle HTTP-Verben und XML-Operationen kapselt

Fokus auf Business-Logik statt auf rohe XML-Generierung

Discovery-Helfer

createDAVClient() führt automatisch den iCloud-Discovery-Flow aus: Verbindung zu caldav.icloud.com, Principal ermitteln, calendar-home-set auflösen, endgültige Basis-URL speichern

Spart Boilerplate für die zweistufige Discovery

Auth-Wrapper

Unterstützt Basic und OAuth 2 (für iCloud: { username, password, authMethod: 'Basic' })

Kein manuelles Base64-Kodieren der Zugangsdaten

Typisierte CRUD-Helfer

fetchCalendars(), fetchCalendarObjects(), createCalendarObject(), updateCalendarObject(), deleteCalendarObject() liefern/akzeptieren reine JS-Objekte

Schnelle CRUD-Implementierung ohne RFC-4791-XML

Sync-Token-Support

syncCollection() kapselt den REPORT sync-collection, verfolgt Tokens und liefert nur Änderungen/Löschungen

Effizientes Polling (iCloud hat kein Push)

Browser + Node

Läuft in Node und im Browser dank isomorphic fetch

Nützlich bei Browser-Extensions oder SPAs

Modernes TS-Projekt

Vollständige Typdefinitionen, tree-shakable ES-Module, minimale Abhängigkeiten

Leicht in moderne Build-Pipelines einzubinden

tsdav erstellt keine iCalendar-Dateien. Nutzen Sie dazu ical-generator (oder eine andere ICS-Bibliothek) zur Erstellung der Event-Payloads.

Weitere nützliche Bibliotheken:

  • ical-generator – Erstellt komplette .ics-Dateien mitsamt Headern, UID, RRULE, Zeitzonen usw.

  • ical.js – Reine JavaScript-Engine (Mozilla-Projekt) zum Parsen von ICS-Antworten in JS-Objekte.

Beispiel: iCloud-Integration mit tsdav + TypeScript

In diesem Beispiel haben Sie bereits den Benutzernamen und das app-spezifische Passwort des Nutzers.

createClient DAVClient erzeugen

import { DAVCalendar, DAVClient, DAVNamespaceShort, DAVObject } from "tsdav";

const APPLE_DAV_URL = "https://caldav.icloud.com";

function createClient({
  username,
  password,
}: {
  username: string;
  password: string;
}) {
  const client = new DAVClient({
    serverUrl: APPLE_DAV_URL,
    credentials: {
      username,
      password,
    },
    authMethod: "Basic",
    defaultAccountType: "caldav",
  });

  return client;
}

constants Konstanten

export const PROD_ID = {
  company: "your-company-name-here",
  product: "your-product-name-here",
};

getCalendars Alle Kalender abrufen

  async getCalendars(
    ...params: Parameters<typeof DAVClient.prototype.fetchCalendars>
  ): Promise<DAVCalendar[]> {
  
  // You can abstract this initialization into another method.
  // For the sake of simplicity, we'll initialize the client on each method.
   const client = createClient({
    username: <email-here>,
    password: <password-here>,
  });
  
    return client.fetchCalendars(...params);
  }

getCalendarById Kalender per ID abrufen

async function getCalendarById(calendarUrl: string): Promise<DAVCalendar> {
  const calendars = await getCalendars();
  const calendar = calendars.find((el) => el.url === calendarUrl);
  if (!calendar) {
    throw new Error(`Apple-Kalender mit ID ${calendarUrl} nicht gefunden`);
  }

  return calendar;
}

getCalendarEvents Ereignisse eines Kalenders abrufen

async function getCalendarEvents(
  calendarUrl: string,
  query: GetCalendarEventsQuery = {}
) {
  const client = createClient({
    username: <email-here>,
    password: <password-here>,
  });

  const calendar = await getCalendarById(calendarUrl);

  const events = await client.fetchCalendarObjects({
    calendar,
    timeRange: query.dateRange ?? undefined,
  });

  return { events, nextSyncToken: calendar.syncToken };
}

getEventById Ereignis per ID abrufen

async function getEventById(calendarUrl: string, eventId: string) {
  const client = createClient({
    username: <email-here>,
    password: <password-here>,
  });

  const eventUrl = new URL(`${eventId}.ics`, calendarUrl).pathname;

  const responses = await client.calendarMultiGet({
    url: calendarUrl,
    props: {
      [`${DAVNamespaceShort.DAV}:getetag`]: {},
      [`${DAVNamespaceShort.CALDAV}:calendar-data`]: {},
    },
    objectUrls: [eventUrl],
    depth: "1",
  });

  if (responses.length === 0) {
    throw new Error(`Keine Antwort beim Abrufen von ${eventUrl}`);
  } else if (responses[0].status >= 400) {
    throw new Error(
      `Fehler beim Abrufen des Apple-Ereignisses. Status: ${responses[0].statusText}`
    );
  }

  const response = responses[0];
  const calendarObject: DAVObject = {
    url: new URL(response.href ?? "", calendarUrl).href,
    etag: `${response.props?.getetag}`,
    data: response.props?.calendarData?._cdata ?? response.props?.calendarData,
  };

  return calendarObject;
}

createEvent Ereignis erstellen

async function createEvent(calendarUrl: string, data: AppleEvent) {
  const client = createClient({
    username: <email-here>,
    password: <password-here>,
  });

  const eventId = data.id ?? <generate-id-here>;

  const calendar = ical({
    prodId: PROD_ID,
    method: ICalCalendarMethod.REQUEST,
  });

  const event = calendar.createEvent({ ...data, id: eventId });

  const response = await client.createCalendarObject({
    calendar: { url: calendarUrl },
    filename: `${event.id()}.ics`,
    iCalString: calendar.toString(),
  });

  if (!response.ok) {
    throw new Error(
      `Fehler beim Erstellen des Apple-Ereignisses: ${response.statusText}`
    );
  }

  return { id: event.id(), eventWithExceptions };
}

updateEvent Ereignis aktualisieren

async function updateEvent(
  calendarUrl: string,
  eventId: string,
  data: AppleEvent
) {
  const client = createClient({
    username: <email-here>,
    password: <password-here>,
  });

  const originalEventData = await getEventById(calendarUrl, eventId);

  const calendar = ical({
    prodId: PROD_ID,
    method: ICalCalendarMethod.REQUEST,
  });

  for (let event of originalEventData) {
    if (event.status === ICalEventStatus.CANCELLED) continue;

    if (event.id === eventId) {
      calendar.createEvent({ ...event, ...data, id: eventId, url: null });
    } else {
      calendar.createEvent({ ...event, id: eventId, url: null });
    }
  }

  const calendarObjectUrl = new URL(`${eventId}.ics`, calendarUrl);

  const response = await client.updateCalendarObject({
    calendarObject: {
      url: calendarObjectUrl.href,
      data: calendar.toString(),
    },
  });

  if (!response.ok) {
    throw new Error(
      `Fehler beim Aktualisieren des Apple-Ereignisses: ${response.statusText}`
    );
  }

  return getEventById(calendarUrl, eventId);
}

deleteEvent Ereignis löschen

async function deleteEvent(calendarUrl: string, eventId: string) {
  const client = createClient({
    username: <email-here>,
    password: <password-here>,
  });

  const calendarObjectUrl = new URL(`${eventId}.ics`, calendarUrl);

  const response = await client.deleteCalendarObject({
    calendarObject: {
      url: calendarObjectUrl.href,
    },
  });

  if (!response.ok) {
    throw new Error(
      `Fehler beim Löschen des Apple-Ereignisses: ${response.statusText}`
    );
  }

  return { id: eventId };
}

Welche Einschränkungen hat Apple iCloud Calendar?

  1. Basic Auth mit app-spezifischem Passwort – Kein OAuth 2.0.

  2. Keine Webhooks/Push-Benachrichtigungen – Es müssen Polling-Lösungen mit sync-collection gebaut werden.

  3. Kein PATCH – Ereignisse müssen vollständig per PUT überschrieben werden.

  4. Keine manuelle Kontrolle über Einladungen – iCloud versendet Einladungen automatisch (Outbox/Inbox nicht verfügbar).

Gibt es einen einfacheren Weg, iCloud Calendar zu integrieren?

Die manuelle Integration von iCloud Calendar ist komplex und weicht von gängigen Kalender-Standards ab. Eine Unified Calendar API bietet:

  • Gut dokumentierte, getestete API nach modernen Standards

  • Weniger Entwicklungs- und Wartungsaufwand

  • Integration weiterer Kalenderanbieter (Google Calendar, Outlook) ohne Zusatzarbeit

  • Push-Benachrichtigungen ohne eigene Polling-Lösung

Unified Calendar API Example

Nutzen Sie eine Unified Calendar API für die iCloud-Integration

Unsere OneCal Unified Calendar API steht kurz vor dem Start. Melden Sie sich für die Warteliste an, um sofort benachrichtigt zu werden und von Einführungsrabatten zu profitieren.

Häufig gestellte Fragen

Welches Protokoll verwendet iCloud Kalender?

iCloud Kalender nutzt CalDAV über HTTP und speichert Ereignisse im iCalendar-(ICS-)Format.

Bietet iCloud Kalender eine REST-API?

Nein. CalDAV ist die einzige Möglichkeit, auf iCloud Kalenderdaten zuzugreifen und sie zu ändern.

Wie authentifiziert man sich bei iCloud Kalender?

Man übergibt die Apple-ID des Nutzers (meist die E-Mail) und ein 16-stelliges app-spezifisches Passwort per Basic Auth über SSL.

Unterstützt iCloud Kalender Push-Benachrichtigungen oder Webhooks?

Nein. Änderungen müssen durch Polling erkannt werden.

Welche Bibliotheken erleichtern die Integration?

tsdav (CalDAV-Client), ical-generator (ICS erstellen) und ical.js (ICS parsen) übernehmen die meisten Low-Level-Aufgaben.

Wie kann ich CalDAV und ICS umgehen?

Nutzen Sie eine Unified Calendar API wie die OneCal Unified Calendar API, die iCloud, Google und Outlook über eine moderne JSON-Schnittstelle vereint.