<?xml version="1.0" encoding="utf-8"?>
<rfc version="3" ipr="trust200902" docName="draft-ietf-jmap-calendars-26" submissionType="IETF" category="std" xml:lang="en" xmlns:xi="http://www.w3.org/2001/XInclude" indexInclude="true" consensus="true">

<front>
<title abbrev="JMAP Calendars">JSON Meta Application Protocol (JMAP) for Calendars</title><seriesInfo value="draft-ietf-jmap-calendars-26" stream="IETF" status="standard" name="Internet-Draft"></seriesInfo>
<author role="editor" initials="N.M." surname="Jenkins" fullname="Neil Jenkins">
    <organization>Fastmail</organization>
    <address>
        <postal>
            <street>PO Box 234, Collins St West</street>
            <city>Melbourne</city>
            <code>VIC 8007</code>
            <country>Australia</country>
        </postal>
        <email>neilj@fastmailteam.com</email>
        <uri>https://www.fastmail.com</uri>
    </address>
</author>
<author role="editor" initials="M." surname="Douglass" fullname="Michael Douglass">
  <organization>Spherical Cow Group</organization>
  <address>
    <postal>
      <street>226 3rd Street</street>
      <city>Troy</city>
      <code>NY 12180</code>
      <country>United States of America</country>
    </postal>
    <email>mdouglass@sphericalcowgroup.com</email>
    <uri>http://sphericalcowgroup.com</uri>
  </address>
</author>
<date year="2025" month="November" day="5"></date>
<area>Applications</area>
<workgroup>JMAP</workgroup>
<keyword>JMAP</keyword>
<keyword>JSON</keyword>
<keyword>calendars</keyword>

<abstract>
<t>This document specifies a data model for synchronizing calendar data with a server using JMAP. Clients can use this to efficiently read, write, and share calendars and events, receive push notifications for changes or event reminders, and keep track of changes made by others in a multi-user environment.</t>
</abstract>

</front>

<middle>

<section anchor="introduction"><name>Introduction</name>
<t>JMAP (<xref target="RFC8620"></xref> — JSON Meta Application Protocol) is a generic protocol for synchronizing data, such as mail, calendars or contacts, between a client and a server. It is optimized for mobile and web environments, and aims to provide a consistent interface to different data types.</t>
<t>This specification defines a data model for synchronizing calendar data between a client and a server using JMAP. The data model is designed to allow a server to provide consistent access to the same data via CalDAV <xref target="RFC4791"></xref> as well as JMAP, however the functionality offered over the two protocols may differ. Unlike CalDAV, this specification does not define access to tasks or journal entries (VTODO or VJOURNAL iCalendar components in CalDAV).</t>

<section anchor="notational-conventions"><name>Notational Conventions</name>
<t>The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 <xref target="RFC2119"></xref> <xref target="RFC8174"></xref> when, and only when, they appear in all capitals, as shown here.</t>
<t>Type signatures, examples, and property descriptions in this document follow the conventions established in <xref target="RFC8620" section="1.1" sectionFormat="of" />.</t>
</section>

<section anchor="the-localdate-data-type"><name>Data Types</name>
<t>The Id data type defined in <xref target="RFC8620" section="1.2" /> is used in this document. So too are the UnsignedInt, UTCDateTime, LocalDateTime, Duration, and TimeZoneId data types defined in Sections <xref target="I-D.ietf-calext-jscalendarbis" section="1.4.3" sectionFormat="bare" />, <xref target="I-D.ietf-calext-jscalendarbis" section="1.4.4" sectionFormat="bare" />, <xref target="I-D.ietf-calext-jscalendarbis" section="1.4.5" sectionFormat="bare" />, <xref target="I-D.ietf-calext-jscalendarbis" section="1.4.6" sectionFormat="bare" />, and <xref target="I-D.ietf-calext-jscalendarbis" section="1.4.8" sectionFormat="bare" /> of <xref target="I-D.ietf-calext-jscalendarbis" /> respectively.</t>
</section>

<section anchor="terminology"><name>Terminology</name>
<t>The same terminology is used in this document as in the core JMAP specification, see <xref target="RFC8620" section="1.6"></xref>.</t>
<t>The terms ParticipantIdentity, Calendar, CalendarEvent, and CalendarEventNotification (with these specific capitalizations) are used to refer to the data types defined in this document and instances of those data types.</t>
</section>

<section anchor="data-model-overview"><name>Data Model Overview</name>
<t>An Account (see <xref target="RFC8620" section="1.6.2"></xref>) with support for the calendar data model contains zero or more Calendar objects, which is a named collection of CalendarEvents. Calendars can also provide defaults, such as alerts and a color to apply to events in the calendar. Clients commonly let users toggle visibility of events belonging to a particular calendar on/off. Servers may allow an event to belong to multiple Calendars within an account.</t>
<t>A CalendarEvent is a representation of an event or recurring series of events in JSCalendar Event <xref target="I-D.ietf-calext-jscalendarbis" /> format. Simple clients may ask the server to expand recurrences for them within a specific time period, and optionally convert times into UTC so they do not have to handle time zone conversion. More full-featured clients will want to access the full event information and handle recurrence expansion and time zone conversion locally.</t>
<t>CalendarEventNotification objects keep track of the history of changes made to a calendar by other users, allowing calendar clients to notify the user of changes to their schedule.</t>
<t>The ParticipantIdentity data type represents the identities of the current user within an Account, which determines which events the user is a participant of and possibly their permissions related to that event.</t>
<t>In servers with support for JMAP Sharing <xref target="RFC9670" />, data may be shared with other users. Sharing permissions are managed per calendar. For example, an individual may have separate calendars for personal and work activities, with both contributing to their free-busy availability, but only the work calendar shared in its entirety with colleagues. Principals (see <xref target="RFC9670" section="2" />) may also represent schedulable entities, such as a meeting room.</t>
<t>Users can normally subscribe to any calendar to which they have access. This indicates the user wants this calendar to appear in their regular list of calendars. The separate "isVisible" property stores whether the user would currently like to view the events in a subscribed calendar.</t>

<section anchor="uids-and-calendarevent-ids"><name>UIDs and CalendarEvent Ids</name>
<t>Each CalendarEvent has a "uid" property (<xref target="I-D.ietf-calext-jscalendarbis" section="4.1.1" />), which is a globally unique identifier that identifies the same event in different Accounts, or different instances of the same recurring event within an Account.</t>
<t>An Account MUST NOT contain more than one CalendarEvent with the same uid unless all of the CalendarEvent objects have distinct, non-null values for their "recurrenceId" property. (This situation occurs if the Principal is added to one or more specific instances of a recurring event without being invited to the whole series.)</t>
<t>Each CalendarEvent also has an id, which is scoped to the JMAP Account and used for referencing it in JMAP methods. There is no necessary link between the uid and the CalendarEvent's id. CalendarEvents with the same uid in different Accounts may have different ids.</t>
</section>
</section>

<section anchor="addition-to-the-capabilities-object"><name>Addition to the Capabilities Object</name>
<t>The capabilities object is returned as part of the JMAP Session object; see <xref target="RFC8620" section="2" />. This document defines three additional capability URIs.</t>

<section anchor="urn-ietf-params-jmap-calendars"><name>urn:ietf:params:jmap:calendars</name>
<t>This represents support for the Calendar, CalendarEvent, CalendarEventNotification, and ParticipantIdentity data types and associated API methods, except for "CalendarEvent/parse". The value of this property in the JMAP Session "capabilities" property is an empty object.</t>
<t>The value of this property in an account's "accountCapabilities" property is an object that MUST contain the following information on server capabilities and permissions for that account:</t>

<dl spacing="normal" newline="true">
<dt><strong>maxCalendarsPerEvent</strong>: <tt>UnsignedInt|null</tt></dt>
<dd>The maximum number of Calendars (see <xref target="calendars" />) that can be assigned to a single CalendarEvent object (see <xref target="calendar-events" />). This MUST be an integer &gt;= 1, or null for no limit (or rather, the limit is always the number of Calendars in the account).</dd>
<dt><strong>minDateTime</strong>: <tt>UTCDateTime</tt></dt>
<dd>The earliest date-time value the server is willing to accept for any date stored in a CalendarEvent.</dd>
<dt><strong>maxDateTime</strong>: <tt>UTCDateTime</tt></dt>
<dd>The latest date-time value the server is willing to accept for any date stored in a CalendarEvent.</dd>
<dt><strong>maxExpandedQueryDuration</strong>: <tt>Duration</tt></dt>
<dd>The maximum duration the user may query over when asking the server to expand recurrences.</dd>
<dt><strong>maxParticipantsPerEvent</strong>: <tt>UnsignedInt|null</tt></dt>
<dd>The maximum number of participants a single event may have, or null for no limit.</dd>
<dt><strong>mayCreateCalendar</strong>: <tt>Boolean</tt></dt>
<dd>If true, the user may create a calendar in this account.</dd>
</dl>
</section>

<section anchor="urn-ietf-params-jmap-principals-availability"><name>urn:ietf:params:jmap:principals:availability</name>
<t>Represents support for the "Principal/getAvailability" method. Any account with this capability MUST also have the <tt>urn:ietf:params:jmap:principals</tt> capability (see <xref target="RFC9670" section="1.5.1" />).</t>
<t>The value of this property in the JMAP Session "capabilities" property is an empty object.</t>
<t>The value of this property in an account's "accountCapabilities" property is an object that MUST contain the following information on server capabilities and permissions for that account:</t>

<dl spacing="normal" newline="true">
<dt><strong>maxAvailabilityDuration</strong>: <tt>Duration</tt></dt>
<dd>The maximum duration over which the server is prepared to calculate availability in a single call (see <xref target="principal-getavailability" />).</dd>
</dl>
</section>

<section anchor="urn-ietf-params-jmap-calendars-parse"><name>urn:ietf:params:jmap:calendars:parse</name>
<t>This represents support for the CalendarEvent/parse method (see <xref target="calendarevent-parse" />). The value of this property is an empty object in both the JMAP session "capabilities" property and an account's "accountCapabilities" property.</t>
</section>
</section>
</section>

<section anchor="principals-and-sharing"><name>Principals and Sharing</name>
<t>For systems that also support JMAP Sharing <xref target="RFC9670" />, the <tt>urn:ietf:params:jmap:calendars</tt> capability is used to indicate that this Principal may be used with calendaring. A new method is defined to allow users to query availability when scheduling events.</t>

<section anchor="principal-capability-urn-ietf-params-jmap-calendars"><name>Principal Capability urn:ietf:params:jmap:calendars</name>
<t>A "urn:ietf:params:jmap:calendars" property is added to the Principal "capabilities" object, the value of which is an object with the following properties:</t>

<dl spacing="normal" newline="true">
<dt><strong>accountId</strong>: <tt>Id|null</tt></dt>
<dd>Id of Account with the <tt>urn:ietf:params:jmap:calendars</tt> capability that contains the calendar data for this Principal, or null if either (a) there is none (e.g. the Principal is a group just used for permissions management), or (b) the user does not have access to any data in the account (with the exception of free/busy, which is governed by the "mayGetAvailability" property). The corresponding Account object can be found in the Principal's "accounts" property, as per <xref target="RFC9670" section="2" />.</dd>
<dt><strong>mayGetAvailability</strong>: <tt>Boolean</tt></dt>
<dd>If true, the user may call the "Principal/getAvailability" method with this Principal.</dd>
<dt><strong>mayShareWith</strong>: <tt>Boolean</tt></dt>
<dd>If true, the user may add this Principal as a calendar share target (by adding them to the "shareWith" property of a calendar, see <xref target="calendars" />).</dd>
<dt><strong>calendarAddress</strong>: <tt>String</tt></dt>
<dd>If this Principal may be added as a participant to an event, this is the calendarAddress to use to receive iTIP scheduling messages.</dd>
</dl>
</section>

<section anchor="principal-getavailability"><name>Principal/getAvailability</name>
<t>This method calculates the availability of the Principal for scheduling within a requested time period. It takes the following arguments:</t>

<dl spacing="normal" newline="true">
<dt><strong>accountId</strong>: <tt>Id</tt></dt>
<dd>The id of the account to use.</dd>
<dt><strong>id</strong>: <tt>Id</tt></dt>
<dd>The id of the Principal to calculate availability for.</dd>
<dt><strong>utcStart</strong>: <tt>UTCDateTime</tt></dt>
<dd>The start time (inclusive) of the period for which to return availability.</dd>
<dt><strong>utcEnd</strong>: <tt>UTCDateTime</tt></dt>
<dd>The end time (exclusive) of the period for which to return availability.</dd>
<dt><strong>showDetails</strong>: <tt>Boolean</tt></dt>
<dd>If true, event details will be returned if the user has permission to view them.</dd>
<dt><strong>eventProperties</strong>: <tt>String[]|null</tt></dt>
<dd>A list of properties to include in any CalendarEvent object returned. If null, all properties of the event will be returned. Otherwise, only properties with names in the given list will be returned.</dd>
</dl>

<t>The server will first find all relevant events, expanding any recurring events. Relevant events are ones where all of the following is true:</t>

<ul spacing="compact">
<li>The Principal is subscribed to the calendar.</li>
<li>The "includeInAvailability" property of the calendar for the Principal is "all" or "attending".</li>
<li>The user has the "mayReadFreeBusy" permission for the calendar.</li>
<li>The event finishes after the "utcStart" argument and starts before the "utcEnd" argument.</li>
<li>The event's "privacy" property is not "secret".</li>
<li>The "freeBusyStatus" property of the event is "busy" (or omitted, as this is the default).</li>
<li>The "status" property of the event is not "cancelled".</li>
<li>If the "includeInAvailability" property of the calendar is "attending", then the Principal is a participant of the event, and has a "participationStatus" of "accepted" or "tentative".</li>
</ul>

<t>If an event is in more than one calendar, it is relevant if all of the above are true for any one calendar that it is in.</t>

<t>The server then generates a BusyPeriod object for each of these events. A <strong>BusyPeriod</strong> object has the following properties:</t>

<dl spacing="normal" newline="true">
<dt><strong>utcStart</strong>: <tt>UTCDateTime</tt></dt>
<dd>The start time (inclusive) of the period this represents.</dd>
<dt><strong>utcEnd</strong>: <tt>UTCDateTime</tt></dt>
<dd>The end time (exclusive) of the period this represents.</dd>
<dt><strong>busyStatus</strong>: <tt>String</tt> (optional, default "unavailable")</dt>
<dd><t>This MUST be one of:</t>
<ul spacing="compact">
<li><tt>"confirmed"</tt> — The event status is "confirmed" and the Principal's "participationStatus" is "attending".</li>
<li><tt>"tentative"</tt> — The event status is "tentative" or the Principal's "participationStatus" is "tentative".</li>
<li><tt>"unavailable"</tt> — The Principal is not available for scheduling at this time for any other reason.</li>
</ul>
</dd>
<dt><strong>event</strong>: <tt>CalendarEvent|null</tt></dt>
<dd><t>The CalendarEvent representation of the event (see <xref target="calendar-events" />), or null if any of the following are true:</t>
<ul spacing="compact">
<li>The "showDetails" argument is false.</li>
<li>The "privacy" property of the event is "private".</li>
<li>The user does not have the "mayReadItems" permission for any of the calendars the event is in.</li>
</ul>
<t>If an "eventProperties" argument was given, any properties in the JSCalendar Event that are not in the eventProperties list are removed from the returned representation.</t></dd>
<dt><strong>accountId</strong>: <tt>Id|null</tt></dt>
<dd>The account id in which this event can be found, or null if the "event" property is null. (The id of the event may be requested in the eventProperties.)</dd>
</dl>

