Cómo integrar la API de iCloud Calendar en tu aplicación

Autores
Publicado el

En nuestro artículo anterior, explicamos cómo integrar la API de Google Calendar en tu aplicación, resaltando todos los pasos necesarios para que la integración funcione.

A diferencia de Google Calendar, integrar iCloud Calendar no es tan sencillo, ya que la documentación es escasa y Apple no ha dedicado tanto esfuerzo a detallar lo que un desarrollador debe hacer para integrar iCloud Calendar en una aplicación.

En este artículo profundizaremos en cómo integrar la API de iCloud Calendar en tu aplicación, incluyendo la autenticación, las operaciones compatibles, las limitaciones y las herramientas que puedes usar para facilitar la integración.

¿Qué protocolos y estándares utiliza Apple iCloud Calendar?

Apple iCloud Calendar utiliza el estándar CalDAV para la comunicación de calendarios. CalDAV es una extensión de WebDAV que permite a los clientes gestionar calendarios y eventos en un servidor.

Los eventos se representan en el formato ICS (iCalendar), que es un formato de texto para datos de calendario. Esto significa que tu aplicación puede comunicarse con los calendarios de iCloud a través de HTTP usando solicitudes CalDAV y datos ICS.

La ventaja es que no se trata de una implementación específica de dispositivos u sistemas operativos de Apple; puedes usarla desde cualquier servidor que se ejecute en cualquier plataforma.

La mala noticia es que iCloud no proporciona una API REST para calendarios, por lo que CalDAV es la única forma de integrar iCloud Calendar en tu aplicación.

Recomendamos que la integración sea de servidor a servidor; por eso decimos que CalDAV es el único camino a seguir.

¿Cómo se autentica en iCloud?

Normalmente, cuando quieres integrar una plataforma en tu aplicación (Google Calendar, por ejemplo), debes crear una cuenta de desarrollador en esa plataforma, crear una aplicación, configurar alcances, añadir usuarios de prueba, completar la información de tu app y enviarla para aprobación.

Tras seguir todos estos pasos y obtener la aprobación, puedes dirigir al usuario final a la pantalla de OAuth de la plataforma, donde el usuario debe iniciar sesión en esa plataforma y conceder acceso explícito al alcance que solicita tu aplicación. El usuario final también ve el nombre de tu aplicación y toda la información que proporcionaste al configurar tu aplicación en esa plataforma.

Apple iCloud no funciona así, ya que iCloud no tiene un flujo OAuth estándar como Google Calendar u Outlook. Para conectar el iCloud Calendar de un usuario, debes autenticarte con su Apple ID mediante Basic Authentication sobre SSL. Debido a que la mayoría de las cuentas de iCloud tienen autenticación de dos factores, el usuario debe crear una contraseña específica de la app para tu aplicación desde la configuración de su Apple ID, en lugar de usar su contraseña principal de Apple.

Tu aplicación solicitará al usuario su correo/iCloud ID y esta contraseña específica de la app de 16 caracteres. Con estas credenciales, podrás conectarte al servicio CalDAV de iCloud.

Ejemplo de cómo tu aplicación pediría al usuario que introduzca la dirección de correo y la contraseña específica de la app.

apple-enter-app-specific-password.webp

Además de los motivos mencionados, Apple exige contraseñas específicas de la app para el acceso de terceros al calendario a fin de mejorar la seguridad, ya que compartir tu contraseña de iCloud con una aplicación de terceros no es lo ideal.

En segundo plano, la cabecera de tu solicitud HTTP incluirá la contraseña específica de la app codificada en Base64, de la siguiente forma:
Authorization: Basic <app-specific-password-here>

¿Qué métodos admite la API de iCloud Calendar?

El servicio CalDAV de iCloud se aloja en caldav.icloud.com. Tras autenticarte, están disponibles los siguientes métodos:

  • Listar los calendarios de un usuario

  • CRUD de eventos

  • Obtener eventos específicos

Para obtener más información sobre el estándar CalDAV, lee RFC 4791, que explica todos los métodos disponibles, filtros y más.

A continuación, resumo la información más importante que necesitas para tu integración con iCloud Calendar.

Verbos HTTP compatibles

