# iCloud, Zoho and CalDAV (https://www.cal-sdk.com/docs/providers/caldav)

CalDAV (RFC 4791) is the open standard behind iCloud, Zoho, Fastmail, Nextcloud and many self-hosted calendars, so one package reaches all of them, with shortcuts for iCloud and Zoho. Expect solid reads and writes, recurring series and sync. What you won't get is anything CalDAV leaves out: video links, reminders, colors and search.

```npm
npm install @cal-sdk/core @cal-sdk/caldav
```

```ts tab="iCloud"
import { icloud } from '@cal-sdk/caldav';

// iCloud needs an app-specific password, made at account.apple.com under Sign-In and Security.
const calendar = icloud({
  appleId: 'me@icloud.com',
  appPassword: process.env.ICLOUD_APP_PASSWORD!,
});
```

```ts tab="Zoho"
import { zoho } from '@cal-sdk/caldav';

// The account's password, or an app-specific password when two-factor authentication is on.
const calendar = zoho({
  email: 'me@example.com',
  password: process.env.ZOHO_APP_PASSWORD!,
});
```

```ts tab="Any CalDAV server"
import { caldav } from '@cal-sdk/caldav';

const calendar = caldav({
  serverUrl: 'https://caldav.fastmail.com/dav/',
  username: 'me@fastmail.com',
  password: process.env.FASTMAIL_APP_PASSWORD!,
});
```

Then use it like any other provider:

```ts
const { calendars } = await listCalendars({ calendar });
const { events } = await listEvents({ calendar, timeMin, timeMax });
```

## Settings [#settings]

<TypeTable
  type={{
    serverUrl: { type: 'string', description: "The server's CalDAV address. The provider finds the account's calendars from it. Set by `icloud()`; optional for `zoho()`.", required: true },
    username: { type: 'string', description: '`appleId` for `icloud()`, email for `zoho()`.', required: true },
    password: { type: 'string', description: 'The password, or an app-specific password where the server asks for one. `appPassword` for `icloud()`.', required: true },
    timeZone: { type: 'string', description: 'Zone for times stored without one, and for the bounds of all-day events in listings.', default: "'UTC'" },
    headers: { type: 'Record<string, string | undefined>', description: 'Headers sent with every request.' },
    fetch: { type: 'typeof fetch', description: 'A custom fetch, for example to log or record requests.' },
  }}
/>

`zoho()` defaults to Zoho's US data center. If the account lives in another one, pass the CalDAV address Zoho Calendar shows under Settings, Synchronize, CalDAV as `serverUrl`.

## Not supported [#not-supported]

CalDAV has no `thisAndFollowing`, search, reminders, colors or free/busy endpoint, and calsdk can't create video links through it. Free/busy is computed from events. See the [comparison](/docs/providers) for every provider.

## How it behaves [#how-it-behaves]

### Discovery and ids [#discovery-and-ids]

You give the provider a server address, and it finds the account's calendars from there. That takes several requests, so it runs once per provider. `'primary'` is the server's default calendar for invitations, or else the first calendar that holds events. Task lists aren't listed, and files whose names don't end in `.ics` are skipped.

A calendar id is the calendar's path, as `listCalendars` returns it. An event id is the name of its `.ics` file without the extension, normally its `UID`. Instances of a series are `<id>_<original start>`, the start being a UTC date-time (`20261103T100000Z`) or, for all-day series, a date (`20261103`).

### Recurring events [#recurring-events]

CalDAV stores the series, not its occurrences, so calsdk expands them, with the same limits as [ICS feeds](/docs/providers/ics): rules repeating every minute or second, or with an `INTERVAL` above 1,000, list only their first instance, with a warning. Changing one instance stores an exception, and deleting one adds an `EXDATE`. Changing a whole series through one of its instances moves the series by as much as that instance moved. When a series changes its start or its rule, exceptions for instances it no longer has are dropped.

### Writing events [#writing-events]

Updates and deletes send `ifMatch` to the server, and a stale one throws `ConflictError`. Every update is also conditional on the version calsdk just read, so a change someone makes in between is never overwritten.

The server invites attendees, from the account's email address, and `sendUpdates: 'none'` asks it not to send invitations. calsdk can't create a video link, but it reads a Meet, Teams or Zoom link from the event's URL, location or description into `conference`.

### Sync [#sync]

Sync uses the server's sync tokens (RFC 6578). Each sync reports the single events, series and exceptions that changed, never computed occurrences. A token the server no longer accepts throws `SyncCursorExpiredError`.

### Your password [#your-password]

CalDAV sends your password with every request, so calsdk only sends it to the server's own origin and to the HTTPS host discovery finds the calendars on (iCloud keeps them on a host of their own). A redirect anywhere else fails rather than leaking the password.

## Provider options [#provider-options]

None. Events have no `providerMetadata`.