<t>The server MAY also generate BusyPeriod objects based on other information it has about the Principal's availability, such as office hours; the "event" and "accountId" properties will always be null for these.</t>
<t>Finally, the server MUST merge and split BusyPeriod objects where the "event" property is null, such that none of them overlap and either there is a gap in time between any two objects (the utcEnd of one does not equal the utcStart of another) or those objects have a different "busyStatus" property. If there are overlapping BusyPeriod time ranges with different "busyStatus" properties the server MUST choose the value in the following order: confirmed &gt; unavailable &gt; tentative.</t>
<t>The response has the following argument:</t>

<dl spacing="normal" newline="true">
<dt><strong>list</strong>: <tt>BusyPeriod[]</tt></dt>
<dd>The list of BusyPeriod objects calculated as described above.</dd>
</dl>

<t>The following additional errors may be returned instead of the
"Principal/getAvailability" response:</t>
<dl spacing="normal" newline="true">
<dt><strong>notFound</strong></dt>
<dd>No Principal with this id exists, or the user does not have permission to see that this Principal exists.</dd>
<dt><strong>forbidden</strong></dt>
<dd>The user does not have permission to query this Principal's availability.</dd>
<dt><strong>tooLarge</strong></dt>
<dd>The duration between utcStart and utcEnd is longer than the server is willing to calculate availability for.</dd>
<dt><strong>rateLimit</strong></dt>
<dd>Too many availability requests have been made recently and the user is being rate limited. It may work to try again later.</dd>
</dl>
</section>

<section anchor="calendaraddress-filter"><name>Principal/query extension</name>
<t>The following extra optional property is added to the <strong>FilterCondition</strong> object for the <strong>Principal/query</strong> method when the <tt>urn:ietf:params:jmap:principals:availability</tt> capability is used:</t>
<dl spacing="normal" newline="true">
<dt><strong>calendarAddress</strong>: <tt>String</tt></dt>
<dd>The given string is a calendarAddress belonging to the Principal.</dd>
</dl>
</section>

</section>

<section anchor="participant-identities"><name>Participant Identities</name>
<t>A ParticipantIdentity stores information about a URI that represents the user within that account in an event's participants.</t>

<t>A <strong>ParticipantIdentity</strong> object has the following properties:</t>

<dl spacing="normal" newline="true">
<dt><strong>id</strong>: <tt>Id</tt> (immutable; server-set)</dt>
<dd>The id of the ParticipantIdentity.</dd>
<dt><strong>name</strong>: <tt>String</tt> (default: "")</dt>
<dd>The display name of the participant to use when adding this participant to an event, e.g. "Joe Bloggs".</dd>
<dt><strong>calendarAddress</strong>: <tt>String</tt></dt>
<dd>The URI that represents this participant for iTIP scheduling <xref target="RFC5546" />.</dd>
<dt><strong>isDefault</strong>: <tt>Boolean</tt> (server-set)</dt>
<dd><t>This SHOULD be true for exactly one participant identity in any account, and
MUST NOT be true for more than one participant identity within an account.
The default identity should be used by clients whenever they need to choose
an identity for the user within this account, and they do not have any other
information on which to make a choice. For example, if creating a scheduled
event in this account, the default identity may be automatically added as
an owner. (But the client may ignore this if, for example, it has its own
feature to allow users to choose which identity to use based on the
invitees.)</t>
<t>If no participant identities have "isDefault" set, users with multiple clients may experience different default choices in each client, which can be confusing or lead to the wrong identity being used by accident.</t></dd>
</dl>

<t>A participant in an event corresponds to a ParticipantIdentity if the "calendarAddress" property of the participant is equivalent to the "calendarAddress" property of the identity after syntax-based normalisation, as per <xref target="RFC3986" section="6.2.2" />.</t>

<t>The following JMAP methods are supported.</t>

<section anchor="participantidentity-get"><name>ParticipantIdentity/get</name>
<t>This is a standard "/get" method as described in <xref target="RFC8620" section="5.1" />. The "ids" argument may be null to fetch all at once.</t>
</section>

<section anchor="participantidentity-changes"><name>ParticipantIdentity/changes</name>
<t>This is a standard "/changes" method as described in <xref target="RFC8620" section="5.2" />.</t>
</section>

<section anchor="participantidentity-set"><name>ParticipantIdentity/set</name>
<t>This is a standard "/set" method as described in <xref target="RFC8620" section="5.3" />, but with the following additional request argument:</t>

<dl spacing="normal" newline="true">
<dt><strong>onSuccessSetIsDefault</strong>: <tt>Id|null</tt></dt>
<dd>If an id is given, and all creates, updates and destroys (if any) succeed
without error, the server will try to set this identity as the default. (For
references to ParticipantIdentity creations, this is equivalent to a
creation-reference, so the id will be the creation id prefixed with a "#".)</dd>
</dl>

<t>If the id is not found, or the change is not permitted by the server for
  policy reasons, it MUST be ignored and the currently default
  ParticipantIdentity (if any) will remain as such. No error is returned to
  the client in this case.</t>
<t>As per <xref target="RFC8620" section="5.3" />, if the default is successfully changed, any
  changed objects MUST be reported in either the "created" or "updated"
  argument in the response as  appropriate, with the server-set value included.</t>
<t>The server MAY restrict the URI values the user may claim, for example only allowing <tt>mailto:</tt> URIs with email addresses that belong to the user. A standard <tt>forbidden</tt> error is returned to reject non-permissible changes.</t>
</section>
</section>

<section anchor="calendars"><name>Calendars</name>
<t>A Calendar is a named collection of events. All events are associated with at least one calendar.</t>
<t>A <strong>Calendar</strong> object has the following properties:</t>

<dl spacing="normal" newline="true">
<dt><strong>id</strong>: <tt>Id</tt> (immutable; server-set)</dt>
<dd>The id of the calendar.</dd>
<dt><strong>name</strong>: <tt>String</tt></dt>
<dd>The user-visible name of the calendar. This MUST NOT be the empty string and MUST NOT be greater than 255 octets in size when encoded as UTF-8.</dd>
<dt><strong>description</strong>: <tt>String|null</tt> (default: null)</dt>
<dd>An optional longer-form description of the calendar, to provide context in shared environments where users need more than just the name.</dd>
<dt><strong>color</strong>: <tt>String|null</tt> (default: null)</dt>
<dd><t>A color to be used when displaying events associated with the calendar.</t>
<t>If not null, the value MUST be a case-insensitive color name taken from the set of names defined in Section 4.3 of CSS Color Module Level 3 <xref target="COLORS" />, or an RGB value in hexadecimal notation, as defined in Section 4.2.1 of CSS Color Module Level 3.</t>
<t>The color SHOULD have sufficient contrast when used as text on a white background, unless the user explicitly chooses otherwise. The Web Content Accessibility Guidelines <xref target="WCAG" /> recommend at least a 4.5:1 contrast ratio for readability.</t></dd>
<dt><strong>sortOrder</strong>: <tt>UnsignedInt</tt> (default: 0)</dt>
<dd><t>Defines the sort order of calendars when presented in the client's UI, so it
is consistent between devices. The number MUST be an integer in the range
0 &lt;= sortOrder &lt; 2<sup>31.</sup></t>
<t>A calendar with a lower order is to be displayed before a calendar with
a higher order in any list of calendars in the client's UI. Calendars with equal order should be sorted in alphabetical order by name. The sorting should take into account locale-specific character order convention.</t></dd>
<dt><strong>isSubscribed</strong>: <tt>Boolean</tt></dt>
<dd><t>True if the user has indicated they wish to see this Calendar in their client. This SHOULD default to false for Calendars in shared accounts the user has access to and true for any new Calendars created by the user themself.</t>
<t>If false, the calendar SHOULD only be displayed when the user explicitly
requests it or to offer it for the user to subscribe to. For example, a company may have a large number of shared calendars which all employees have permission to access, but the user would only subscribe to the ones they care about and want to be able to have normally accessible.</t></dd>
<dt><strong>isVisible</strong>: <tt>Boolean</tt> (default: true)</dt>
<dd>If true, the calendar's events should be displayed to the user. If false, the user has indicated they wish to temporarily hide this calendar's events. Clients MUST ignore this property if isSubscribed is false. If an event is in multiple calendars, it should be displayed if isVisible is true for any of those calendars.</dd>
<dt><strong>isDefault</strong>: <tt>Boolean</tt> (server-set)</dt>
<dd>This SHOULD be true for exactly one calendar in any account, and MUST NOT be
true for more than one calendar within an account. The default calendar
should be used by clients whenever they need to choose a calendar for the
user within this account, and they do not have any other information on
which to make a choice. For example, if the user creates a new event, the
client may automatically set the event as belonging to the default calendar
from the user's primary account.</dd>
<dt><strong>includeInAvailability</strong>: <tt>String</tt></dt>
<dd><t>This determines which events in this calendar will be used as part of availability calculation. The value MUST be one of:</t>
<ul spacing="compact">
<li><tt>"all"</tt> — all events are considered.</li>
<li><tt>"attending"</tt> — events the user is a confirmed or tentative participant of are considered.</li>
<li><tt>"none"</tt> — all events are ignored (but may be considered if also in another calendar).</li>
</ul>
<t>This should default to "all" for the calendars in the user's own account,
and "none" for calendars shared with the user.</t></dd>
<dt><strong>defaultAlertsWithTime</strong>: <tt>Id[Alert]|null</tt></dt>
<dd><t>A map of alert ids to Alert objects (see <xref target="I-D.ietf-calext-jscalendarbis" section="4.5.1" />) to apply for events where "showWithoutTime" is false and "useDefaultAlerts" is true. Ids MUST be unique across all default alerts in the account, including those in other calendars; a UUID <xref target="RFC9562" /> is recommended.  The "trigger" MUST NOT be an AbsoluteTrigger, as this would fire for every event at the same time and so does not make sense for a default alert.</t>
<t>If omitted on creation, the default is server dependent. For example, servers may choose to always default to null, or may copy the alerts from the default calendar.</t></dd>
<dt><strong>defaultAlertsWithoutTime</strong>: <tt>Id[Alert]|null</tt></dt>
<dd><t>A map of alert ids to Alert objects (see <xref target="I-D.ietf-calext-jscalendarbis" section="4.5.1" />) to apply for events where "showWithoutTime" is true (commonly referred to as "all day" events) and "useDefaultAlerts" is true. Ids MUST be unique across all default alerts in the account, including those in other calendars; a UUID <xref target="RFC9562" /> is recommended. The "trigger" MUST NOT be an AbsoluteTrigger, as this would fire for every event at the same time and so does not make sense for a default alert.</t>
<t>If omitted on creation, the default is server dependent. For example, servers may choose to always default to null, or may copy the alerts from the default calendar.</t></dd>
<dt><strong>timeZone</strong>: <tt>TimeZoneId|null</tt> (default: null)</dt>
<dd>The time zone to use for events without a time zone when the server needs to resolve them into absolute time, e.g., for alerts or availability calculation. The value MUST be a time zone id from the IANA Time Zone Database <xref target="IANA-TZDB" />. If null, the timeZone of the account's associated Principal will be used.</dd>
<dt><strong>shareWith</strong>: <tt>Id[CalendarRights]|null</tt> (default: null)</dt>
<dd><t>This is a map configuring who the calendar is shared with, or null if it is not shared with anyone. Each key in the map is the id of a Principal with whom the calendar is shared. The value for each key is the set of access rights that Principal has for the calendar. The account id for the Principals may be found in the <tt>urn:ietf:params:jmap:principals:owner</tt> capability of the Account to which the calendar belongs.</t>
<t>The Principal to which this calendar belongs MUST NOT be in the map.</t>
<t>The property may only be modified if the user has the mayShare right.</t></dd>
<dt><strong>myRights</strong>: <tt>CalendarRights</tt> (server-set)</dt>
<dd><t>The set of access rights the user has in relation to this Calendar. If any event is in multiple calendars, the user has the following rights:</t>

