Your team runs a standup every weekday at 9:00 a.m., and you don’t want to create 260 separate events for the year. A recurring event solves this with one request and one schedule rule. The hard part is the rule itself: it’s an RRULE string with cryptic tokens like FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR. This recipe shows you how to write that string and attach it to an event.
For a single non-repeating event, including Google Meet auto-creation and participant invites, see Create Google calendar events. This page focuses only on the recurrence piece.
Create a recurring event
Section titled “Create a recurring event”Send a POST to /v3/grants/{grant_id}/events?calendar_id={calendar_id} with a recurrence array and a when object. The recurrence array holds one RRULE string that defines the repeat pattern, and when sets the time and timezone of the first occurrence. One request creates all 52 weekly occurrences, and every later occurrence inherits that same time of day.
The request below creates a weekly standup that repeats every Monday. The start_timezone and end_timezone fields anchor the series to America/New_York, so the 9:00 a.m. slot stays correct through daylight saving changes. Without a timezone, the series falls back to UTC.
curl --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": "Weekly standup", "busy": true, "when": { "start_time": 1674604800, "end_time": 1674606600, "start_timezone": "America/New_York", "end_timezone": "America/New_York" }, "recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=MO"] }'const Nylas = require("nylas").default;
const nylas = new Nylas({ apiKey: "<NYLAS_API_KEY>" });
const event = await nylas.events.create({ identifier: "<NYLAS_GRANT_ID>", queryParams: { calendarId: "<CALENDAR_ID>" }, requestBody: { title: "Weekly standup", busy: true, when: { startTime: 1674604800, endTime: 1674606600, startTimezone: "America/New_York", endTimezone: "America/New_York", }, recurrence: ["RRULE:FREQ=WEEKLY;BYDAY=MO"], },});
console.log(event);from nylas import Client
nylas = Client(api_key="<NYLAS_API_KEY>")
event = nylas.events.create( identifier="<NYLAS_GRANT_ID>", query_params={"calendar_id": "<CALENDAR_ID>"}, request_body={ "title": "Weekly standup", "busy": True, "when": { "start_time": 1674604800, "end_time": 1674606600, "start_timezone": "America/New_York", "end_timezone": "America/New_York", }, "recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=MO"], },)
print(event)The response returns the created event with an id, a recurrence array that echoes your RRULE, and the when object you sent. Save that id. You’ll need it to update or cancel the series later. The same request body works for Google, Microsoft, iCloud, and Exchange accounts with no provider-specific changes.
Write the RRULE
Section titled “Write the RRULE”An RRULE is a single string built from semicolon-separated parts, defined by RFC 5545. At minimum you set FREQ (how often) and then narrow it with parts like INTERVAL, BYDAY, COUNT, or UNTIL. Nylas passes the string to the provider unchanged, so the same rule behaves consistently across all 4 supported calendar providers.
The 5 parts below cover most real schedules. Combine them in one string, separated by semicolons, with no spaces. For example, RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=MO repeats every other Monday.
| Part | What it controls | Example |
|---|---|---|
FREQ | Repeat unit: DAILY, WEEKLY, MONTHLY, YEARLY | FREQ=WEEKLY |
INTERVAL | Step between repeats (default 1) | INTERVAL=2 (every 2 weeks) |
BYDAY | Days of the week, or an ordinal day | BYDAY=MO,WE,FR or BYDAY=1MO |
COUNT | Total number of occurrences | COUNT=10 (stops after 10) |
UNTIL | End date in UTC (YYYYMMDDTHHMMSSZ) | UNTIL=20251231T000000Z |
Here’s how those parts map to common scenarios:
- Every Monday:
RRULE:FREQ=WEEKLY;BYDAY=MO - Every 2 weeks on Monday:
RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=MO - First Monday of each month:
RRULE:FREQ=MONTHLY;BYDAY=1MO - Weekdays for 10 occurrences:
RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;COUNT=10
To skip specific dates in a series, add an EXDATE string as a second array entry, like ["RRULE:FREQ=WEEKLY;BYDAY=MO", "EXDATE:20251110T000000Z"]. Nylas doesn’t support the EXRULE or RDATE parts. For the full token list and provider behavior, see the recurring events reference.
Update or cancel a series vs one instance
Section titled “Update or cancel a series vs one instance”Editing a recurring event changes either one occurrence or the whole series, depending on which id you target. Each occurrence has its own event id and a shared master_event_id that links it to the parent. Send a PUT to /v3/grants/{grant_id}/events/{event_id} against an occurrence id to change just that instance, or against the parent id to change every occurrence.
The request below reschedules a single Monday standup to a later time without touching the rest of the series. Because you target one occurrence id, the other 51 weekly events stay put. The response includes an original_start_time field marking the instance you moved.
curl --request PUT \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events/<EVENT_ID>?calendar_id=<CALENDAR_ID>' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json' \ --data '{ "when": { "start_time": 1674820800, "end_time": 1674822600, "start_timezone": "America/New_York", "end_timezone": "America/New_York" } }'To cancel one occurrence, send a DELETE to the same path with that occurrence’s id. To cancel the entire series, send a DELETE with the parent id. Each participant receives an event notification per change, so a single-occurrence edit on a busy series can still fan out widely. The recurring events reference covers editing part of a sequence (the “this and following” case), which needs an extra step.
Things to know about recurring events
Section titled “Things to know about recurring events”Recurrence is governed by RFC 5545, the same iCalendar standard every major provider implements, but providers diverge in small ways that surface through the API. The notes below cover the 4 behaviors that trip people up most when building against real Google and Microsoft accounts.
Timezone and DST handling
Section titled “Timezone and DST handling”RRULE defines a time of day, not an absolute instant, so daylight saving transitions matter. A 9:00 a.m. weekly event stays at 9:00 a.m. local time even when the clock shifts in spring and fall, as long as you set start_timezone and end_timezone. If you omit the timezone, the series runs in UTC and appears to drift by an hour for participants during the 2 annual DST changes. Always pass an IANA timezone like America/New_York.
Google vs Microsoft differences
Section titled “Google vs Microsoft differences”The 2 largest providers handle pattern edits differently. When you change a series, for example from weekly to daily, Google keeps existing overrides that still fit the new pattern, while Microsoft removes them. Microsoft also can’t create an event that recurs monthly on multiple weekdays, such as FREQ=MONTHLY;BYDAY=1TH,3TH, because its recurrence model puts the index on the month, not the day. Build for the stricter of the two if you support both.
Exceptions and overrides
Section titled “Exceptions and overrides”An override is a single occurrence that differs from the parent, for example one standup you moved to the afternoon. The provider stores it separately and links it through master_event_id. Deleted occurrences become EXDATE entries, except on Google, which returns the deleted occurrence with status set to cancelled instead of adding an EXDATE. Editing an EXDATE value after creation returns a 200 but doesn’t remove the event, so use a DELETE request to drop an occurrence.
Expanding instances when you list
Section titled “Expanding instances when you list”Recurring events expand into individual occurrences only when you read a date range. A list events request with start and end query parameters returns each occurrence in that window as its own object sharing one master_event_id. Without a date range, you get the parent event and its RRULE, not the expanded list. Set a bounded window so an open-ended FREQ=DAILY series doesn’t try to return thousands of instances.
What’s next
Section titled “What’s next”- Create Google calendar events for single events, Google Meet auto-creation, and participant invites
- Recurring events and RRULE reference for the full token list, EXDATE handling, and per-provider behavior
- Checking calendar availability to find open slots before you schedule a series
- Events API reference for every event field and query parameter