# How to create Microsoft calendar events

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

Creating events on Microsoft 365 and Outlook calendars through Microsoft Graph means registering an Azure AD app, managing MSAL tokens, passing Windows timezone IDs in request bodies, and configuring admin consent for write access. If you want to support multiple calendar providers, you also need to build and maintain separate integrations for each one.

Nylas handles all of that behind a single REST API. You send the same create event request whether the account is Microsoft, Google, or iCloud. Nylas takes care of authentication, timezone conversion, and provider-specific formatting. This guide walks through creating events on Microsoft accounts, including participants, conferencing, recurring events, and the write-specific details you need to know.

## Why use Nylas instead of Microsoft Graph directly?

Writing to Microsoft calendars through Graph is more involved than reading. You need `Calendars.ReadWrite` permissions, which are more likely to require admin consent in enterprise tenants. Request bodies need Windows timezone IDs. Attaching a Teams meeting requires a specific conferencing object structure. Notification behavior differs depending on how you configure the request, and error messages from Graph can be opaque when something goes wrong.

Nylas simplifies all of this. You pass IANA timezones (like `America/New_York`), and Nylas converts them for Microsoft. Conferencing auto-creation works through a single `autocreate` object. Participant notifications are controlled with one query parameter. Your create event code works identically across providers.

That said, if you only target Microsoft accounts and already have a working Graph integration, there's no need to switch.

## Before you begin

You'll need:

- A [Nylas application](/docs/v3/getting-started/) with a valid API key
- A [grant](/docs/v3/auth/) for a Microsoft 365 or Outlook account
- The `Calendars.ReadWrite` scope enabled in your Azure AD app registration (note: creating events requires the **write** scope, not just `Calendars.Read`)


> **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.


### Microsoft admin consent

Microsoft organizations often require admin approval before third-party apps can access calendar data. Write scopes like `Calendars.ReadWrite` are more likely to trigger this requirement than read-only scopes. If your users see a "Need admin approval" screen during auth, their organization restricts user consent.

You have two options:

- **Ask the tenant admin** to grant consent for your app via the Azure portal
- **Configure your Azure app** to request only permissions that don't need admin consent

Nylas has a detailed walkthrough: [Configuring Microsoft admin approval](/docs/provider-guides/microsoft/admin-approval/). If you're targeting enterprise customers, you'll almost certainly need to deal with this.

You also need to be a [verified publisher](/docs/provider-guides/microsoft/verification-guide/). Microsoft requires publisher verification since November 2020, and without it users see an error during auth.

## Create an event

> **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 you put in the `participants` sub-object. Make sure you actually want to send this invite to those addresses before running this command!

Make a [Create Event request](/docs/reference/api/events/create-event/) with the grant ID and a `calendar_id` query parameter. You can use `primary` as the `calendar_id` to target the account's default calendar.

```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)
}

```


The `calendar_id` query parameter is required. For Microsoft accounts, calendar IDs are long base64-encoded strings, but `primary` works as a shortcut to target the default calendar. Nylas returns the created event with its `id`, which you can use for subsequent updates or deletions.

## Create Microsoft events from the terminal