<ul spacing="compact">
  <li>The user may fetch the event if they have the mayReadItems right on any calendar the event is in.</li>
  <li>The user may remove an event from a calendar (by modifying the event's "calendarIds" property) if the user has the appropriate permission for that
calendar.</li>
  <li>The user may make other changes to the event if they have the right to do so in <strong>all</strong> calendars to which the event belongs.</li>
</ul></dd>
</dl>

<t>A <strong>CalendarRights</strong> object has the following properties:</t>

<dl spacing="normal" newline="true">
<dt><strong>mayReadFreeBusy</strong>: <tt>Boolean</tt></dt>
<dd>The user may read the free-busy information for this calendar as part of a call to "Principal/getAvailability" (see <xref target="principal-getavailability" />).</dd>
<dt><strong>mayReadItems</strong>: <tt>Boolean</tt></dt>
<dd>The user may fetch the events in this calendar.</dd>
<dt><strong>mayWriteAll</strong>: <tt>Boolean</tt></dt>
<dd>The user may create, modify or destroy all events in this calendar, or move events to or from this calendar. If this is true, the mayWriteOwn, mayUpdatePrivate and mayRSVP properties MUST all also be true.</dd>
<dt><strong>mayWriteOwn</strong>: <tt>Boolean</tt></dt>
<dd>The user may create, modify or destroy an event on this calendar if either they are the owner of the event (see below) or the event has no owner. This means the user may also transfer ownership by updating an event so they are no longer an owner.</dd>
<dt><strong>mayUpdatePrivate</strong>: <tt>Boolean</tt></dt>
<dd><t>The user may modify per-user properties (see <xref target="per-user-properties" />) on all events in the calendar, even if they would not otherwise have permission to modify that event. These properties MUST all be stored per-user, and changes do not affect any other user of the calendar.</t>
<t>The user may also modify these properties on a per-occurrence basis for recurring events (updating the "recurrenceOverrides" property of the event to do so).</t></dd>
<dt><strong>mayRSVP</strong>: <tt>Boolean</tt></dt>
<dd><t>The user may modify the following properties of any Participant object that corresponds to one of the user's ParticipantIdentity objects in the account, even if they would not otherwise have permission to modify that event:</t>

<ul spacing="compact">
<li>participationStatus</li>
<li>expectReply</li>
<li>scheduleSequence</li>
<li>scheduleUpdated</li>
</ul>

<t>If the event has its "mayInviteSelf" property set to true (see <xref target="mayinviteself" />), then the user may also add a new Participant to the event with a calendarAddress property that is the same as the calendarAddress property of one of the user's ParticipantIdentity objects in the account. The "roles" property of the participant MUST only contain "attendee".</t>

<t>If the event has its "mayInviteOthers" property set to true (see <xref target="mayinviteothers" />) and there is an existing Participant in the event corresponding to one of the user's ParticipantIdentity objects in the account, then the user may also add new participants. The "roles" property of any new participant MUST only contain "attendee".</t>

<t>The user may also do all of the above on a per-occurrence basis for recurring events (updating the "recurrenceOverrides" property of the event to do so).</t></dd>
<dt><strong>mayShare</strong>: <tt>Boolean</tt></dt>
<dd>The user may modify the "shareWith" property for this calendar.</dd>
<dt><strong>mayDelete</strong>: <tt>Boolean</tt></dt>
<dd>The user may delete the calendar itself.</dd>
</dl>
<t>The user is an <strong>owner</strong> for an event if the CalendarEvent object has a "participants" property, and one of the Participant objects both:</t>

<ol type="a">
<li>Has the "owner" role.</li>
<li>Corresponds to one of the user's ParticipantIdentity objects in the account (as per <xref target="participant-identities" />).</li>
</ol>

<t>An event has no owner if its "participants" property is null or omitted, or if none of the Participant objects have the "owner" role.</t>

<section anchor="calendar-get"><name>Calendar/get</name>
<t>This is a standard "/get" method as described in <xref target="RFC8620" section="5.1" />. The "ids" argument may be null to fetch all at once.</t>
<t>If mayReadFreeBusy is the only permission the user has, the calendar MUST NOT be returned in "Calendar/get" and "Calendar/query"; it must behave as though it did not exist. The data is just used as part of "Principal/getAvailability".</t>
</section>

<section anchor="calendar-changes"><name>Calendar/changes</name>
<t>This is a standard "/changes" method as described in <xref target="RFC8620" section="5.2" />.</t>
</section>

<section anchor="calendar-set"><name>Calendar/set</name>
<t>This is a standard "/set" method as described in <xref target="RFC8620" section="5.3" /> but with the following additional request arguments:</t>

<dl spacing="normal" newline="true">
<dt><strong>onDestroyRemoveEvents</strong>: <tt>Boolean</tt> (default: false)</dt>
<dd>If false, any attempt to destroy a Calendar that still has CalendarEvents in it will be rejected with a <tt>calendarHasEvent</tt> SetError. If true, any CalendarEvents that were in the Calendar will be removed from it, and if in no other Calendars they will be destroyed. This MUST NOT send scheduling messages to participants. It MAY create CalendarEventNotification objects, if deemed likely to be helpful to the user. In the case of a calendar with a large number of events, the large number of notifications resulting from the single deletion is unlikely to be useful to the user, and the server MAY choose to omit creating them. In systems supporting JMAP Sharing, a single ShareNotification (as defined in <xref target="RFC9670" section="3"/>) MUST be created for each Principal the calendar was previously shared with, to inform them they no longer have access to the calendar.</dd>
<dt><strong>onSuccessSetIsDefault</strong>: <tt>Id|null</tt></dt>
<dd><t>If an id is given, and all creates, updates and destroys (if any) succeed
without error, the server will try to set this calendar as the default. (For
references to Calendar creations, this is equivalent to a
creation-reference, so the id will be the creation id prefixed with a "#".)</t>
<t>If the id is not found, or the change is not permitted by the server for
  policy reasons, it MUST be ignored and the currently default calendar (if
  any) will remain as such. No error is returned to the client in this case.</t>
<t>As per <xref target="RFC8620" section="5.3" />, if the default is successfully changed, any
  changed objects MUST be reported in either the "created" or "updated"
  argument in the response as  appropriate, with the server-set value included.</t></dd>
</dl>

<t>The "shareWith" property may only be set by users that have the mayShare right.
When modifying the "shareWith" property, the user cannot give a right to a Principal if the Principal did not already have that right and the user making the change also does not have that right. Any attempt to do so must be rejected with a <tt>forbidden</tt> SetError.</t>
<t>Users can subscribe or unsubscribe to a calendar by setting the "isSubscribed" property. The server MAY forbid users from subscribing to certain calendars even though they have permission to see them, rejecting the update with a <tt>forbidden</tt> SetError.</t>
<t>The following properties may be set by anyone who is subscribed to the calendar and are always stored per-user:</t>

<ul spacing="compact">
<li>name</li>
<li>color</li>
<li>sortOrder</li>
<li>isVisible</li>
<li>timeZone</li>
<li>includeInAvailability</li>
<li>defaultAlertsWithoutTime</li>
<li>defaultAlertsWithTime</li>
</ul>

<t>The "name", "color", and "timeZone" properties are initially inherited from the owner's copy of the calendar, but if set by a user with whom the calendar is shared, they then get their own copy of the property. This does not change the value for any other users. If the value of the property in the owner's calendar changes after this, it does not overwrite any user's custom value.</t>
<t>The "sortOrder", "isVisible", "includeInAvailability", "defaultAlertsWithTime", and "defaultAlertsWithoutTime" properties are initially the default value for each user with whom the calendar is shared; they are not inherited from the owner.</t>

<t>The following extra SetError type is defined:</t>
<t>For "destroy":</t>

<dl spacing="normal" newline="true">
<dt><strong>calendarHasEvent</strong></dt>
<dd>The Calendar has at least one CalendarEvent assigned to it, and the "onDestroyRemoveEvents" argument was false.</dd>
</dl>
</section>
</section>

<section anchor="calendar-events"><name>Calendar Events</name>
<t>A <strong>CalendarEvent</strong> object contains information about an event, or recurring series of events, that takes place at a particular time. It is a JSCalendar Event object, as defined in <xref target="I-D.ietf-calext-jscalendarbis" />, with the following additional properties:</t>

<dl spacing="normal" newline="true">
<dt><strong>id</strong>: <tt>Id</tt> (immutable; server-set)</dt>
<dd>The id of the CalendarEvent. The id uniquely identifies a JSCalendar Event with a particular uid and recurrenceId within a particular account.</dd>
<dt><strong>baseEventId</strong>: <tt>Id|null</tt> (immutable; server-set)</dt>
<dd>This is only defined if the "id" property is a synthetic id, generated by the server to represent a particular instance of a recurring event (see <xref target="calendarevent-query" />). This property gives the id of the "real" CalendarEvent this was generated from.</dd>
<dt><strong>calendarIds</strong>: <tt>Id[Boolean]</tt></dt>
<dd>The set of Calendar ids this event belongs to. An event MUST belong to one or more Calendars at all times (until it is destroyed). The set is represented as an object, with each key being a Calendar id. The value for each key in the object MUST be true.</dd>
<dt><strong>isDraft</strong>: <tt>Boolean</tt> (default: false)</dt>
<dd>If true, this event is to be considered a draft. The server will not send any scheduling messages to participants or send push notifications for alerts. This may only be set to true upon creation. Once set to false, the value cannot be updated to true. This property MUST NOT appear in "recurrenceOverrides".</dd>
<dt><strong>isOrigin</strong>: <tt>Boolean</tt> (server-set)</dt>
<dd>
  <t>Is this the authoritative source for this event (i.e., does it control
scheduling for this event; the event has not been added as a result of an
invitation from another calendar system)? This is true if, and only if:</t>
  <ul spacing="compact">
    <li>the event's "organizerCalendarAddress" property is null; or</li>
    <li>the account will receive messages sent to the "organizerCalendarAddress" URI.</li>
  </ul>
</dd>
<dt><strong>utcStart</strong>: <tt>UTCDateTime</tt></dt>
<dd><t>For simple clients that do not implement time zone support. Clients should only use this if also asking the server to expand recurrences, as you cannot accurately expand a recurrence without the original time zone.</t>
<t>This property is calculated at fetch time by the server. Time zones are political and they can and do change at any time. Fetching exactly the same property again may return a different result if the time zone data has been updated on the server. Time zone data changes are not considered "updates" to the event.</t>
<t>If set, the server will convert the UTC date to the event's current time zone and store the local time. If the event does not have a non-null "timeZone" property, the server MUST also set this property (and return it in the created/updated response, as per <xref target="RFC8620" section="5.3" />). The value MUST be the "timeZone" property of the Calendar(s) the event is in if all of them have the same non-null value. Otherwise, the time zone is to be set to "Etc/UTC".</t>
<t>This property is not included in "CalendarEvent/get" responses by default and must be requested explicitly.</t>
<t>Floating events (events without a time zone) will be interpreted as per the time zone given as a "CalendarEvent/get" argument.</t>
<t>Note that it is not possible to accurately calculate the expansion of a recurrence rule or recurrence overrides with the "utcStart" property rather than the local start time. Even simple recurrences such as "repeat weekly" may cross a daylight-savings boundary and end up at a different UTC time. Clients that wish to use "utcStart" are RECOMMENDED to request the server expand recurrences (see <xref target="calendarevent-query" />).</t></dd>
<dt><strong>utcEnd</strong>: <tt>UTCDateTime</tt></dt>
<dd>The server calculates the end time in UTC from the start/timeZone/duration properties of the event. This property is not included by default and must be requested explicitly. Like utcStart, it is calculated at fetch time if requested and may change due to time zone data changes. Floating events will be interpreted as per the time zone given as a "CalendarEvent/get" argument.</dd>
<dt><strong>useDefaultAlerts</strong>: <tt>Boolean</tt> (optional, default: false)</dt>
<dd>If true, use the user's default alerts and ignore the value of the <tt>alerts</tt> property (see <xref target="default-alerts" />).</dd>
</dl>

<t>CalendarEvent objects MUST NOT have a "method" property as this is only used when representing iTIP <xref target="RFC5546"></xref> scheduling messages, not events in a data store.</t>

<section anchor="additional-jscalendar-properties"><name>Additional common-use JSCalendar properties</name>
<t>This document defines three new JSCalendar properties for common use.</t>

<section anchor="mayinviteself"><name>mayInviteSelf</name>
<t>Type: <tt>Boolean</tt> (default: false)</t>
<t>Context: Event, Task</t>
<t>If true, anyone may add themselves to the event as a participant with the "attendee" role. This property MUST NOT be altered in the recurrenceOverrides; it may only be set on the base object.</t>
</section>

<section anchor="mayinviteothers"><name>mayInviteOthers</name>
<t>Type: <tt>Boolean</tt> (default: false)</t>
<t>Context: Event, Task</t>
<t>If true, any current participant with the "attendee" role may add new participants with the "attendee" role to the event. This property MUST NOT be altered in the recurrenceOverrides; it may only be set on the base object.</t>
</section>

<section anchor="hideattendees"><name>hideAttendees</name>
<t>Type: <tt>Boolean</tt> (default: false)</t>
<t>Context: Event, Task</t>
<t>If true, only the owners of the event may see the full set of participants. Other users with access to the event may only see the owners and themselves. This property MUST NOT be altered in the recurrenceOverrides; it may only be set on the base object.</t>
</section>
</section>

<section anchor="additional-reserved-jscalendar-properties"><name>Additional reserved JSCalendar properties</name>
<t>This document also reserves two new JSCalendar properties in the Participant object for use in JMAP Calendars:</t>

<section anchor="schedulesequence"><name>scheduleSequence</name>
<t>Type: <tt>UnsignedInt</tt> (default: 0)</t>
<t>Context: Participant</t>
<t>This is the <tt>sequence</tt> property of the most recent iTIP response that has been applied for this participant. It can be compared to the <tt>sequence</tt> property in future responses to detect and discard older responses
delivered out of order.</t>
<t>Whenever an event is updated due to an iTIP response, whether by the server or the client, this property MUST be set appropriately.</t>
</section>

<section anchor="scheduleupdated"><name>scheduleUpdated</name>
<t>Type: <tt>UTCDateTime</tt> (optional)</t>
<t>Context: Participant</t>
<t>This is the <tt>updated</tt> property of the most recent iTIP response that has been applied for this participant. It can be compared to the <tt>updated</tt> property in future responses to detect and discard older responses
delivered out of order (if the "scheduleSequence" is the same).</t>
<t>Whenever an event is updated due to an iTIP response, whether by the server or the client, this property MUST be set appropriately.</t>
</section>

</section>

<section anchor="attachments"><name>Attachments</name>
<t>The Link object, as defined in <xref target="I-D.ietf-calext-jscalendarbis" section="1.4.11" />, with a "rel" property equal to "enclosure" is used to represent attachments. Instead of mandating an "href" property, clients may set a "blobId" property instead to reference a blob of binary data in the account, as per <xref target="RFC8620" section="6" />.</t>
<t>The server MUST translate this to an embedded <tt>data:</tt> URL <xref target="RFC2397" /> when sending the event to a system that cannot access the blob. Servers that support CalDAV access to the same data are recommended to expose these files as managed attachments <xref target="RFC8607" />.</t>
</section>

<section anchor="per-user-properties"><name>Per-user properties</name>
<t>In shared calendars, any top-level property registered in the IANA registry as "Is Per-User: yes" (see <xref target="update-to-the-jscalendar-properties-registry" />) MUST be stored per-user. This includes:</t>

<ul spacing="compact">
<li>keywords</li>
<li>color</li>
<li>freeBusyStatus</li>
<li>useDefaultAlerts</li>
<li>alerts</li>
</ul>
<t>If the user modifies any such properties on a per-occurrence basis for recurring events then again, these MUST also be stored per-user. Sharees initially receive the default value for each of these properties, not whatever value another user may have set.</t>
<t>When writing only per-user properties, the "updated" property MUST also be stored just for that user if set. When fetching the "updated" property, the value to return is whichever is later of the per-user updated time or the updated time of the base event.</t>
</section>

<section anchor="recurring-events"><name>Recurring events</name>
<t>Events may recur, in which case they represent multiple occurrences or instances. The data store will either contain a single base event, containing a recurrence rule and/or recurrence overrides; or, a set of individual instances (when invited to specific occurrences only).</t>
<t>The client may ask the server to expand recurrences within a specific time range in "CalendarEvent/query". This will generate synthetic ids representing individual instances in the requested time range. The client can fetch and update the objects using these ids and the server will make the appropriate changes to the base event. Synthetic ids do not appear in "CalendarEvent/changes" responses; only the ids of events as actually stored on the server.</t>
<t>If the user is invited to specific instances then later added to the base event, "CalendarEvent/changes" will show the ids of all the individual instances being destroyed and the id for the base event being created.</t>
</section>

<section anchor="updating-for-this-and-future"><name>Updating for "this-and-future"</name>
<t>When editing a recurring event, you can either update the base event (affecting all instances unless overridden) or update an override for a specific occurrence. To update all occurrences from a specific point onwards, there are therefore two options: split the event, or update the base event and override all occurrences before the split point back to their original values.</t>

<section anchor="splitting-an-event"><name>Splitting an event</name>
<t>If the event is not scheduled (has no participants), the simplest thing to do is to duplicate the event, modifying the recurrence rule of the original so it finishes before the split point, and the duplicate so it starts at the split point. As per JSCalendar <xref target="I-D.ietf-calext-jscalendarbis" section="4.1.2" />, a "next" and "first" relation MUST be set on the new objects respectively.</t>
</section>

<section anchor="updating-the-base-event-and-overriding-previous"><name>Updating the base event and overriding previous</name>
<t>Splitting an event with participants is problematic, because they will see two separate changes and may not understand they are connected.</t>

<t>An alternative approach is to do the following:</t>

<ol>
<li>Update the base event with the changes for "this and future".</li>
<li>Create overrides for all occurrences before the split point to restore the properties to their previous values.</li>
</ol>

<t>This approach MUST be used if the user does not have the permissions required to split the event (e.g., if they are not an owner of the event).</t>

<t>This approach is RECOMMENDED if making changes to properties other than "start", "timeZone", or recurrenceRule", as the single event update is easier for participants to understand.</t>

<t>Modifying the recurrence rule or start date/time zone with this method is NOT RECOMMENDED. While allowed by the data format specification, there is poor support in servers for recurrence overrides that add instances prior to the base event start date, and so this should be avoided. When making a change to these properties, the client SHOULD split the event instead, unless it knows all participants are using software that can handle this type of change.</t>
</section>
</section>

<section anchor="calendarevent-get"><name>CalendarEvent/get</name>
<t>This is a standard "/get" method as described in <xref target="RFC8620" section="5.1" />, with four extra arguments:</t>

<dl spacing="normal" newline="true">
<dt><strong>recurrenceOverridesBefore</strong>: <tt>UTCDateTime|null</tt></dt>
<dd>If given, only recurrence overrides with a recurrence id before this date (when translated into UTC) will be returned.</dd>
<dt><strong>recurrenceOverridesAfter</strong>: <tt>UTCDateTime|null</tt></dt>
<dd>If given, only recurrence overrides with a recurrence id on or after this date (when translated into UTC) will be returned.</dd>
<dt><strong>reduceParticipants</strong>: <tt>Boolean</tt> (default: false)</dt>
<dd>If true, only participants with the "owner" role or corresponding to the user's participant identities will be returned in the "participants" property of the base event and any recurrence overrides. If false, all participants will be returned.</dd>
<dt><strong>timeZone</strong>: <tt>TimeZoneId</tt> (default "Etc/UTC")</dt>
<dd>The time zone to use when calculating the "utcStart"/"utcEnd" property of floating events. This argument has no effect if those properties are not requested.</dd>
</dl>
<t>A CalendarEvent object is a JSCalendar Event object so may have arbitrary properties. If the client makes a "CalendarEvent/get" call with a null or omitted "properties" argument, all properties that are defined on the JSCalendar Event object in the store are returned except for "iCalComponent". In addition, the "id", "calendarIds", "isDraft", and "isOrigin" properties MUST be included on each object. The "utcStart" and "utcEnd" computed properties are only returned if explicitly requested. If either are requested, the "recurrenceOverrides" property MUST NOT be requested (recurrence overrides cannot be interpreted accurately with just the UTC times). The "iCalComponent" property is also only returned if explicitly requested.</t>
<t>If specific properties are requested from the JSCalendar Event and the property is not present on the object in the server's store, the server MUST return the default value if known for that property.</t>
<t>A requested id may represent a server-expanded single instance of a recurring event if the client asked the server to expand recurrences in "CalendarEvent/query". In such a case, the server will resolve any overrides and set the appropriate "start" and "recurrenceId" properties on the CalendarEvent object returned to the client. The "recurrenceRule" and "recurrenceOverrides" properties MUST be returned as null if requested for such an event.</t>
<t>An event with the same uid/recurrenceId may appear in different accounts. Clients may coalesce the view of such events, but must be aware that the data may be different in the different accounts due to per-user properties, difference in permissions, etc.</t>
<t>The "hideAttendees" property of a JSCalendar Event object allows the event owner(s) to hide the full set of participants when sharing the event. If this is true, when a non-owner fetches the event the server MUST only return participants with the "owner" role or corresponding to the user's participant identities.</t>
<t>The "privacy" property of a JSCalendar Event object allows the Principal that owns the calendar to override how the event is exposed to those with whom the calendar is shared. If set to "private", then when another user fetches the event the server MUST only return properties that are:</t>

<ul spacing="compact">
<li>the basic time and metadata properties of the JSCalendar Event object as specified in <xref target="I-D.ietf-calext-jscalendarbis" section="4.4.3" />; or</li>
<li>properties that are wholly derived from these permitted properties (i.e., utcStart, utcEnd); or</li>
<li>Additional CalendarEvent properties not derived from the JSCalendar Event data (i.e., id, baseEventId, calendarIds, isDraft, isOrigin).</li>
</ul>
<t>If "privacy" is set to "secret", the server MUST behave as though the event does not exist for all users other than the Principal that owns the calendar.</t>
</section>

<section anchor="calendarevent-changes"><name>CalendarEvent/changes</name>
<t>This is a standard "/changes" method as described in <xref target="RFC8620" section="5.2" />.</t>
<t>Synthetic ids generated by the server expanding recurrences in "CalendarEvent/query" do not appear in "CalendarEvent/changes" responses; only the ids of events as actually stored on the server.</t>
</section>

<section anchor="calendarevent-set"><name>CalendarEvent/set</name>
<t>This is a standard "/set" method as described in <xref target="RFC8620" section="5.3" />, with the following extra argument:</t>

<dl spacing="normal" newline="true">
<dt><strong>sendSchedulingMessages</strong>: <tt>Boolean</tt> (default: false)</dt>
<dd>
<t>If true then any changes to scheduled events will be sent to all the participants (if the server is the origin of the event) or back to the origin (otherwise), as per <xref target="sending-invitations-and-responses" />.</t>
</dd>
</dl>

<t>An id may represent a server-expanded single instance of a recurring event if the client asked the server to expand recurrences in "CalendarEvent/query". When the synthetic id for such an instance is given, the server MUST process an update as an update to the recurrence override for that instance on the base event, and a destroy as removing just that instance.</t>
<t>Clients MUST NOT send an update/destroy to both the base event and a synthetic instance in a single "/set" request; the result of this is undefined. Note however, a client may replace a series of explicit instances (each with the same uid but a different "recurrenceId" property) with the base event (same uid, no recurrenceId) in a single "/set" call. (So the "/set" will destroy the existing instances and create the new base event.) This will happen when someone is initially invited to a specific instance or instances of a recurring event, then later invited to the whole series.</t>
<t>If a property is set to null in a create/update, this is equivalent to
omitting/removing the property from the JSCalendar Event object.</t>
<t>Servers MUST enforce the user's permissions as returned in the "myRights" property of the Calendar objects and reject changes with a <tt>forbidden</tt> SetError if not allowed.</t>
<t>The "privacy" property of a JSCalendar Event object allows the Principal that owns the calendar to override how the event is exposed to those with whom the calendar is shared. If this is set to "private", the event may only be destroyed or updated by the owner of the calendar it is in. Any attempt by another user to modify such an event MUST be rejected with a <tt>forbidden</tt> SetError (even if only modifying per-user properties). If set to "secret", the server MUST behave as though the event does not exist for all users other than the Principal that owns the calendar.</t>
<t>The "privacy" property MUST NOT be set to anything other than "public" (the default) for events in a calendar that does not belong to the user (e.g. a shared team calendar, or a calendar shared by another user). The server MUST reject this with an <tt>invalidProperties</tt> SetError.</t>
<t>If omitted on create, the server MUST set the following properties:</t>
<ul spacing="compact">
<li><tt>@type</tt> MUST be set to "Event"</li>
<li><tt>uid</tt> MUST be set to a new globally unique identifier, such as a UUID.</li>
<li><tt>created</tt> MUST be set to the current date-time.</li>
</ul>
<t>If (and only if) the server is the origin of the event (i.e., the event's "isOrigin" property is true), the "updated" property MUST be set to the current time by the server whenever an event is created or updated. If the client tries to set a value for this property it is not an error, but it MUST be overridden and replaced with the server's time. If the event is being created and the overridden "updated" time is now earlier than a client-supplied "created" time, the "created" time MUST also be overridden to the server's time. If the server is not the origin of the event it MUST NOT automatically set an "updated" time, as this can break correct processing of scheduling messages.</t>
<t>Clients MUST NOT allow users to edit anything other than per-user
properties when the "isOrigin" property is false, even if the calendar "myRights" allows them to do so. All other properties may be overwritten when a future update arrives to this event from the origin (e.g., via an iTIP REQUEST message). Such updates may be directly applied by the server, or applied at the user's request by a client if it has access to the data through some other means (e.g., the client also has access to the user's email and can parse an iMIP message).</t>
<t>When updating an event, if all of:</t>

<ul spacing="compact">
<li>a property has been changed other than "calendarIds", "isDraft", "updated" or a per-user property (see <xref target="per-user-properties" />); and</li>
<li>the server is the origin of the event (the "isOrigin" property is true); and</li>
<li>the "sequence" property is not explicitly set in the update, or the given
value is less than or equal to the current "sequence" value on the server;</li>
</ul>
<t>then the server MUST increment the "sequence" value by one.</t>
<t>The "method" property MUST NOT be set. Any attempt to do so is rejected with a standard <tt>invalidProperties</tt> SetError.</t>
<t>If "utcStart" is set, this is translated into a "start" property using the server's current time zone information. It MUST NOT be set in addition to a "start" property and it cannot be set inside "recurrenceOverrides"; this MUST be rejected with an <tt>invalidProperties</tt> SetError.</t>
<t>Similarly, the "utcEnd" property is translated into a "duration" property if set. It MUST NOT be set in addition to a "duration" property and it cannot be set inside "recurrenceOverrides"; this MUST be rejected with an <tt>invalidProperties</tt> SetError.</t>
<t>The server does not automatically reset the "partipationStatus" or "expectReply" properties of a Participant when changing other event details. Clients should either be intelligent about whether the change invalidates previous RSVPs, or ask the user whether to reset them.</t>
<t>The server MAY enforce that all events have an owner, for example in team calendars. If the user tries to create an event without participants in such a calendar, the server MUST automatically add a participant with the "owner" role corresponding to one of the user's ParticipantIdentities (see <xref target="participant-identities" />).</t>
<t>When creating an event with participants, or adding participants to an event that previously did not have participants, the server MUST set the "organizerCalendarAddress" property of the event if not present. Clients SHOULD NOT set the "organizerCalendarAddress" property for events when the user adds participants, as the server is better placed to choose the best address (for example, it might choose to user a different address per event).</t>

<t>The following extra SetError type is defined:</t>

<dl spacing="normal" newline="true">
<dt><strong>noSupportedScheduleMethods</strong></dt>
<dd>The server was requested to send scheduling messages, but does not support any of the methods available for at least one of the recipients.</dd>
</dl>

<section anchor="patching"><name>Patching</name>
<t>The JMAP "/set" method allows you to update an object by sending a patch, rather than having to supply the whole object. When doing so, care must be taken if updating a property of a CalendarEvent where the value is itself a PatchObject, e.g. inside "localizations" or "recurrenceOverrides". In particular, you cannot add a property with value null to the CalendarEvent using a direct patch on that property, as this is interpreted instead as a patch to remove the property.</t>
<t>This is more easily understood with an example. Suppose you have a CalendarEvent object like so:</t>

<figure>
<name>A CalendarEvent object</name>
<sourcecode type="json">
{
  "id": "123",
  "title": "FooBar team meeting",
  "start": "2025-01-08T09:00:00",
  "recurrenceRule": {
    "@type": "RecurrenceRule",
    "frequency": "weekly"
  },
  "organizerCalendarAddress": "mailto:6489-4f14-a57f-c1@schedule.example.com",
  "participants": {
    "dG9tQGZvb2Jhci5xlLmNvbQ": {
      "@type": "Participant",
      "name": "Tom",
      "email": "tom@foobar.example.com",
      "calendarAddress": "mailto:6489-4f14-a57f-c1@calendar.example.com",
      "participationStatus": "accepted",
      "roles": {
        "attendee": true
      }
    },
    "em9lQGZvb2GFtcGxlLmNvbQ": {
      "@type": "Participant",
      "name": "Zoe",
      "email": "zoe@foobar.example.com",
      "calendarAddress": "mailto:zoe@foobar.example.com",
      "participationStatus": "accepted",
      "roles": {
        "owner": true,
        "attendee": true,
        "chair": true
      }
    },
    "recurrenceOverrides": {
      "2025-03-05T09:00:00": {
        "start": "2025-03-05T10:00:00",
        "participants/dG9tQGZvb2Jhci5xlLmNvbQ/participationStatus":
          "declined"
      }
    }
  }
}
</sourcecode>
</figure>
<t>In this example, Tom is normally going to the weekly meeting but has declined
the occurrence on 2025-03-05, which starts an hour later than normal. Now, if Zoe too were to decline that meeting, she could update the event by just sending a patch like so:</t>

<figure>
<name>"methodCalls" Property of a JMAP Request. NOTE: '\' line wrapping per <xref target="RFC8792" />.</name>
<sourcecode type="json">
[[ "CalendarEvent/set", {
  "accountId": "a0x9",
  "update": {
    "123": {
      "recurrenceOverrides/2025-03-05T09:00:00/\
        participants~1em9lQGZvb2GFtcGxlLmNvbQ~1participationStatus":
          "declined"
    }
  }
}, "0" ]]
</sourcecode>
</figure>

<t>This patches the "2025-03-05T09:00:00" PatchObject in recurrenceOverrides so that it ends up like this:</t>

<figure>
<name>"recurrenceOverrides" Property in a CalendarEvent Object</name>
<sourcecode type="json">
"recurrenceOverrides": {
  "2025-03-05T09:00:00": {
    "start": "2025-03-05T10:00:00",
    "participants/dG9tQGZvb2Jhci5xlLmNvbQ/participationStatus":
      "declined",
    "participants/em9lQGZvb2GFtcGxlLmNvbQ/participationStatus":
      "declined"
  }
}
</sourcecode>
</figure>

<t>Now if Tom were to change his mind and remove his declined status override (thus meaning he is attending, as inherited from the top-level event), he might remove his patch from the overrides like so:</t>

<figure>
<name>"methodCalls" Property of a JMAP Request. NOTE: '\' line wrapping per <xref target="RFC8792" />.</name>
<sourcecode type="json">
[[ "CalendarEvent/set", {
  "accountId": "a0x9",
  "update": {
    "123": {
      "recurrenceOverrides/2025-03-05T09:00:00/\
        participants~1dG9tQGZvb2Jhci5xlLmNvbQ~1participationStatus": null
    }
  }
}, "0" ]]
</sourcecode>
</figure>

<t>However, if you instead want to remove Tom from this instance altogether, you could not send this patch:</t>

<figure>
<name>"methodCalls" Property of a JMAP Request. NOTE: '\' line wrapping per <xref target="RFC8792" />.</name>
<sourcecode type="json">
[[ "CalendarEvent/set", {
  "accountId": "a0x9",
  "update": {
    "123": {
      "recurrenceOverrides/2025-03-05T09:00:00/\
        participants~1dG9tQGZvb2Jhci5xlLmNvbQ": null
    }
  }
}, "0" ]]
</sourcecode>
</figure>

<t>This would mean removing the "participants/dG9tQGZvb2Jhci5xlLmNvbQ" property at path "recurrenceOverrides" -&gt; "2025-03-05T09:00:00" inside the object; but this doesn't exist. We actually want to add this property and make it map to null. The client must instead send the full object that contains the property mapping to null, like so:</t>

<figure>
<name>"methodCalls" Property of a JMAP Request</name>
<sourcecode type="json">
[[ "CalendarEvent/set", {
  "accountId": "a0x9",
  "update": {
    "123": {
      "recurrenceOverrides/2025-03-05T09:00:00": {
        "start": "2025-03-05T10:00:00",
        "participants/em9lQGZvb2GFtcGxlLmNvbQ/participationStatus":
          "declined",
        "participants/dG9tQGZvb2Jhci5xlLmNvbQ": null
      }
    }
  }
}, "0" ]]
</sourcecode>
</figure>
</section>

<section anchor="sending-invitations-and-responses"><name>Sending invitations and responses</name>
<t>If "sendSchedulingMessages" is true, the server MUST send appropriate iTIP <xref target="RFC5546"></xref> scheduling messages after successfuly creating, updating or destroying a calendar event.</t>
<t>When determining which scheduling messages to send, the server must first establish whether it is the <strong>origin</strong> of the event, as described in the "isOrigin" property.</t>
<t>The server sends the scheduling message via iTIP <xref target="RFC5546" /> to the "calendarAddress" property of a participant (if the server is the origin) or the "organizerCalendarAddress" property of the event (otherwise). If any of the recipients do not have a calendar address the server can send to, the server MUST reject the change with a <tt>noSupportedScheduleMethods</tt> SetError.</t>
<t>If the server is the origin of the event it MUST NOT send messages to any participant where it will receive the message back in the same account (i.e. it must not send messages to the owner of the calendar the event is already on).</t>
<t>If sending via iMIP <xref target="RFC6047"></xref>, the server MAY choose to only send updates it deems "essential" to avoid flooding the recipient's email with changes they do not care about. For example, changes to the participation status of another participant, or changes to events solely in the past may be omitted.</t>

<section anchor="request"><name>REQUEST</name>
<t>When the server is the origin for the event, a REQUEST message (<xref target="RFC5546" sectionFormat="comma" section="3.2.2" />) is sent to all current participants (except those corresponding to the owner of the calendar) if either:</t>

<ul spacing="compact">
<li><t>The event is being created; or</t></li>
<li><t>Any non per-user property (see <xref target="per-user-properties" />) is updated on the event (including adding/removing participants), except if just modifying the recurrenceOverrides such that CANCEL messages are generated (see the next section).</t></li>
</ul>
<t>Note, if the only change is adding an additional instance (not generated by the event's recurrence rule) to the recurrenceOverrides, this MAY be handled via sending an ADD message (<xref target="RFC5546" section="3.2.4" />) for the single instance rather than a REQUEST message for the base event. However, for interoperability reasons this is not recommended due to poor support in the wild for this type of message.</t>
<t>The server MUST ensure participants are only sent information about recurrence instances they are added to when sending scheduling messages for recurring events. If the participant is not invited to the full recurring event but only individual instances, scheduling messages MUST be sent for just those expanded occurrences individually. If a participant is invited to a recurring event, but removed via a recurrence override from a particular instance, any scheduling messages to this participant MUST return the instance as "excluded" (if it matches a recurrence rule for the event) or omit the instance entirely (otherwise).</t>
<t>If the event's "hideAttendees" property is set to true, the recipient MUST be the only attendee in the message; all others are omitted.</t>
</section>

<section anchor="cancel"><name>CANCEL</name>
<t>When the server is the origin for the event, a CANCEL message (<xref target="RFC5546" sectionFormat="comma" section="3.2.5" />) is sent if any of:</t>

<ul spacing="compact">
<li><t>A participant is removed from either the base event or a single instance (the message is only sent to this participant; remaining participants will get a REQUEST, as described above).</t></li>
<li><t>The event is destroyed.</t></li>
<li><t>An exclusion is added to recurrenceOverrides to remove an instance generated by the event's recurrence rule.</t></li>
<li><t>An additional instance (not generated by the event's recurrence rule) is removed from the recurrenceOverrides.</t></li>
</ul>
<t>In each of the latter 3 cases, the message is sent to all participants (except those corresponding to the owner of the calendar).</t>
</section>

<section anchor="reply"><name>REPLY</name>
<t>When the server is <strong>not</strong> the origin for the event, a REPLY message (<xref target="RFC5546" sectionFormat="comma" section="3.2.3" />) is sent for every participant corresponding to one of the user's ParticipantIdentitities in the account if any of the following changes are made:</t>

<ul spacing="compact">
<li><t>The "participationStatus" property of the participant is changed, either for the base event or a specific instance, to any value other than "needs-action".</t></li>
<li><t>The event is created and the participationStatus is not "needs-action".</t></li>
<li><t>The event is destroyed and the participationStatus was not "needs-action".</t></li>
</ul>
<t>If the participationStatus property is changed for just a single instance of the event (i.e., set in recurrenceOverrides), the REPLY message SHOULD be sent for just that recurrence id.</t>
</section>
</section>

</section>

<section anchor="calendarevent-copy"><name>CalendarEvent/copy</name>
<t>This is a standard "/copy" method as described in <xref target="RFC8620" section="5.4" />.</t>
</section>

<section anchor="calendarevent-query"><name>CalendarEvent/query</name>
<t>This is a standard "/query" method as described in <xref target="RFC8620" section="5.5" />, with two extra arguments:</t>

<dl spacing="normal" newline="true">
<dt><strong>expandRecurrences</strong>: <tt>Boolean</tt> (default: false)</dt>
<dd>If true, the server will expand any recurring event. If true, the filter MUST be just a FilterCondition (not a FilterOperator) and MUST include both a "before" and "after" property. This ensures the server is not asked to return an infinite number of results.</dd>
<dt><strong>timeZone</strong>: <tt>TimeZoneId</tt></dt>
<dd>The time zone for before/after filter conditions (default: "Etc/UTC")</dd>
</dl>

<t>If expandRecurrences is true, a separate id will be returned for each instance of a recurring event that matches the query. This synthetic id is opaque to the client, but uniquely identifies the base event id + recurrence id within the account, allowing the server to resolve these for "/get" and "/set" operations. Otherwise, a single id will be returned for matching recurring events that represents the entire event.</t>
<t>There is no necessary correspondence between the ids of different instances of the same expanded event.</t>
<t>The following additional error may be returned instead of the "CalendarEvent/query" response:</t>
<t><tt>expandDurationTooLarge</tt>: The query has expandRecurrences set to true, and the duration between the "before" and "after" properties exceeds the maxExpandedQueryDuration limit (as found in the account capabilities).</t>
<t><tt>cannotCalculateOccurrences</tt>: The server cannot expand a recurrence required to return the results for this query.</t>

<section anchor="filtering"><name>Filtering</name>
<t>A <strong>FilterCondition</strong> object has the following properties, any of which may be omitted:</t>

<dl spacing="normal" newline="true">
<dt><strong>inCalendar</strong>: <tt>Id</tt></dt>
<dd>A Calendar id. An event must be in this calendar to match the condition.</dd>
<dt><strong>after</strong>: <tt>LocalDateTime</tt></dt>
<dd>The end of the event, or any recurrence of the event, in the time zone given as the "timeZone" argument, must be after this date to match the condition.</dd>
<dt><strong>before</strong>: <tt>LocalDateTime</tt></dt>
<dd>The start of the event, or any recurrence of the event, in the time zone given as the "timeZone" argument, must be before this date to match the condition.</dd>
<dt><strong>text</strong>: <tt>String</tt></dt>
<dd>Looks for the text in the "title", "description", "locations" (matching name/description), "participants" (matching name/email) and any other textual properties of the event or any recurrence of the event.</dd>
<dt><strong>title</strong>: <tt>String</tt></dt>
<dd>Looks for the text in the "title" property of the event, or the overridden "title" property of a recurrence.</dd>
<dt><strong>description</strong>: <tt>String</tt></dt>
<dd>Looks for the text in the "description" property of the event, or the overridden "description" property of a recurrence.</dd>
<dt><strong>location</strong>: <tt>String</tt></dt>
<dd>Looks for the text in the "locations" property of the event (matching name/description of a location), or the overridden "locations" property of a recurrence.</dd>
<dt><strong>owner</strong>: <tt>String</tt></dt>
<dd>Looks for the text in the name or email fields of a participant in the "participants" property of the event, or the overridden "participants" property of a recurrence, where the participant has a role of "owner".</dd>
<dt><strong>attendee</strong>: <tt>String</tt></dt>
<dd>Looks for the text in the name or email fields of a participant in the "participants" property of the event, or the overridden "participants" property of a recurrence, where the participant has a role of "attendee".</dd>
<dt><strong>uid</strong>: <tt>String</tt></dt>
<dd>The uid of the event is exactly the given string.</dd>
</dl>

<t>If expandRecurrences is true, all conditions must match against the same instance of a recurring event for the instance to match. If expandRecurrences is false, all conditions must match, but they may each match any instance of the event.</t>
<t>If zero properties are specified on the FilterCondition, the condition MUST always evaluate to true. If multiple properties are specified, ALL must apply for the condition to be true (it is equivalent to splitting the object into one-property conditions and making them all the child of an AND filter operator).</t>
<t>The exact semantics for matching <tt>String</tt> fields is <strong>deliberately not defined</strong> to allow for flexibility in indexing implementation, subject to the following:</t>

<ul spacing="compact">
<li>Text should be matched in a case-insensitive manner.</li>
<li>Text contained in either (but matched) single or double quotes should be treated as a <strong>phrase search</strong>, that is a match is required for that exact sequence of words, excluding the surrounding quotation marks. Use <tt>\"</tt>, <tt>\'</tt> and <tt>\\</tt> to match a literal <tt>"</tt>, <tt>'</tt> and <tt>\</tt> respectively in a phrase.</li>
<li>Outside of a phrase, white-space should be treated as dividing separate tokens that may be searched for separately in the event, but must all be present for the event to match the filter.</li>
<li>Tokens may be matched on a whole-word basis using stemming (so for example a text search for <tt>bus</tt> would match "buses" but not "business").</li>
</ul>
</section>

<section anchor="sorting"><name>Sorting</name>
<t>The following properties MUST be supported for sorting:</t>

<ul spacing="compact">
<li>start</li>
<li>uid</li>
<li>recurrenceId</li>
</ul>
<t>The following properties SHOULD be supported for sorting:</t>

<ul spacing="compact">
<li>created</li>
<li>updated</li>
</ul>
</section>
</section>

<section anchor="calendarevent-querychanges"><name>CalendarEvent/queryChanges</name>
<t>This is a standard "/queryChanges" method as described in <xref target="RFC8620" section="5.6" />.</t>
</section>

<section anchor="calendarevent-parse"><name>CalendarEvent/parse</name>
<t>This method allows the client to parse blobs as <tt>iCalendar</tt> files <xref target="RFC5545"></xref> to get <tt>CalendarEvent</tt> objects. This can be used to parse, display, and import information from iCalendar files without having to implement iCalendar parsing in the client. Server support for this is optional, and indicated via the <tt>urn:ietf:params:jmap:calendars:parse</tt> capability, as per <xref target="urn-ietf-params-jmap-calendars-parse" />.</t>
<t>The following metadata properties on the CalendarEvent objects will be null if requested:</t>

<ul spacing="compact">
<li>id</li>
<li>baseEventId</li>
<li>calendarIds</li>
<li>isDraft</li>
<li>isOrigin</li>
</ul>
<t>The "CalendarEvent/parse" method takes the following arguments:</t>

<dl spacing="normal" newline="true">
<dt><strong>accountId</strong>: <tt>Id</tt></dt>
<dd>The id of the account to use.</dd>
<dt><strong>blobIds</strong>: <tt>Id[]</tt></dt>
<dd>The ids of the blobs to parse.</dd>
<dt><strong>properties</strong>: <tt>String[]</tt></dt>
<dd>If supplied, only the properties listed in the array are returned for each <tt>CalendarEvent</tt> object. If omitted, defaults to all the properties.</dd>
</dl>

<t>The response object contains the following arguments:</t>

<dl spacing="normal" newline="true">
<dt><strong>accountId</strong>: <tt>Id</tt></dt>
<dd>The id of the account used for the call.</dd>
<dt><strong>parsed</strong>: <tt>Id[CalendarEvent[]]|null</tt></dt>
<dd>A map of blob ids to parsed <tt>CalendarEvent</tt> objects representations for each successfully parsed blob, or null if none.</dd>
<dt><strong>notFound</strong>: <tt>Id[]|null</tt></dt>
<dd>A list of blob ids given that could not be found, or null if none.</dd>
<dt><strong>notParsable</strong>: <tt>Id[]|null</tt></dt>
<dd>A list of blob ids given that corresponded to blobs that could not be parsed as CalendarEvents, or null if none.</dd>
</dl>

<t>Parsed <tt>iCalendars</tt> are to be converted into <tt>CalendarEvent</tt> objects following the process defined in the <eref target="https://datatracker.ietf.org/doc/draft-ietf-calext-jscalendar-icalendar">JSCalendar: Converting from and to iCalendar</eref> document.</t>
</section>
</section>

<section anchor="alerts"><name>Alerts</name>
<t>Alerts may be specified on events as described in <xref target="I-D.ietf-calext-jscalendarbis" section="4.5" />.</t>
<t>Alerts MUST only be triggered for events in calendars where the user is subscribed.</t>
<t>When an alert with an "email" action is triggered, the server MUST send an email to the user to notify them of the event. The content of the email is implementation specific. Clients MUST NOT perform an action for these alerts.</t>
<t>When an alert with a "display" action is triggered, clients should display an alert in a platform-appropriate manner to the user to remind them of the event. Clients with a full offline cache of events may choose to calculate when alerts should trigger locally. Alternatively, they can subscribe to push events from the server.</t>

<section anchor="default-alerts"><name>Default alerts</name>
<t>If the "useDefaultAlerts" property of an event is true, the alerts are taken from the "defaultAlertsWithTime" or "defaultAlertsWithoutTime" property of all Calendars (see <xref target="calendars" />) the event is in, rather than the "alerts" property of the CalendarEvent.</t>
<t>When using default alerts, the "alerts" property of the event is ignored except for the following:</t>

<ul spacing="compact">
<li>The "acknowledged" time for an alert is stored here when a default alert for the event is dismissed. The id of the alert MUST be the same as the id of the default alert in the calendar. See <xref target="acknowledging-an-alert" /> on acknowledging alerts.</li>
<li>If an alert has a "relatedTo" property where the parent is the id of one of the calendar default alerts, it is processed as normal and not ignored. This is to support snoozing default alerts; see <xref target="snoozing-an-alert" />.</li>
</ul>
</section>

<section anchor="acknowledging-an-alert"><name>Acknowledging an alert</name>
<t>To dismiss an alert, clients set the "acknowledged" property of the Alert object to the current date-time. If the alert was a calendar default, it may need to be added to the event at this point in order to acknowledge it. When other clients fetch the updated CalendarEvent they SHOULD automatically dismiss or suppress duplicate alerts (alerts with the same alert id that triggered on or before the "acknowledged" date-time) and alerts that have been removed from the event.</t>
<t>Setting the "acknowledged" property MUST NOT create a new recurrence override. For a recurring calendar object, the "acknowledged" property of the parent object MUST be updated, unless the alert is already overridden in the "recurrenceOverrides" property.</t>
</section>

<section anchor="snoozing-an-alert"><name>Snoozing an alert</name>
<t>Users may wish to dismiss an alert temporarily and have it come back after a specific period of time. To do this, clients MUST:</t>

<ol spacing="compact">
<li>Acknowledge the alert as described in <xref target="acknowledging-an-alert" />.</li>
<li>Add a new alert to the event that has an <tt>AbsoluteTrigger</tt> specifying the date-time when the alert will trigger again. Add a "relatedTo" property to the new alert, setting the "snooze" relation to point to the original alert. This MUST NOT create a new recurrence override; it is added to the same "alerts" property that contains the alert that was acknowledged in step 1.</li>
</ol>
<t>When acknowledging a snoozed alert (i.e. one with a "snooze" relatedTo pointing to the original alert), the client should delete the alert rather than setting the "acknowledged" property.</t>
</section>

<section anchor="push-events"><name>Push events</name>
<t>Servers that support the <tt>urn:ietf:params:jmap:calendars</tt> capability MUST support registering for the pseudo-type "CalendarAlert" in push subscriptions and event source connections, as described in <xref target="RFC8620"></xref>, Sections <xref target="RFC8620" section="7.2" sectionFormat="bare" /> and <xref target="RFC8620" section="7.3" sectionFormat="bare" />.</t>
<t>If requested, a CalendarAlert notification will be pushed whenever an alert is triggered for the user. For Event Source connections, this notification is pushed as an event called "calendarAlert".</t>
<t>A <strong>CalendarAlert</strong> object has the following properties:</t>

<dl spacing="normal" newline="true">
<dt><strong>@type</strong>: <tt>String</tt></dt>
<dd>This MUST be the string "CalendarAlert".</dd>
<dt><strong>accountId</strong>: <tt>Id</tt></dt>
<dd>The account id for the calendar in which the alert triggered.</dd>
<dt><strong>calendarEventId</strong>: <tt>Id</tt></dt>
<dd>The CalendarEvent id for the alert that triggered. Note, for a recurring
event this is the id of the base event, never a synthetic id for a particular
instance.</dd>
<dt><strong>uid</strong>: <tt>String</tt></dt>
<dd>The "uid" property of the CalendarEvent for the alert that triggered.</dd>
<dt><strong>recurrenceId</strong>: <tt>LocalDateTime|null</tt></dt>
<dd>The recurrenceId for the instance of the event for which this alert is being
triggered, or null if the event is not recurring.</dd>
<dt><strong>alertId</strong>: <tt>String</tt></dt>
<dd>The id for the alert that triggered.</dd>
</dl>
</section>
</section>

<section anchor="calendar-event-notifications"><name>Calendar Event Notifications</name>
<t>The CalendarEventNotification data type represents changes to events in calendars that the user is subscribed to where the change was not explicitly made by the user themself. CalendarEventNotifications are only created by the server; users cannot create them explicitly. They are stored in the same Account as the CalendarEvent that was changed.</t>
<t>Clients may present the list of notifications to the user and allow the user to dismiss them. To dismiss a notification, use a standard "/set" call to destroy it.</t>
<t>The server should create a CalendarEventNotification whenever an event is added, updated or destroyed by someone else. This includes:</t>
<ul spacing="compact">
<li>When an event is edited directly by another user on a shared calendar.</li>
<li>If the server chooses to auto-process scheduling requests, such as an iTIP message. Note, whether to auto-process such messages is an implementation policy.</li>
</ul>
<t>The CalendarEventNotification does not have any per-user data. A single instance may therefore be maintained on the server for all users with whom the calendar is shared. The server need only keep track of which users have yet to destroy the notification.</t>

<t>The server <bcp14>MAY</bcp14> limit the maximum number of notifications it will store for a user. When the limit is reached, any new notification will cause the previously oldest notification to be automatically deleted.</t>
<t>The server <bcp14>MAY</bcp14> coalesce notifications if appropriate or remove notifications after a certain period of time or that it deems are no longer relevant.</t>

<t>A <strong>CalendarEventNotification</strong> object has the following properties:</t>

<dl spacing="normal" newline="true">
<dt><strong>id</strong>: <tt>Id</tt></dt>
<dd>The id of the CalendarEventNotification.</dd>
<dt><strong>created</strong>: <tt>UTCDateTime</tt></dt>
<dd>The time this notification was created.</dd>
<dt><strong>changedBy</strong>: <tt>Person</tt></dt>
<dd><t>Who made the change. The Person object has the following properties:</t>

<dl spacing="compact" newline="true">
<dt><strong>name</strong>: <tt>String</tt></dt>
<dd>The name of the person who made the change.</dd>
<dt><strong>email</strong>: <tt>String|null</tt></dt>
<dd>The email of the person who made the change, or null if no email is available.</dd>
<dt><strong>principalId</strong>: <tt>Id|null</tt></dt>
<dd>The id of the Principal corresponding to the person who made the change, if any. This will be null if the change was due to an entity outside of this user's organisation, e.g. an iTIP invitation from an external person.</dd>
<dt><strong>calendarAddress</strong>: <tt>String|null</tt></dt>
<dd>The calendarAddress URI of the person who made the change, if any. This may be set if the change was made due to receving a scheduling message, such as an iTIP message, in addition to changes made by internal users.</dd>
</dl></dd>
<dt><strong>comment</strong>: <tt>String|null</tt></dt>
<dd>Comment sent along with the change by the user that made it. (e.g. COMMENT
property in an iTIP message), if any.</dd>
<dt><strong>type</strong>: <tt>String</tt></dt>
<dd><t>This MUST be one of:</t>
  <ul spacing="compact">
    <li><tt>"created"</tt></li>
    <li><tt>"updated"</tt></li>
    <li><tt>"destroyed"</tt></li>
  </ul>
</dd>
<dt><strong>calendarEventId</strong>: <tt>Id</tt></dt>
<dd>The id of the CalendarEvent that this notification is about.</dd>
<dt><strong>isDraft</strong>: <tt>Boolean</tt> (created/updated only)</dt>
<dd>True if the event is a draft.</dd>
<dt><strong>event</strong>: <tt>JSCalendar Event</tt></dt>
<dd>The data before the change (if updated or destroyed), or the data
after creation (if created).</dd>
<dt><strong>eventPatch</strong>: <tt>PatchObject</tt> (updated only)</dt>
<dd>A patch encoding the change between the data in the event property, and the
data after the update.</dd>
</dl>
<t>If the change only affects a single instance of a recurring event, the server MAY set the event and eventPatch properties for just that instance; the calendarEventId MUST still be for the base event.</t>

<section anchor="calendareventnotification-get"><name>CalendarEventNotification/get</name>
<t>This is a standard "/get" method as described in <xref target="RFC8620" section="5.1" />.</t>
</section>

<section anchor="calendareventnotification-changes"><name>CalendarEventNotification/changes</name>
<t>This is a standard "/changes" method as described in <xref target="RFC8620" section="5.2" />.</t>
</section>

<section anchor="calendareventnotification-set"><name>CalendarEventNotification/set</name>
<t>This is a standard "/set" method as described in <xref target="RFC8620" section="5.3" />.</t>
<t>Only destroy is supported; any attempt to create/update MUST be rejected with a
<tt>forbidden</tt> SetError.</t>
</section>

<section anchor="calendareventnotification-query"><name>CalendarEventNotification/query</name>
<t>This is a standard "/query" method as described in <xref target="RFC8620" section="5.5" />.</t>

<section anchor="filtering-1"><name>Filtering</name>
<t>A <strong>FilterCondition</strong> object has the following properties:</t>

<dl spacing="normal" newline="true">
<dt><strong>after</strong>: <tt>UTCDateTime|null</tt></dt>
<dd>The creation date must be on or after this date to match the condition.</dd>
<dt><strong>before</strong>: <tt>UTCDateTime|null</tt></dt>
<dd>The creation date must be before this date to match the condition.</dd>
<dt><strong>type</strong>: <tt>String</tt></dt>
<dd>The "type" property must be the same to match the condition.</dd>
<dt><strong>calendarEventIds</strong>: <tt>Id[]|null</tt></dt>
<dd>A list of event ids. The "calendarEventId" property of the notification must be in this list to match the condition.</dd>
</dl>
</section>

<section anchor="sorting-1"><name>Sorting</name>
<t>The "created" property MUST be supported for sorting.</t>
</section>
</section>

<section anchor="calendareventnotification-querychanges"><name>CalendarEventNotification/queryChanges</name>
<t>This is a standard "/queryChanges" method as described in <xref target="RFC8620" section="5.6" />.</t>
</section>
</section>

<section anchor="examples"><name>Examples</name>
<t>For brevity, in the following examples only the "methodCalls" property of the Request object, and the "methodResponses" property of the Response object is shown.</t>

<section anchor="fetching-initial-data"><name>Fetching initial data</name>
<t>A user has authenticated and the client has fetched the JMAP Session object. It finds a single Account with the <tt>urn:ietf:params:jmap:calendars</tt> capability, with id "a0x9", and wants to display all the calendar information for January 2023 in the Australia/Melbourne time zone. It might make the following request:</t>

<figure>
<name>"methodCalls" Property of a JMAP Request</name>
<sourcecode type="json">
[
  ["Calendar/get", {
    "accountId": "a0x9"
  }, "0"],
  ["ParticipantIdentity/get", {
    "accountId": "a0x9"
  }, "1"],
  ["CalendarEvent/query", {
    "accountId": "a0x9",
    "timeZone": "Australia/Melbourne",
    "filter": {
      "after": "2023-01-01T00:00:00",
      "before": "2023-02-01T00:00:00"
    }
  }, "2"],
  ["CalendarEvent/get", {
    "accountId": "a0x9",
    "#ids": {
      "resultOf": "2",
      "name": "CalendarEvent/query",
      "path": "/ids"
    }
  }, "3"]
]
</sourcecode>
</figure>
<t>The server might respond with something like:</t>

<figure>
<name>"methodResponses" Property of a JMAP Response</name>
<sourcecode type="json">
[
  ["Calendar/get", {
    "accountId": "a0x9",
    "list": [{
      "id": "062adcfa-105d-455c-bc60-6db68b69c3f3",
      "name": "Private",
      "sortOrder": 12,
      "isDefault": false,
      "defaultAlertsWithTime": null,
      ...
    }, {
      "id": "3ddf2ad7-0e0c-4fb5-852d-f0ff56f3c662",
      "name": "Work",
      "sortOrder": 4,
      "isDefault": true,
      "defaultAlertsWithTime": {
        "631BE24C-A3B6-11EC-BF4C-B027680D752E": {
          "@type": "Alert",
          "action": "display",
          "trigger": {
            "@type": "OffsetTrigger",
            "offset": "-PT1H",
            "relativeTo": "start"
          }
        }
      },
      ...
    }],
    "notFound": [],
    "state": "~506"
  }, "0"],
  ["ParticipantIdentity/get", {
    "accountId": "a0x9",
    "list": [{
      "id": "3",
      "name": "Jane Doe",
      "calendarAddress": "mailto:jane@example.com",
      "isDefault": true
    }],
    "notFound": [],
    "state": "lgkf:98144:aae"
  }, "1"],
  ["CalendarEvent/query", {
    "accountId": "a0x9",
    "canCalculateChanges": false,
    "position": 0,
    "queryState": "~206",
    "ids": [
      "E-01c9626e-1490-43df-a34f-457021256281",
      "E-07a2b89d-96b6-4920-982a-54fdf0a386ce",
      ...
    ]
  }, "2"],
  ["CalendarEvent/get", {
    "accountId": "a0x9",
    "list": [{
      "id": "E-01c9626e-1490-43df-a34f-457021256281",
      "calendarIds": {
        "3ddf2ad7-0e0c-4fb5-852d-f0ff56f3c662": true
      },
      "title": "Q1 All hands",
      "start": "2023-01-09T10:00:00",
      "duration": "PT1H",
      "timeZone": "Australia/Sydney",
      ...
    }, ...],
    "notFound": [],
    "state": "$$/413/206"
  }, "3"]
]
</sourcecode>
</figure>
<t>The client now has everything it needs to display that month in full.</t>
</section>

<section anchor="creating-an-event"><name>Creating an event</name>
<t>Suppose the user asks the client to create a new event. The client should default to adding it to the "Work" calendar, as this is the default calendar for the user, unless it has information to make a more informed decision. (e.g. The client may have a feature to automatically choose the calendar based on the time of day, and the user indicates the event is at 7pm, so it knows to default to "Private".)</t>

<figure>
<name>"methodCalls" Property of a JMAP Request</name>
<sourcecode type="json">
[["CalendarEvent/set", {
  "accountId": "a0x9",
  "create": {
    "k559": {
      "uid": "5d5776f6-ff8e-4bfd-ab3e-fe2fe5d4fa91",
      "calendarIds": {
        "3ddf2ad7-0e0c-4fb5-852d-f0ff56f3c662": true
      },
      "title": "Party at Pete’s",
      "start": "2023-02-03T19:00:00",
      "duration": "PT3H0M0S",
      "timeZone": "Australia/Melbourne",
      "showWithoutTime": false,
      "participants": {
        "1": {
          "@type": "Participant",
          "name": "Jane Doe",
          "calendarAddress": "mailto:jane@example.com",
          "kind": "individual",
          "roles": {
            "attendee": true,
            "owner": true
          },
          "participationStatus": "accepted",
          "expectReply": false
        },
        "2": {
          "@type": "Participant",
          "name": "Joe Bloggs",
          "calendarAddress": "mailto:joe@example.com",
          "kind": "individual",
          "roles": {
            "attendee": true
          },
          "participationStatus": "needs-action",
          "expectReply": true
        }
      },
      "mayInviteSelf": false,
      "mayInviteOthers": false,
      "useDefaultAlerts": false,
      "alerts":null
    }
  },
  "sendSchedulingMessages": true
}, "0"]]
</sourcecode>
</figure>
<t>As the event has participants, the server sets an "organizerCalendarAddress" property. This server uses a special email address for receiving iMIP RSVPs (<xref target="RFC5546" />) rather than just receiving them at the owner's regular email address. The response may look something like this:</t>

<figure>
<name>"methodResponses" Property of a JMAP Response. NOTE: '\' line wrapping per <xref target="RFC8792" />.</name>
<sourcecode type="json">
[["CalendarEvent/set", {
  "accountId": "a0x9",
  "created": {
    "k559": {
      "id": "E-5d5776f6-ff8e-4bfd-ab3e-fe2fe5d4fa91",
      "isOrigin": true,
      "@type": "Event",
      "created": "20221005T20:42:13Z",
      "updated": "20221005T20:42:13Z",
      "sequence": 1,
      "organizerCalendarAddress": "mailto:3e87-1b18bb5e6b4@itip.example.com"
    }
  },
  ...
}, "0"]]
</sourcecode>
</figure>
</section>

<section anchor="snoozing-an-alert-1"><name>Snoozing an alert</name>
<t>The client is connected to the event source and receives a push:</t>

<figure>
<name>Object Received via Push Connection</name>
<sourcecode type="json">
{
  "@type": "CalendarAlert",
  "accountId": "a0x9",
  "calendarEventId": "E-7e93e3ee-4e6e-408a-9adc-cbaf1dbd0a3f",
  "uid": "b6f7e27b-5872-4b52-b457-0242541bb01c",
  "recurrenceId": null,
  "alertId": "7519a951-1e6f-4a6c-b08b-20dd2e5a89cd"
}
</sourcecode>
</figure>

<t>Not finding this event in its local cache, the client fetches the information for this event that it needs to show the alert by making the following request:</t>

<figure>
<name>"methodCalls" Property of a JMAP Request</name>
<sourcecode type="json">
[["CalendarEvent/get", {
  "accountId": "a0x9",
  "ids":["E-7e93e3ee-4e6e-408a-9adc-cbaf1dbd0a3f"],
  "properties": ["calendarIds", "title", "start",
    "timeZone", "useDefaultAlerts", "alerts"]
}, "0"]]
</sourcecode>
</figure>

<t>In response it receives:</t>

<figure>
<name>"methodResponses" Property of a JMAP Response</name>
<sourcecode type="json">
[["CalendarEvent/get", {
  "accountId": "a0x9",
  "list": [{
    "id": "E-7e93e3ee-4e6e-408a-9adc-cbaf1dbd0a3f",
    "calendarIds": {
      "3ddf2ad7-0e0c-4fb5-852d-f0ff56f3c662": true
    },
    "title": "Team catchup",
    "start": "2023-02-10T17:00:00",
    "timeZone": "America/New_York",
    "useDefaultAlerts": false,
    "alerts": {
      "7519a951-1e6f-4a6c-b08b-20dd2e5a89cd": {
        "@type": "Alert",
        "action": "display",
        "trigger": {
          "@type": "OffsetTrigger",
          "relativeTo": "start",
          "offset": "-PT1H"
        }
      }
    }
  }],
  "notFound": [],
  "state": "$$/414/208"
}, "0"]]
</sourcecode>
</figure>
<t>The client displays an alert in a platform-appropriate manner. Presuming the user here is in the Australia/Melbourne time zone, this might look something like:</t>

<figure>
  <name>Notification Shown to User for a Calendar Alert</name>
  <artset>
    <artwork type="svg" name="notification.svg">
      <svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="112" width="344" viewBox="0 0 344 112" class="diagram" text-anchor="middle" font-family="monospace" font-size="13px" stroke-linecap="round">
      <path d="M 8,16 L 8,96" fill="none" stroke="black"/>
      <path d="M 336,16 L 336,96" fill="none" stroke="black"/>
      <path d="M 8,16 L 336,16" fill="none" stroke="black"/>
      <path d="M 8,96 L 336,96" fill="none" stroke="black"/>
      <g class="text">
      <text x="116" y="52">Reminder: Team catchup</text>
      <text x="128" y="68">Today at 10am (in 1 hour)</text>
      <text x="284" y="84">[Snooze ▼]</text>
      </g>
      </svg>
    </artwork>
    <artwork type="ascii-art" name="notification.txt">
      <![CDATA[
+----------------------------------------+
|                                        |
|  Reminder: Team catchup                |
|  Today at 10am (in 1 hour)             |
|                             [Snooze\/] |
+----------------------------------------+
      ]]>
    </artwork>
  </artset>
</figure>

<t>The user snoozes the notification for 30 minutes. The client dismisses the current notification and sends an update to the event to the server:</t>

<figure>
<name>"methodCalls" Property of a JMAP Request. NOTE: '\' line wrapping per <xref target="RFC8792" />.</name>
<sourcecode type="json">
[
  ["CalendarEvent/set", {
    "accountId": "a0x9",
    "update": {
      "E-01c9626e-1490-43df-a34f-457021256281": {
        "alerts/7519a951-1e6f-4a6c-b08b-20dd2e5a89cd\
          /acknowledged": "2023-02-10T23:00:29Z",
        "alerts/86b0-318b8291045f": {
          "@type": "Alert",
          "action": "display",
          "trigger": {
            "@type": "AbsoluteTrigger",
            "when": "2023-02-10T23:30:00Z",
            "relatedTo": {
              "7519a951-1e6f-4a6c-b08b-20dd2e5a89cd": {
                "@type": "Relation",
                "relation": {
                  "snooze": true
                }
              }
            }
          }
        }
      }
    }
  }, "0"]
]</sourcecode>
</figure>
<t>Any other connected client will receive a push, sync the change and dismiss any duplicate alert. After the snooze time has elapsed, the new alert will trigger.</t>
</section>

<section anchor="changing-the-default-calendar"><name>Changing the default calendar</name>
<t>The client tries to change the default calendar from "Work" to "Private" (and
makes no other change):</t>

<figure>
<name>"methodCalls" Property of a JMAP Request</name>
<sourcecode type="json">
[["Calendar/set", {
  "accountId": "a0x9",
  "onSuccessSetIsDefault": "062adcfa-105d-455c-bc60-6db68b69c3f3"
}, "0"]]
</sourcecode>
</figure>
<t>The server allows the change, returning the following response:</t>

<figure>
<name>"methodResponses" Property of a JMAP Response</name>
<sourcecode type="json">
[["Calendar/set", {
  "accountId": "a0x9",
  "updated": {
    "062adcfa-105d-455c-bc60-6db68b69c3f3": {
      "isDefault": true
    },
    "3ddf2ad7-0e0c-4fb5-852d-f0ff56f3c662": {
      "isDefault": false
    }
  }
}, "0"]]
</sourcecode>
</figure>
</section>

<section anchor="parsing-an-icalendar-file"><name>Parsing an iCalendar file</name>
<t>The client makes a request to parse the calendar event from a blob id representing an icalendar file:</t>

<figure>
<name>"methodCalls" Property of a JMAP Request</name>
<sourcecode type="json">
[[ "CalendarEvent/parse", {
  "accountId": "a0x9",
  "blobIds": ["Ge682d5d7aad50b3a4f7180a7ed9276476485ea52"]
}, "c1"]]
</sourcecode>
</figure>
<t>The server responds:</t>

<figure>
<name>"methodResponses" Property of a JMAP Response</name>
<sourcecode type="json">
[[ "CalendarEvent/parse", {
  "accountId": "a0x9",
  "parsed": {
    "Ge682d5d7aad50b3a4f7180a7ed9276476485ea52": [{
      "@type": "Event",
      "method": "publish",
      "prodId": "-//IETF//datatracker.ietf.org ical agenda//EN",
      "uid": "ietf-119-16811-jmap",
      "sequence": 2,
      "updated": "2024-02-09T22:49:26Z",
      "start": "2024-03-19T13:00:00",
      "duration": "PT2H",
      "timeZone": "Australia/Brisbane",
      "showWithoutTime": false,
      "title": "jmap - JSON Mail Access Protocol",
      "freeBusyStatus": "busy",
      "descriptionContentType": "text/plain",
      "description": "Session II\n\nRemember to sign the blue sheets!",
      "locations": {
        "eec47e7589ce131d6331b10383f89f91f8d4a4ef": {
          "@type": "Location",
          "name": "P3, Brisbane Convention Centre"
        }
      },
      "status": "confirmed"
    }]
  },
  "notFound": null,
  "notParsable": null
}, "c1"]]
</sourcecode>
</figure>
<t>If the blob id had not been found, the server would have responded:</t>

<figure>
<name>"methodResponses" Property of a JMAP Response</name>
<sourcecode type="json">
[[ "CalendarEvent/parse", {
  "accountId": "a0x9",
  "notFound": ["Ge682d5d7aad50b3a4f7180a7ed9276476485ea52"]
}, "c1" ]]
</sourcecode>
</figure>
<t>If the blob id had been found but was not parsable, the server would have responded:</t>

<figure>
<name>"methodResponses" Property of a JMAP Response</name>
<sourcecode type="json">
[[ "CalendarEvent/parse", {
  "accountId": "a0x9",
  "notParsable": ["Ge682d5d7aad50b3a4f7180a7ed9276476485ea52"]
}, "c1" ]]
</sourcecode>
</figure>
</section>
</section>

<section anchor="security-considerations"><name>Security Considerations</name>
<t>All security considerations of JMAP <xref target="RFC8620"></xref> and JSCalendar <xref target="I-D.ietf-calext-jscalendarbis" /> apply to this specification. Additional considerations specific to the data types and functionality introduced by this document are described in the following subsections.</t>

<section anchor="privacy"><name>Privacy</name>
<t>Calendars often contain the precise movements, activities, and contacts of people; all intensely private data. Privacy leaks can have real world consequences, and calendar servers and clients MUST be mindful of the need to keep all data secure.</t>
<t>Servers MUST enforce the ACLs set on calendars to ensure only authorised data is shared. The additional restrictions specified by the "privacy" property of a JSCalendar Event object (see <xref target="I-D.ietf-calext-jscalendarbis" section="4.4.3" />) MUST also be enforced.</t>
<t>Users may have multiple Participant Identities that they use for areas of their life kept private from one another. Using one identity with an event MUST NOT leak the existence of any other identity. For example, sending an RSVP from identity worklife@example.com MUST NOT reveal anything about another identity present in the account such as privatelife@example.org.</t>
<t>Severs SHOULD enforce that invitations sent to external systems are only transmitted via secure encrypted and signed connections to protect against eavesdropping and modification of data.</t>
</section>

<section anchor="spoofing"><name>Spoofing</name>
<t>When receiving events and updates from external systems, it can be hard to verify that the identity of the author is who they claim to be. When receiving events via email, DKIM <xref target="RFC6376"></xref> and S/MIME <xref target="RFC8551"></xref> are two mechanisms that may be used to verify certain properties about the email data, which can be correlated with the event information.</t>
</section>

<section anchor="denial-of-service"><name>Denial-of-service</name>
<t>There are many ways in which a calendar user can make a request liable to cause a calendar server to spend an inordinate amount of processing time. Care must be taken to limit resources allocated to any one user to ensure the system does not become unresponsive. The following subsections list particularly hazardous areas.</t>

<section anchor="expanding-recurrences"><name>Expanding Recurrences</name>
<t>Recurrence rules can be crafted to occur as frequently as every second. Servers MUST be careful to not allow resources to be exhausted when expanding, and limit the number of expansions they will create. Equally, rules can be generated that never create any occurrences at all. Servers MUST be careful to limit the work spent iterating in search of the next occurrence.</t>
</section>

<section anchor="firing-alerts"><name>Firing alerts</name>
<t>An alert firing for an event can cause a notification to be pushed to the user's devices, or to send them an email. Servers MUST rate limit the number of alerts sent for any one user. The combination of recurring events with multiple alerts can in particular define unreasonably frequent alerts, leading to denial of service for either the server processing them or the user's devices receiving them.</t>
<t>Similarly, clients generating alerts from the data on device must take the same precautions.</t>
<t>The "email" alert type (see <xref target="I-D.ietf-calext-jscalendarbis" section="4.5.1" />) causes an email to be sent when triggered. Clients MUST ignore this alert type; the email is sent only by the calendar server. There is no mechanism in JSCalendar to specify a particular email address: the server MUST only allow alerts to be sent to an address it has verified as belonging to the user to avoid this being used as a spamming vector.</t>
</section>

<section anchor="load-spikes"><name>Load spikes</name>
<t>Since most events are likely to start on the hour mark, a large spike of activity is often seen at these times, with particularly large spikes at certain common times in the time zone of the server's user base. In particular, a large number of alerts (across different users and events) will be triggered at the same time. Servers may mitigate this somewhat by adding jitter to the triggering of the alerts; it is RECOMMENDED to fire them slightly early rather than slightly late if needed to spread load.</t>
</section>
</section>

<section anchor="spam"><name>Spam</name>
<t>Invitations received from an untrusted source may be spam. If this is added to the user's calendar automatically it can be very obtrusive, especially if it is a recurring event that now appears every day. It could also be used to attempt to phish the user, or cause them to physically go to a location included in the event. Incoming invitations to events should be subject to spam scanning, and suspicious events should not be added to the calendar automatically.</t>
<t>Servers should strip any alerts on invitations when adding to the user's calendar; the "useDefaultAlerts" property should be set instead to apply the user's preferences.</t>
<t>Similarly, a malicious user may use a calendar system to send spam by inviting people to an event. Outbound scheduling messages should be subject to all the same controls used on outbound email systems, and rate limited as appropriate. A rate limit on the number of distinct recipients as well as overall messages is recommended.</t>
</section>
</section>

<section anchor="iana-considerations"><name>IANA Considerations</name>

<section anchor="jmap-capability-registration-for-calendars"><name>JMAP Capability Registration for "calendars"</name>
<t>IANA will register the "calendars" JMAP Capability as follows:</t>
<dl newline="false" spacing="compact">
<dt>Capability Name:</dt> <dd><tt>urn:ietf:params:jmap:calendars</tt></dd>
<dt>Specification document:</dt> <dd>this document</dd>
<dt>Intended use:</dt> <dd>common</dd>
<dt>Change Controller:</dt> <dd>IETF</dd>
<dt>Security and privacy considerations:</dt> <dd>this document, <xref target="urn-ietf-params-jmap-calendars" /></dd>
</dl>
</section>

<section anchor="jmap-capability-registration-for-principals-availability"><name>JMAP Capability Registration for "principals:availability"</name>
<t>IANA will register the "principals:availability" JMAP Capability as follows:</t>
<dl newline="false" spacing="compact">
<dt>Capability Name:</dt> <dd><tt>urn:ietf:params:jmap:principals:availability</tt></dd>
<dt>Specification document:</dt> <dd>this document</dd>
<dt>Intended use:</dt> <dd>common</dd>
<dt>Change Controller:</dt> <dd>IETF</dd>
<dt>Security and privacy considerations:</dt> <dd>this document, <xref target="urn-ietf-params-jmap-principals-availability" /></dd>
</dl>
</section>

<section anchor="jmap-data-type-registration-for-calendar"><name>JMAP Data Type Registration for "Calendar"</name>
<t>IANA will register "Calendar" in the "JMAP Data Types" registry as follows:</t>
<dl newline="false" spacing="compact">
<dt>Type Name:</dt> <dd>Calendar</dd>
<dt>Can reference blobs:</dt> <dd>no</dd>
<dt>Can Use for State Change:</dt> <dd>yes</dd>
<dt>Capability:</dt> <dd><tt>urn:ietf:params:jmap:calendars</tt></dd>
<dt>Specification document:</dt> <dd>this document</dd>
</dl>
</section>

<section anchor="jmap-data-type-registration-for-calendarevent"><name>JMAP Data Type Registration for "CalendarEvent"</name>
<t>IANA will register "CalendarEvent" in the "JMAP Data Types" registry as follows:</t>
<dl newline="false" spacing="compact">
<dt>Type Name:</dt> <dd>CalendarEvent</dd>
<dt>Can reference blobs:</dt> <dd>yes</dd>
<dt>Can Use for State Change:</dt> <dd>yes</dd>
<dt>Capability:</dt> <dd><tt>urn:ietf:params:jmap:calendars</tt></dd>
<dt>Specification document:</dt> <dd>this document</dd>
</dl>
</section>

<section anchor="jmap-data-type-registration-for-calendareventnotification"><name>JMAP Data Type Registration for "CalendarEventNotification"</name>
<t>IANA will register "CalendarEventNotification" in the "JMAP Data Types" registry as follows:</t>
<dl newline="false" spacing="compact">
<dt>Type Name:</dt> <dd>CalendarEventNotification</dd>
<dt>Can reference blobs:</dt> <dd>no</dd>
<dt>Can Use for State Change:</dt> <dd>yes</dd>
<dt>Capability:</dt> <dd><tt>urn:ietf:params:jmap:calendars</tt></dd>
<dt>Specification document:</dt> <dd>this document</dd>
</dl>
</section>

<section anchor="jmap-data-type-registration-for-participantidentity"><name>JMAP Data Type Registration for "ParticipantIdentity"</name>
<t>IANA will register "ParticipantIdentity" in the "JMAP Data Types" registry as follows:</t>
<dl newline="false" spacing="compact">
<dt>Type Name:</dt> <dd>ParticipantIdentity</dd>
<dt>Can reference blobs:</dt> <dd>no</dd>
<dt>Can Use for State Change:</dt> <dd>yes</dd>
<dt>Capability:</dt> <dd><tt>urn:ietf:params:jmap:calendars</tt></dd>
<dt>Specification document:</dt> <dd>this document</dd>
</dl>
</section>

<section anchor="jmap-error-codes-registry" title="JMAP Error Codes Registry">
<t>The following subsections register some new error codes in the "JMAP
Error Codes" registry, as defined in <xref target="RFC8620"/>.
</t>

<section anchor="calendarhasevent" title="calendarHasEvent">
<dl newline="false" spacing="compact">
<dt>JMAP Error Code:</dt> <dd>calendarHasEvent</dd>
<dt>Intended use:</dt> <dd>common</dd>
<dt>Change controller:</dt> <dd>IETF</dd>
<dt>Reference:</dt> <dd>This document, <xref target="calendar-set"/></dd>
<dt>Description:</dt> <dd>The Calendar has at least one CalendarEvent assigned to it, and the "onDestroyRemoveEvents" argument was false.</dd>
</dl>
</section>

<section anchor="nosupportedschedulemethods" title="noSupportedScheduleMethods">
<dl newline="false" spacing="compact">
<dt>JMAP Error Code:</dt> <dd>noSupportedScheduleMethods</dd>
<dt>Intended use:</dt> <dd>common</dd>
<dt>Change controller:</dt> <dd>IETF</dd>
<dt>Reference:</dt> <dd>This document, <xref target="calendarevent-set"/></dd>
<dt>Description:</dt> <dd>The server was requested to send scheduling messages, but does not support any of the methods available for at least one of the recipients.</dd>
</dl>
</section>

<section anchor="expanddurationtoolarge" title="expandDurationTooLarge">
<dl newline="false" spacing="compact">
<dt>JMAP Error Code:</dt> <dd>expandDurationTooLarge</dd>
<dt>Intended use:</dt> <dd>common</dd>
<dt>Change controller:</dt> <dd>IETF</dd>
<dt>Reference:</dt> <dd>This document, <xref target="calendarevent-query"/></dd>
<dt>Description:</dt> <dd>The query has expandRecurrences set to true, and the duration between the "before" and "after" properties exceeds the maxExpandedQueryDuration limit.</dd>
</dl>
</section>

<section anchor="cannotcalculateoccurrences" title="cannotCalculateOccurrences">
<dl newline="false" spacing="compact">
<dt>JMAP Error Code:</dt> <dd>cannotCalculateOccurrences</dd>
<dt>Intended use:</dt> <dd>common</dd>
<dt>Change controller:</dt> <dd>IETF</dd>
<dt>Reference:</dt> <dd>This document, <xref target="calendarevent-query"/></dd>
<dt>Description:</dt> <dd>The server cannot expand a recurrence required to return the results for the requested query.</dd>
</dl>
</section>
</section>

<section anchor="update-to-the-jscalendar-properties-registry"><name>Update to the JSCalendar Properties Registry</name>
<t>IANA will update the "JSCalendar Properties" registry, originally created in <xref target="RFC8984" section="8.2" />, to add a new column called "Is Per-User". The value in this column for each entry MUST be either "yes" or "no", indicating whether each user with whom the object is shared should be able to set their own value for this property without affecting the value for other users.</t>

<section anchor="update-to-jscalendar-properties-registry-template"><name>Update to "JSCalendar Properties" Registry Template</name>
<t>An additional field is added to the template:</t>
<t>Is Per-User</t>
</section>

<section anchor="initial-values-for-existing-registrations"><name>Initial values for existing registrations</name>
<t>IANA will set "Is per-user: yes" on the following property registrations:</t>

<ul spacing="compact">
<li>keywords</li>
<li>color</li>
<li>freeBusyStatus</li>
<li>useDefaultAlerts</li>
<li>alerts</li>
</ul>
<t>All other existing registrations will have "Is per-user: no".</t>
</section>
</section>

<section anchor="jscalendar-property-registrations"><name>JSCalendar Property Registrations</name>
<t>IANA will register the following additional properties in the JSCalendar Properties Registry.</t>

<section anchor="id"><name>id</name>
<dl newline="false" spacing="compact">
<dt>Property Name:</dt> <dd>id</dd>
<dt>Property Type:</dt> <dd>Not applicable</dd>
<dt>Property Context:</dt> <dd>Event, Task</dd>
<dt>Intended Use:</dt> <dd>Reserved</dd>
<dt>Is per-user:</dt> <dd>no</dd>
</dl>
</section>

<section anchor="baseeventid"><name>baseEventId</name>
<dl newline="false" spacing="compact">
<dt>Property Name:</dt> <dd>baseEventId</dd>
<dt>Property Type:</dt> <dd>Not applicable</dd>
<dt>Property Context:</dt> <dd>Event, Task</dd>
<dt>Intended Use:</dt> <dd>Reserved</dd>
<dt>Is per-user:</dt> <dd>no</dd>
</dl>
</section>

<section anchor="calendarids"><name>calendarIds</name>
<dl newline="false" spacing="compact">
<dt>Property Name:</dt> <dd>calendarIds</dd>
<dt>Property Type:</dt> <dd>Not applicable</dd>
<dt>Property Context:</dt> <dd>Event, Task</dd>
<dt>Intended Use:</dt> <dd>Reserved</dd>
<dt>Is per-user:</dt> <dd>no</dd>
</dl>
</section>

<section anchor="isdraft"><name>isDraft</name>
<dl newline="false" spacing="compact">
<dt>Property Name:</dt> <dd>isDraft</dd>
<dt>Property Type:</dt> <dd>Not applicable</dd>
<dt>Property Context:</dt> <dd>Event, Task</dd>
<dt>Intended Use:</dt> <dd>Reserved</dd>
<dt>Is per-user:</dt> <dd>no</dd>
</dl>
</section>

<section anchor="isorigin"><name>isOrigin</name>
<dl newline="false" spacing="compact">
<dt>Property Name:</dt> <dd>isOrigin</dd>
<dt>Property Type:</dt> <dd>Not applicable</dd>
<dt>Property Context:</dt> <dd>Event, Task</dd>
<dt>Intended Use:</dt> <dd>Reserved</dd>
<dt>Is per-user:</dt> <dd>no</dd>
</dl>
</section>

<section anchor="utcstart"><name>utcStart</name>
<dl newline="false" spacing="compact">
<dt>Property Name:</dt> <dd>utcStart</dd>
<dt>Property Type:</dt> <dd>Not applicable</dd>
<dt>Property Context:</dt> <dd>Event, Task</dd>
<dt>Intended Use:</dt> <dd>Reserved</dd>
<dt>Is per-user:</dt> <dd>no</dd>
</dl>
</section>

<section anchor="utcend"><name>utcEnd</name>
<dl newline="false" spacing="compact">
<dt>Property Name:</dt> <dd>utcEnd</dd>
<dt>Property Type:</dt> <dd>Not applicable</dd>
<dt>Property Context:</dt> <dd>Event, Task</dd>
<dt>Intended Use:</dt> <dd>Reserved</dd>
<dt>Is per-user:</dt> <dd>no</dd>
</dl>
</section>

<section anchor="usedefaultalerts"><name>useDefaultAlerts</name>
<dl newline="false" spacing="compact">
<dt>Property Name:</dt> <dd>useDefaultAlerts</dd>
<dt>Property Type:</dt> <dd>Not applicable</dd>
<dt>Property Context:</dt> <dd>Event</dd>
<dt>Intended Use:</dt> <dd>Reserved</dd>
<dt>Is per-user:</dt> <dd>yes</dd>
</dl>
</section>

<section anchor="mayinviteself-1"><name>mayInviteSelf</name>
<dl newline="false" spacing="compact">
<dt>Property Name:</dt> <dd>mayInviteSelf</dd>
<dt>Property Type:</dt> <dd><tt>Boolean</tt> (default: false)</dd>
<dt>Property Context:</dt> <dd>Event, Task</dd>
<dt>Reference:</dt> <dd>This document, <xref target="mayinviteself" />.</dd>
<dt>Intended Use:</dt> <dd>Common</dd>
<dt>Is per-user:</dt> <dd>no</dd>
</dl>
</section>

<section anchor="mayinviteothers-1"><name>mayInviteOthers</name>
<dl newline="false" spacing="compact">
<dt>Property Name:</dt> <dd>mayInviteOthers</dd>
<dt>Property Type:</dt> <dd><tt>Boolean</tt> (default: false)</dd>
<dt>Property Context:</dt> <dd>Event, Task</dd>
<dt>Reference:</dt> <dd>This document, <xref target="mayinviteothers" />.</dd>
<dt>Intended Use:</dt> <dd>Common</dd>
<dt>Is per-user:</dt> <dd>no</dd>
</dl>
</section>

<section anchor="hideattendees-1"><name>hideAttendees</name>
<dl newline="false" spacing="compact">
<dt>Property Name:</dt> <dd>hideAttendees</dd>
<dt>Property Type:</dt> <dd><tt>Boolean</tt> (default: false)</dd>
<dt>Property Context:</dt> <dd>Event, Task</dd>
<dt>Reference:</dt> <dd>This document, <xref target="hideattendees" />.</dd>
<dt>Intended Use:</dt> <dd>Common</dd>
<dt>Is per-user:</dt> <dd>no</dd>
</dl>
</section>

<section anchor="schedulesequence-1"><name>scheduleSequence</name>
<dl newline="false" spacing="compact">
<dt>Property Name:</dt> <dd>scheduleSequence</dd>
<dt>Property Type:</dt> <dd>Not applicable</dd>
<dt>Property Context:</dt> <dd>Participant</dd>
<dt>Intended Use:</dt> <dd>Reserved</dd>
<dt>Is per-user:</dt> <dd>no</dd>
</dl>
</section>

<section anchor="scheduleupdated-1"><name>scheduleUpdated</name>
<dl newline="false" spacing="compact">
<dt>Property Name:</dt> <dd>scheduleUpdated</dd>
<dt>Property Type:</dt> <dd>Not applicable</dd>
<dt>Property Context:</dt> <dd>Participant</dd>
<dt>Intended Use:</dt> <dd>Reserved</dd>
<dt>Is per-user:</dt> <dd>no</dd>
</dl>
</section>

<section anchor="blobid"><name>blobId</name>
<dl newline="false" spacing="compact">
<dt>Property Name:</dt> <dd>blobId</dd>
<dt>Property Type:</dt> <dd>Not applicable</dd>
<dt>Property Context:</dt> <dd>Link</dd>
<dt>Intended Use:</dt> <dd>Reserved</dd>
<dt>Is per-user:</dt> <dd>no</dd>
</dl>
</section>
</section>
</section>

</middle>

<back>
<references><name>Normative References</name>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.2119.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.2397.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.3986.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.5545.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.5546.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.6376.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.8174.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.8551.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.8620.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.8792.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.8984.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.9562.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.9670.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml3/reference.I-D.ietf-calext-jscalendarbis.xml"/>
<reference anchor="IANA-TZDB"
 target="https://www.iana.org/time-zones">
 <front>
   <title>Time Zone Database</title>
   <author>
     <organization>IANA</organization>
   </author>
   <date/>
 </front>
</reference>
<reference anchor="COLORS"
 target="https://www.w3.org/TR/css-color-3/">
 <front>
   <title>CSS Color Module Level 3</title>
   <author>
     <organization>W3C</organization>
   </author>
   <date/>
 </front>
</reference>
</references>
<references><name>Informative References</name>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.4791.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.6047.xml"/>
<xi:include href="https://xml2rfc.ietf.org/public/rfc/bibxml/reference.RFC.8607.xml"/>
<reference anchor="WCAG"
 target="https://www.w3.org/TR/WCAG21/">
 <front>
   <title>Web Content Accessibility Guidelines</title>
   <author>
     <organization>W3C</organization>
   </author>
   <date/>
 </front>
</reference>
</references>

</back>

</rfc>