Verbo HTTPQué hace en CalDAVCompatibilidad iCloudObservaciones
OPTIONSDescubre las capacidades del servidorÚtil para depurar; no requerido en producción.
PROPFINDBusca principals, calendar-home-set, lista calendarios, obtiene propiedadesAutentícate antes; usa Depth 0 o 1.
MKCALENDARCrea una nueva colección de calendarioRequiere permisos de escritura; ver Sección 5.3.1.
REPORTConsulta datos (calendar-query, calendar-multiget, free-busy-query)Los tres informes son obligatorios y están presentes en iCloud.
PUTSube/reemplaza un recurso .ics (evento/tarea)Debes enviar un VCALENDAR completo; no hay PATCH.
DELETEElimina un evento o calendarioUsa If-Match con ETag para mayor seguridad.
COPY / MOVECopia o mueve eventos entre calendariosSujetos a las mismas precondiciones de PUT.
GETObtiene un recurso .ics individualDevuelve text/calendar y ETag.

Propiedades de la colección de calendarios

PropiedadPropósitoNotas específicas de iCloud
CALDAV:calendar-descriptionDescripción legibleTotalmente compatible
CALDAV:calendar-timezoneZona horaria predeterminada para consultasCompatible
CALDAV:supported-calendar-component-setComponentes aceptados (VEVENT, VTODO)Eventos y tareas se guardan en calendarios separados
CALDAV:supported-calendar-dataMIME/versión permitida (text/calendar 2.0)Predeterminado
CALDAV:max-resource-sizeTamaño máximo por eventoLímite aproximado de 20 MB
CALDAV:min/max-date-time, max-instances, max-attendees-per-instanceLímites adicionales del servidorRespétalos para evitar errores 403/507

La OneCal Unified Calendar API está casi lista; únete a nuestra lista de espera para que te avisemos cuando la lancemos y aproveches las promociones de lanzamiento.

¿Qué bibliotecas puedes usar para simplificar la integración?

Trabajar con CalDAV, ICS, XML y otros detalles propios de iCloud Calendar no resulta ideal, ya que dedicarás mucho tiempo a entender cada método, convertir XML a JSON y usarlo en tu aplicación.

Para una integración más sencilla, recomendamos las siguientes bibliotecas:

  • tsdav: Imprescindible si empleas JavaScript/TypeScript. Te permite comunicarte con el servidor de iCloud sin manejar directamente la sintaxis CalDAV. Proporciona una API de alto nivel que envuelve los verbos HTTP y el XML que de otra forma escribirías a mano. Consulta la documentación de tsdav.

  • ical-generator: Al integrar a través de CalDAV, necesitas subir y reemplazar archivos .ics completos al crear o actualizar eventos. ical-generator facilita la creación correcta de dichos archivos.

  • ical.js: Parser/engine JavaScript puro creado por Mozilla Calendar para convertir respuestas ICS a clases JS.

Papel de tsdav en la integración con iCloud Calendar

Rol en la pilaQué hace tsdavPor qué importa en iCloud
Cliente CalDAV/WebDAVProporciona una API TypeScript de alto nivel que envuelve los verbos HTTP y el XML necesario.Evita generar XML manualmente y parsear respuestas multistatus.
Helpers de descubrimientocreateDAVClient() sigue el flujo de descubrimiento: contacta caldav.icloud.com, encuentra el principal y resuelve calendar-home-set.Elimina el boilerplate del proceso de descubrimiento propio de iCloud.
Wrappers de autenticaciónSoporta Basic y OAuth 2. Para iCloud se usa { username, password, authMethod: 'Basic' }.No necesitas codificar Base64 ni añadir cabeceras manualmente.
Helpers tipados para CRUDMétodos como fetchCalendars(), createCalendarObject() manejan objetos JS planos.Implementa CRUD rápido sin preocuparte por la sintaxis XML de la RFC 4791.
Soporte de sync tokensyncCollection() envuelve el REPORT sync-collection y devuelve solo cambios/eliminaciones.Permite sondear iCloud (no hay push) con una sola línea.
Compatibilidad Browser + NodeFunciona en Node y navegador gracias a fetch isomórfico.Útil si parte de tu app vive en una extensión o SPA.
Proyecto TS modernoTipos completos, módulos ES tree-shakable y pocas dependencias.Fácil de integrar en pipelines modernos.

tsdav no genera iCalendar. Úsalo junto con ical-generator (o tu generador favorito) para crear el contenido del evento.