Microsoft 365 supports Teams conferencing and room resources on new events, behind write scopes and admin consent on enterprise tenants. The [Nylas CLI](https://cli.nylas.com/docs/commands) handles the formatting: `nylas calendar events create` books an event and sends invitations 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.


Attendees you add with `--participant` get a standard Outlook invitation, and the API converts the times into the account's timezone. The example reserves a 60 minute slot; change `--start` and `--end` for any window. Attach a Teams link through the Events API conferencing field when you need one.

## Add participants and send invitations

The `notify_participants` query parameter controls whether Microsoft sends email invitations to everyone in the `participants` array. It defaults to `true`, so participants receive calendar invitations automatically unless you explicitly disable it.

When `notify_participants` is set to `false`, the event is created only on the organizer's calendar. Participants don't receive an email, an ICS file, or any notification. The event won't appear on their calendars at all.

Here's an example with notifications explicitly enabled:

```bash
curl --request POST \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events?calendar_id=primary&notify_participants=true' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "title": "Project kickoff",
    "when": {
      "start_time": 1674604800,
      "end_time": 1674608400,
      "start_timezone": "America/New_York",
      "end_timezone": "America/New_York"
    },
    "participants": [
      {
        "name": "Jordan Lee",
        "email": "jordan@example.com"
      }
    ]
  }'
```

> **Warn:** 
> **Keep in mind**: When `notify_participants=false`, your request doesn't create an event for the participant. Participants don't receive a message or an ICS file.

## Things to know about Microsoft

A few provider-specific details that matter when you're creating events on Microsoft calendar accounts.

### Write scopes require admin consent more often

The `Calendars.ReadWrite` scope is more likely to need admin approval than `Calendars.Read`, especially in enterprise tenants with strict consent policies. If your app previously worked with read-only access but fails on event creation, this is probably why. Check that the grant has the write scope and that the tenant admin has approved it.

### Teams conferencing

You can automatically create a Microsoft Teams meeting when creating an event by using the `conferencing` object with `autocreate`. Nylas provisions the Teams link and attaches the join URL and dial-in details to the event. You can also manually add a Teams link by passing a `conferencing` object with `provider` set to `"Microsoft Teams"` and the meeting URL in `details`.

For the full setup, including configuring connectors for auto-creation, see [Adding conferencing to events](/docs/v3/calendar/add-conferencing/). You need an active Microsoft 365 subscription for Teams conferencing to work.

### Timezones in request bodies

Nylas accepts IANA timezone identifiers in `start_timezone` and `end_timezone` (like `America/New_York` or `Europe/London`). You don't need to convert to Windows timezone IDs the way you would with Microsoft Graph directly. Nylas handles the conversion before sending the request to Microsoft.

If you omit the timezone fields, Nylas uses the account's default timezone.

### All-day events

To create an all-day event, use a `datespan` type with `start_date` and `end_date` as date strings (formatted `YYYY-MM-DD`). The end date is exclusive, meaning a single-day event on December 1st should have `start_date: "2024-12-01"` and `end_date: "2024-12-02"`. This matches how Microsoft Graph represents all-day events internally and is consistent across providers.

```json
"when": {
  "start_date": "2024-12-01",
  "end_date": "2024-12-02"
}
```

### Room resources

Microsoft supports booking conference rooms and other resources when creating events. Pass the room's email address in the `resources` array:

```json
"resources": [{
  "name": "Board Room 3A",
  "email": "boardroom-3a@example.com"
}]
```

The room must be accessible to the organizer's account. If the room has an approval workflow or is restricted to certain groups, the booking may be declined. Check your organization's room mailbox settings if bookings aren't going through.

### Recurring events

You can create recurring events by including a `recurrence` array with RRULE strings. Microsoft has a few limitations worth knowing:

- **Overrides are removed on recurrence change.** If you modify a recurring series pattern (for example, changing from weekly to daily), Microsoft removes all existing overrides. Google keeps them if they still fit the pattern.
- **No multi-day monthly BYDAY.** You can't create a monthly recurring event on multiple days of the week (like the first and third Thursday). Microsoft's recurrence model doesn't support different indices within a single rule.
- **EXDATE recovery isn't possible.** Once you remove an occurrence from a recurring series, you can't undo it through Nylas. You'd need to create a separate standalone event to fill the gap.

For the full breakdown of recurring event behavior and provider differences, see [Recurring events](/docs/v3/calendar/recurring-events/).

### Rate limits

Write operations count toward Microsoft's per-mailbox rate limits, and create requests are heavier than reads. If your app triggers a `429` response, Nylas handles the retry automatically with appropriate backoff.

If you're creating events in bulk (for example, migrating a calendar), space out requests to avoid hitting limits. For real-time awareness of event changes after creation, use [webhooks](/docs/v3/notifications/) instead of polling.

## 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 Microsoft calendar events](/docs/cookbook/calendar/events/list-events-microsoft/) to read events from Microsoft accounts
- [Add conferencing to events](/docs/v3/calendar/add-conferencing/) to attach Teams, Zoom, or other meeting links
- [Recurring events](/docs/v3/calendar/recurring-events/) for series creation, overrides, and provider-specific behavior
- [Availability](/docs/v3/calendar/calendar-availability/) to check free/busy across multiple calendars
- [Webhooks](/docs/v3/notifications/) for real-time notifications when events change
- [Microsoft admin approval](/docs/provider-guides/microsoft/admin-approval/) to configure consent for enterprise organizations
- [Microsoft publisher verification](/docs/provider-guides/microsoft/verification-guide/), required for production apps