# How to create iCloud calendar events

Source: https://developer.nylas.com/docs/cookbook/calendar/events/create-events-icloud/

Apple has no public calendar REST API. Creating events on iCloud Calendar natively means constructing iCalendar (ICS) payloads wrapped in XML envelopes and sending them over CalDAV, a protocol that requires persistent connections and manual credential management. Nylas handles all of that for you and exposes iCloud Calendar through the same [Events API](/docs/reference/api/events/) you use for Google and Microsoft.

This guide walks through creating events on iCloud Calendar accounts, including the app-specific password requirement, participant notification behavior, and the iCloud-specific limitations you should plan around.

## Why use Nylas instead of CalDAV directly?

CalDAV is functional, but building event creation on it directly comes with real costs:

- **ICS format construction.** Creating an event means building a valid iCalendar object with correct VTIMEZONE blocks, VEVENT properties, and RRULE syntax, all wrapped in a CalDAV PUT request. Nylas gives you a JSON body and a single POST endpoint.
- **No conferencing auto-create.** Google can generate Meet links automatically when you create an event. Microsoft can attach Teams links. CalDAV has nothing comparable. You would need to integrate with a conferencing provider separately.
- **No room resources.** CalDAV does not support the concept of room or resource booking. If your app needs meeting rooms, iCloud cannot provide them natively.
- **No programmatic password generation.** Every user must manually create an app-specific password through their Apple ID settings. This step cannot be automated.
- **Connection management.** You need to maintain CalDAV sessions per user, handle reconnections, and manage sync state yourself. Nylas does this behind the scenes.

If you only target iCloud and are comfortable with iCalendar format, CalDAV works. For multi-provider apps or faster development, Nylas removes the protocol-level complexity entirely.

## Before you begin

You'll need:

- A [Nylas application](/docs/v3/getting-started/) with a valid API key
- A [grant](/docs/v3/auth/) for an iCloud account using the **iCloud connector** (not generic IMAP)
- An iCloud connector configured in your Nylas application


> **Info:** 
> **New to Nylas?** Start with the [quickstart guide](/docs/v3/getting-started/) to set up your app and connect a test account before continuing here.


### App-specific passwords

iCloud requires **app-specific passwords** for third-party access. Unlike Google or Microsoft OAuth, there's no way to generate these programmatically. Each user must create one manually in their Apple ID settings.

Nylas supports two authentication flows for iCloud:

| Method                              | Best for                                                               |
| ----------------------------------- | ---------------------------------------------------------------------- |
| Hosted OAuth                        | Production apps where Nylas guides users through the app password flow |
| Bring Your Own (BYO) Authentication | Custom auth pages where you collect credentials directly               |

With either method, users need to:

1. Go to [appleid.apple.com](https://appleid.apple.com/) and sign in
2. Navigate to **Sign-In and Security** then **App-Specific Passwords**
3. Generate a new app password
4. Use that password (not their regular iCloud password) when authenticating

> **Warn:** 
> **App-specific passwords can't be generated via API.** Your app's onboarding flow should include clear instructions telling users how to create one. Users who enter their regular iCloud password will fail authentication.

The full setup walkthrough is in the [iCloud provider guide](/docs/provider-guides/icloud/) and the [app passwords guide](/docs/provider-guides/app-passwords/).

## Create an event

Make a [Create Event request](/docs/reference/api/events/create-event/) with the grant ID and a `calendar_id` query parameter. Nylas creates the event on the specified calendar and returns the new event object with its `id`.

> **Warn:** 
> **iCloud does not support `calendar_id=primary`.** You must call the [List Calendars endpoint](/docs/reference/api/calendar/get-all-calendars/) first to get the actual calendar ID for the account. The default calendar name varies by language and region, so always discover calendar IDs dynamically.

> **Error:** 
> **You're about to send a real event invite!** The following samples send an email from the account you connected to the Nylas API to any email addresses in the `participants` sub-object. Make sure you actually want to invite those addresses before running this request.

```bash
curl --compressed --request POST \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events?calendar_id=<CALENDAR_ID>' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "title": "Annual Philosophy Club Meeting",
    "busy": true,
    "conferencing": {
        "provider": "Zoom Meeting",
        "autocreate": {
          "conf_grant_id": "<NYLAS_GRANT_ID>",
          "conf_settings": {
            "settings": {
              "join_before_host": true,
              "waiting_room": false,
              "mute_upon_entry": false,
              "auto_recording": "none"
            }
          }
        }
      },
    "participants": [
      {
        "name": "Leyah Miller",
        "email": "leyah@example.com"
      },
      {
        "name": "Nyla",
        "email": "nyla@example.com"
      }
    ],
    "resources": [{
      "name": "Conference room",
      "email": "conference-room@example.com"
    }],
    "description": "Come ready to talk philosophy!",
    "when": {
      "start_time": 1674604800,
      "end_time": 1722382420,
      "start_timezone": "America/New_York",
      "end_timezone": "America/New_York"
    },
    "location": "New York Public Library, Cave room",
    "recurrence": [
      "RRULE:FREQ=WEEKLY;BYDAY=MO",
      "EXDATE:20211011T000000Z"
  ],
}'

```

```json
{
  "request_id": "1",
  "data": {
    "busy": true,
    "calendar_id": "primary",
    "conferencing": {
      "details": {
        "meeting_code": "<MEETING_CODE>",
        "url": "<MEETING_URL>"
      },
      "provider": "Google Meet"
    },
    "created_at": 1701974804,
    "creator": {
      "email": "leyah@example.com",
      "name": "Leyah Miller"
    },
    "description": null,
    "grant_id": "<NYLAS_GRANT_ID>",
    "hide_participants": false,
    "html_link": "<EVENT_LINK>",
    "ical_uid": "6aaaaaaame8kpgcid6hvd0q@google.com",
    "id": "<NYLAS_EVENT_ID>",
    "object": "event",
    "organizer": {
      "email": "leyah@example.com",
      "name": "Leyah Miller"
    },
    "participants": [
      {
        "email": "jenna.doe@example.com",
        "status": "yes"
      },
      {
        "email": "leyah@example.com",
        "status": "yes"
      }
    ],
    "read_only": true,
    "reminders": {
      "overrides": null,
      "use_default": true
    },
    "status": "confirmed",
    "title": "Holiday check in",
    "updated_at": 1701974915,
    "when": {
      "end_time": 1701978300,
      "end_timezone": "America/Los_Angeles",
      "object": "timespan",
      "start_time": 1701977400,
      "start_timezone": "America/Los_Angeles"
    }
  }
}


```

```js [createEvents-Node.js SDK]

import Nylas from "nylas";

const nylas = new Nylas({
  apiKey: "<NYLAS_API_KEY>",
  apiUri: "<NYLAS_API_URI>",
});

const now = Math.floor(Date.now() / 1000); // Time in Unix timestamp format (in seconds)

async function createAnEvent() {
  try {
    const event = await nylas.events.create({
      identifier: "<NYLAS_GRANT_ID>",
      requestBody: {
        title: "Build With Nylas",
        when: {
          startTime: now,
          endTime: now + 3600,
        },
      },
      queryParams: {
        calendarId: "<CALENDAR_ID>",
      },
    });

    console.log("Event:", event);
  } catch (error) {
    console.error("Error creating event:", error);
  }
}

createAnEvent();


```

```python [createEvents-Python SDK]

from nylas import Client

nylas = Client(
    "<NYLAS_API_KEY>",
    "<NYLAS_API_URI>"
)

grant_id = "<NYLAS_GRANT_ID>"

events = nylas.events.create(
  grant_id,
  request_body={
    "title": 'Build With Nylas',
    "when": {
      "start_time": 1609372800,
      "end_time": 1609376400
    },
  },
  query_params={
    "calendar_id": "<CALENDAR_ID>"
  }
)

print(events)

```


```ruby [createEvents-Ruby SDK]

require 'nylas'
require 'date'

nylas = Nylas::Client.new(api_key: "<NYLAS_API_KEY>")

query_params = {
  calendar_id: "<CALENDAR_ID>"
}

today = Date.today
start_time = Time.local(today.year, today.month, today.day, 13, 0, 0).to_i
end_time = Time.local(today.year, today.month, today.day, 13, 30, 0).to_i

request_body = {
  when: {
    start_time: start_time,
    end_time: end_time
  },
  title: "Let's learn some Nylas Ruby SDK!",
  location: "Nylas' Headquarters",
  description: "Using the Nylas API with the Ruby SDK is easy.",
  participants: [{
    name: "Blag",
    email: "atejada@gmail.com",
    status: 'noreply'
  }]
}

event, _request_id = nylas.events.create(
  identifier: "<NYLAS_GRANT_ID>",
  query_params: query_params,
  request_body: request_body
)

puts event


```

```java [createEvents-Java SDK]

import com.nylas.NylasClient;
import com.nylas.models.*;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.*;

public class create_calendar_events {
  public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError {
    NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();

    // Get today's date
    LocalDate today = LocalDate.now();

    // Set time. Because we're using UTC we need to add the hours in difference from our own timezone.
    Instant sixPmUtc = today.atTime(13, 0).toInstant(ZoneOffset.UTC);

    // Set the date and time for the event. We add 30 minutes to the starting time.
    Instant sixPmUtcPlus = sixPmUtc.plus(30, ChronoUnit.MINUTES);

    // Get the Date and Time as a Unix timestamp
    long startTime = sixPmUtc.getEpochSecond();
    long endTime = sixPmUtcPlus.getEpochSecond();

    // Define title, location, and description of the event
    String title = "Let's learn some about the Nylas Java SDK!";
    String location = "Nylas Headquarters";
    String description = "Using the Nylas API with the Java SDK is easy.";

    // Create the timespan for the event
    CreateEventRequest.When.Timespan timespan = new CreateEventRequest.
        When.Timespan.
        Builder(Math.toIntExact(startTime), Math.toIntExact(endTime)).
        build();

    // Create the list of participants.
    List<CreateEventRequest.Participant> participants_list = new ArrayList<>();

    participants_list.add(new CreateEventRequest.
        Participant("johndoe@example.com", ParticipantStatus.NOREPLY,
        "John Doe", "", ""));

    // Build the event details.
    CreateEventRequest createEventRequest = new CreateEventRequest.Builder(timespan)
        .participants(participants_list)
        .title(title)
        .location(location)
        .description(description)
        .build();

    // Build the event parameters. In this case, the Calendar ID.
    CreateEventQueryParams createEventQueryParams = new CreateEventQueryParams.Builder("<CALENDAR_ID>").build();

    // Create the event itself
    Event event = nylas.events().create(
        "<NYLAS_GRANT_ID>",
        createEventRequest,
        createEventQueryParams).getData();
  }
}

```

```kt [createEvents-Kotlin SDK]

import com.nylas.NylasClient
import com.nylas.models.*

import java.time.LocalDateTime
import java.time.ZoneOffset

fun main(args: Array<String>) {
  val nylas: NylasClient = NylasClient(apiKey = "<NYLAS_API_KEY>")
  var startDate = LocalDateTime.now()

  // Set the time. Because we're using UTC, we need to add the difference in hours from our own timezone.
  startDate = startDate.withHour(13);
  startDate = startDate.withMinute(0);
  startDate = startDate.withSecond(0);
  val endDate = startDate.withMinute(30);

  // Convert the dates from Unix timestamp format to integer.
  val iStartDate: Int = startDate.toEpochSecond(ZoneOffset.UTC).toInt()
  val iEndDate: Int = endDate.toEpochSecond(ZoneOffset.UTC).toInt()

  // Create the timespan for the event.
  val eventWhenObj: CreateEventRequest.When = CreateEventRequest.When.
  Timespan(iStartDate, iEndDate);

  // Define the title, location, and description of the event.
  val title: String = "Let's learn about the Nylas Kotlin/Java SDK!"
  val location: String = "Blag's Den!"
  val description: String = "Using the Nylas API with the Kotlin/Java SDK is easy."

  // Create the list of participants.
  val participants: List<CreateEventRequest.Participant> = listOf(CreateEventRequest.
      Participant("<PARTICIPANT_EMAIL>", ParticipantStatus.NOREPLY, "<PARTICIPANT_NAME>"))

  // Create the event request. This adds date/time, title, location, description, and participants.
  val eventRequest: CreateEventRequest = CreateEventRequest(eventWhenObj, title, location, description, participants)

  // Set the event parameters.
  val eventQueryParams: CreateEventQueryParams = CreateEventQueryParams("<CALENDAR_ID>")

  val event: Response<Event> = nylas.events().create("<NYLAS_GRANT_ID>",
      eventRequest, eventQueryParams)
}

```


For iCloud accounts, replace `<CALENDAR_ID>` in these samples with an actual calendar ID from the [List Calendars](/docs/reference/api/calendar/get-all-calendars/) response. The `primary` shortcut that works for Google and Microsoft is not available on iCloud.

## Create iCloud events from the terminal

iCloud calendars run on CalDAV with a simpler feature set than Google or Microsoft, and a 16-character app-specific password connects the account. The [Nylas CLI](https://cli.nylas.com/docs/commands) creates events on them anyway: `nylas calendar events create` schedules an event from your terminal.


The Nylas CLI creates events from your terminal through the same Events API. After `nylas init` and `nylas auth login`, `calendar events create` schedules an event and invites attendees in one command. Start and end times use the `YYYY-MM-DD HH:MM` format:

```bash
nylas calendar events create \
  --title "Design review" \
  --start "2026-06-25 14:00" \
  --end "2026-06-25 15:00" \
  --participant alice@example.com \
  --participant bob@example.com \
  --location "Conference Room A"
```

Each `--participant` adds an attendee who receives a normal calendar invitation; repeat the flag for more. See the [`calendar events create`](https://cli.nylas.com/docs/commands/calendar-events-create) command reference for every flag, including `--description` for the event body and `--location` for the venue.


iCloud is one of the 6 providers `events create` supports, so the same command works whether the grant is iCloud, Gmail, or Exchange. Participants you add receive invitations, though CalDAV omits some cloud-only extras like automatic video links. Start and end times use the `YYYY-MM-DD HH:MM` format.

## Add participants and send invitations

When you include participants in your create event request and set `notify_participants=true` (the default), Nylas sends invitation emails to each participant. On iCloud, these invitations go out as ICS file attachments via email, which is how CalDAV handles event notifications natively.

This is simpler than the notification systems on Google and Microsoft. There are no push notifications, no in-app notification bells, and no rich invitation cards. Participants receive a standard email with an ICS attachment they can accept or decline.

> **Warn:** 
> **When `notify_participants=false`**, Nylas creates the event on the organizer's calendar only. Participants do not receive an invitation email and the event does not appear on their calendars.

A few things to keep in mind:

- CalDAV sends invitations as ICS files. Some email clients render these as calendar invites with accept/decline buttons, while others show them as plain attachments.
- There is no way to customize the invitation email body through CalDAV. The content is generated automatically based on the event details.
- Participant response status (`accepted`, `declined`, `tentative`) syncs back through CalDAV, but with the latency you would expect from a polling-based protocol.

## Things to know about iCloud

iCloud Calendar runs on CalDAV, which gives it a different feature profile than Google or Microsoft. Here's what matters when creating events.

### No `primary` calendar shortcut

Google and Microsoft both support `calendar_id=primary` as a shorthand for the user's default calendar. iCloud does not. You must call the [List Calendars endpoint](/docs/reference/api/calendar/get-all-calendars/) first and pick the correct calendar ID from the response.

The default calendar name varies by language and region. English accounts typically have a calendar called "Calendar" or "Home", but don't hardcode that. Always discover calendar IDs dynamically.

### No conferencing auto-create

Google can automatically generate Meet links when you create an event, and Microsoft can attach Teams links. iCloud has no equivalent. CalDAV does not support any conferencing integration.

If your users need a video call link on the event, you can include the URL in the `location` or `description` field manually. This works, but you will need to handle the conferencing provider integration yourself.

### No room resources

iCloud does not support the `resources` field. CalDAV has no concept of room or resource booking. If your app needs meeting room scheduling alongside event creation, iCloud cannot provide it. Events that include `resources` in the request body will have that field ignored.

### Simpler event model

CalDAV supports the core event fields and not much else. On iCloud:

- `title`, `when`, `participants`, `description`, `location`, and `recurrence` all work as expected
- `event_type` is not available (no focus time, out of office, or working location support)
- `color_id` is not exposed through CalDAV. Calendar and event colors are managed locally in the Apple Calendar app
- `capacity` is not supported

The core fields cover most use cases. If your app depends on extended event properties, test against iCloud specifically to confirm what comes back.

### All-day events

Use the `datespan` format for all-day events, the same as Google and Microsoft. Set the `when` object with a `start_date` and `end_date` in `YYYY-MM-DD` format. The end date is exclusive, so a single-day event on March 5 would use `start_date: "2026-03-05"` and `end_date: "2026-03-06"`.

### Recurring events

Standard RRULE support works through CalDAV using iCalendar (RFC 5545) recurrence rules. Nylas expands recurring events into individual instances, just like it does for other providers. You can create recurring events by including an `recurrence` array in the request body.

For details on managing recurring event series, see the [recurring events guide](/docs/v3/calendar/recurring-events/).

### App-specific passwords can break

If a user revokes their app-specific password through their Apple ID settings, all API calls for that grant will fail. There is no way to detect a revoked password proactively. Use [webhooks](/docs/v3/notifications/) to listen for `grant.expired` events so your app can prompt the user to re-authenticate.

Your onboarding flow should set clear expectations: if the user deletes their app password, their calendar integration stops working until they create a new one.

### Sync timing

CalDAV sync can be slower than Google's push notifications or Microsoft's change subscriptions. Events you create through the API may take a few minutes to appear in Apple Calendar apps on the user's devices. This latency is inherent to CalDAV and not something Nylas or your app can control.

Use [webhooks](/docs/v3/notifications/) rather than polling to detect changes. Nylas monitors for updates and sends notifications when events are created, updated, or deleted.

## What's next

- [Events API reference](/docs/reference/api/events/) for full endpoint documentation and all available parameters
- [Using the Events API](/docs/v3/calendar/using-the-events-api/) for updating, deleting, and managing events
- [List iCloud calendar events](/docs/cookbook/calendar/events/list-events-icloud/) for retrieving events from iCloud accounts
- [Recurring events](/docs/v3/calendar/recurring-events/) for expanding and managing recurring event series
- [Availability](/docs/v3/calendar/calendar-availability/) for checking free/busy status across calendars
- [Webhooks](/docs/v3/notifications/) for real-time notifications instead of polling
- [iCloud provider guide](/docs/provider-guides/icloud/) for full iCloud setup including authentication
- [App passwords guide](/docs/provider-guides/app-passwords/) for generating app-specific passwords for iCloud and other providers