Ejemplo de integración de iCloud Calendar usando tsdav + TypeScript

En este ejemplo se asume que ya has obtenido el nombre de usuario y la contraseña específica de la app del usuario.

Método createClient que crea el DAVClient

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;
}

Archivo constants

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

Método getCalendars para obtener todos los calendarios

async getCalendars(
...params: Parameters<typeof DAVClient.prototype.fetchCalendars>
): Promise<DAVCalendar[]> {
 // Para simplicidad, inicializamos el cliente en cada llamada const client = createClient({
 username: "<correo>",
 password: "<contraseña>",
});

 return client.fetchCalendars(...params);
}

Método getCalendarById

async getCalendarById(calendarUrl: string): Promise<DAVCalendar> {
 const calendars = await getCalendars();
 const calendar = calendars.find((el) => el.url === calendarUrl);

 if (!calendar) {
 throw new Error(`Apple Calendar con id ${calendarUrl} no encontrado`);
}

 return calendar;
}

Método getCalendarEvents para listar eventos

async getCalendarEvents(
calendarUrl: string,
query: GetCalendarEventsQuery = {}
) {
 const client = createClient({
 username: "<correo>",
 password: "<contraseña>",
});

 const calendar = await getCalendarById(calendarUrl);

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

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

Método getEventById

async getEventById(calendarUrl: string, eventId: string) {
 const client = createClient({
 username: "<correo>",
 password: "<contraseña>",
});

 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(`Sin respuesta al obtener ${eventUrl}`);
} else if (responses[0].status >= 400) {
 throw new Error(
 `Error al obtener evento. Estado: ${responses[0].statusText}`
);
}

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

 return calendarObject;
}

Método createEvent

async createEvent(calendarUrl: string, data: AppleEvent) {
 const client = createClient({
 username: "<correo>",
 password: "<contraseña>",
});

 const eventId = data.id ?? "<genera-id>";

 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(
 `Error al crear evento de Apple: ${response.statusText}`
);
}

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

Método updateEvent

async updateEvent(calendarUrl: string, eventId: string, data: AppleEvent) {
 const client = createClient({
 username: "<correo>",
 password: "<contraseña>",
});

 const originalData = await getEventById(calendarUrl, eventId);

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

 for (const ev of originalData) {
 if (ev.status === ICalEventStatus.CANCELLED) continue;

 if (ev.id === eventId) {
calendar.createEvent({ ...ev, ...data, id: eventId, url: null });
} else {
calendar.createEvent({ ...ev, 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(
 `Error al actualizar evento: ${response.statusText}`
);
}

 return getEventById(calendarUrl, eventId);
}

Método deleteEvent

async deleteEvent(calendarUrl: string, eventId: string) {
 const client = createClient({
 username: "<correo>",
 password: "<contraseña>",
});

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

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

 if (!response.ok) {
 throw new Error(
 `Error al eliminar evento: ${response.statusText}`
);
}

 return { id: eventId };
}

¿Qué limitaciones tiene Apple iCloud Calendar?

  1. Basic Auth con contraseña específica de la app: iCloud no sigue OAuth 2.0; debes usar una contraseña específica.

  2. Sin webhooks/notificaciones push: No puedes registrar webhooks; debes sondear y usar sync-collection.

  3. Sin soporte PATCH: No se pueden actualizar eventos parcialmente; debes hacer un PUT completo.

  4. Sin control de invitaciones: iCloud gestiona automáticamente las invitaciones y sus actualizaciones.

¿Existe una forma más sencilla de integrar iCloud Calendar?

Integrar iCloud Calendar es complejo y no sigue convenciones estándar. Una opción más simple es usar una Unified Calendar API.

Unified Calendar API Example

Ventajas de una Unified Calendar API:

  • Integra iCloud Calendar con una API moderna y documentada.

  • Menos tiempo de desarrollo y mantenimiento: la API maneja casos límite y clientes.

  • Añade otros proveedores (Google, Outlook) sin esfuerzo adicional.

  • Webhooks/push soportados sin implementar sondeo propio.

Usa una Unified Calendar API para integrar iCloud Calendar

Estamos a punto de lanzar la OneCal Unified Calendar API, que te proporcionará todos los beneficios mencionados.

Únete a nuestra lista de espera para que te avisemos en cuanto lancemos y aproveches los descuentos de lanzamiento.