|
| 1 | +--- |
| 2 | +sidebar_position: 6 |
| 3 | +--- |
| 4 | + |
| 5 | +# Calendar Export API |
| 6 | + |
| 7 | +The Calendar Export API provides endpoints for exporting calendar events as iCalendar (`.ics`) files and managing calendar feed subscriptions. These endpoints are part of the v4 API and are served by the `CalendarExportController`. |
| 8 | + |
| 9 | +## Endpoints Overview |
| 10 | + |
| 11 | +| Endpoint | Method | Auth | Description | |
| 12 | +|----------|--------|------|-------------| |
| 13 | +| `v4/CalendarExport/ExportICalFile` | GET | Bearer token | Download a single event as `.ics` | |
| 14 | +| `v4/CalendarExport/ExportDepartmentICalFeed` | GET | Bearer token | Download full department calendar as `.ics` | |
| 15 | +| `v4/CalendarExport/GetCalendarSubscriptionUrl` | GET | Bearer token | Get or activate the calendar subscription URL | |
| 16 | +| `v4/CalendarExport/RegenerateCalendarSubscriptionUrl` | POST | Bearer token | Regenerate the subscription URL (invalidates old) | |
| 17 | +| `v4/CalendarExport/CalendarFeed/{token}` | GET | Anonymous | Fetch the iCal feed via encrypted token | |
| 18 | + |
| 19 | +## Export Single Event |
| 20 | + |
| 21 | +``` |
| 22 | +GET /api/v4/CalendarExport/ExportICalFile?calendarItemId={id} |
| 23 | +Authorization: Bearer {access_token} |
| 24 | +``` |
| 25 | + |
| 26 | +Returns a single calendar event as a `.ics` file with content type `text/calendar`. |
| 27 | + |
| 28 | +### Parameters |
| 29 | + |
| 30 | +| Parameter | Type | Required | Description | |
| 31 | +|-----------|------|----------|-------------| |
| 32 | +| `calendarItemId` | int | Yes | The ID of the calendar item to export | |
| 33 | + |
| 34 | +### Response |
| 35 | + |
| 36 | +- **Content-Type:** `text/calendar` |
| 37 | +- **Content-Disposition:** attachment with `.ics` filename |
| 38 | +- Body contains a valid iCalendar file with a single `VEVENT` |
| 39 | + |
| 40 | +## Export Department Calendar |
| 41 | + |
| 42 | +``` |
| 43 | +GET /api/v4/CalendarExport/ExportDepartmentICalFeed |
| 44 | +Authorization: Bearer {access_token} |
| 45 | +``` |
| 46 | + |
| 47 | +Returns all calendar events for the authenticated user's department as a single `.ics` file. |
| 48 | + |
| 49 | +### Response |
| 50 | + |
| 51 | +- **Content-Type:** `text/calendar` |
| 52 | +- Body contains a valid iCalendar file with multiple `VEVENT` entries |
| 53 | + |
| 54 | +## Get Calendar Subscription URL |
| 55 | + |
| 56 | +``` |
| 57 | +GET /api/v4/CalendarExport/GetCalendarSubscriptionUrl |
| 58 | +Authorization: Bearer {access_token} |
| 59 | +``` |
| 60 | + |
| 61 | +Returns the user's calendar subscription URL. If the user has not yet activated calendar sync, it is activated automatically and a new subscription URL is generated. |
| 62 | + |
| 63 | +### Response |
| 64 | + |
| 65 | +```json |
| 66 | +{ |
| 67 | + "Data": { |
| 68 | + "SubscriptionUrl": "https://api.resgrid.com/api/v4/CalendarExport/CalendarFeed/{encrypted_token}", |
| 69 | + "WebCalUrl": "webcal://api.resgrid.com/api/v4/CalendarExport/CalendarFeed/{encrypted_token}", |
| 70 | + "IsActive": true |
| 71 | + }, |
| 72 | + "Status": "success" |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +## Regenerate Calendar Subscription URL |
| 77 | + |
| 78 | +``` |
| 79 | +POST /api/v4/CalendarExport/RegenerateCalendarSubscriptionUrl |
| 80 | +Authorization: Bearer {access_token} |
| 81 | +``` |
| 82 | + |
| 83 | +Generates a new sync key, invalidating all previously issued subscription URLs for this user. |
| 84 | + |
| 85 | +### Response |
| 86 | + |
| 87 | +```json |
| 88 | +{ |
| 89 | + "Data": { |
| 90 | + "SubscriptionUrl": "https://api.resgrid.com/api/v4/CalendarExport/CalendarFeed/{new_encrypted_token}", |
| 91 | + "WebCalUrl": "webcal://api.resgrid.com/api/v4/CalendarExport/CalendarFeed/{new_encrypted_token}", |
| 92 | + "IsActive": true |
| 93 | + }, |
| 94 | + "Status": "success" |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +:::warning |
| 99 | +Regenerating the subscription URL immediately invalidates the previous URL. Any external calendar application using the old URL will no longer receive updates. |
| 100 | +::: |
| 101 | + |
| 102 | +## Calendar Feed (Anonymous) |
| 103 | + |
| 104 | +``` |
| 105 | +GET /api/v4/CalendarExport/CalendarFeed/{token} |
| 106 | +``` |
| 107 | + |
| 108 | +This endpoint does **not** require a Bearer token. Authentication is performed via the encrypted token embedded in the URL. This is the endpoint that external calendar applications (Google Calendar, Outlook, Apple Calendar) call to fetch events. |
| 109 | + |
| 110 | +### Parameters |
| 111 | + |
| 112 | +| Parameter | Type | Required | Description | |
| 113 | +|-----------|------|----------|-------------| |
| 114 | +| `token` | string | Yes | URL-safe Base64-encoded encrypted token | |
| 115 | + |
| 116 | +### Response |
| 117 | + |
| 118 | +- **200 OK** — Content-Type `text/calendar` with the full department iCal feed |
| 119 | +- **401 Unauthorized** — Token is invalid, expired, or the sync key has been regenerated |
| 120 | + |
| 121 | +### Token Format |
| 122 | + |
| 123 | +The encrypted token contains `{departmentId}|{userId}|{calendarSyncToken}` encrypted via the system's `IEncryptionService`. The token is made URL-safe using Base64 URL-safe encoding (`+` → `-`, `/` → `_`, trailing `=` trimmed). |
| 124 | + |
| 125 | +On each request, the system: |
| 126 | +1. Decodes and decrypts the token |
| 127 | +2. Extracts the department ID, user ID, and sync GUID |
| 128 | +3. Loads the user profile and validates that `CalendarSyncToken` matches the embedded GUID |
| 129 | +4. If valid, generates and returns the department calendar feed |
| 130 | + |
| 131 | +### Feature Flag |
| 132 | + |
| 133 | +The feed endpoint respects the `CalendarConfig.ICalFeedEnabled` configuration flag. When disabled, the endpoint returns an error response. |
| 134 | + |
| 135 | +## iCal Output Format |
| 136 | + |
| 137 | +All iCal output conforms to **RFC 5545** and is generated using the `Ical.Net` library. |
| 138 | + |
| 139 | +### Event Mapping |
| 140 | + |
| 141 | +| Calendar Item Field | iCal Property | Notes | |
| 142 | +|---------------------|---------------|-------| |
| 143 | +| Title | `SUMMARY` | | |
| 144 | +| Description | `DESCRIPTION` | | |
| 145 | +| Location | `LOCATION` | | |
| 146 | +| Start | `DTSTART` | `VALUE=DATE` format when all-day | |
| 147 | +| End | `DTEND` | `VALUE=DATE` format when all-day; exclusive (day after last event day) | |
| 148 | +| IsAllDay | `DTSTART`/`DTEND` format | Date-only vs. date-time | |
| 149 | +| Reminder | `VALARM` | `TRIGGER` based on `GetMinutesForReminder()` | |
| 150 | +| Attendees | `ATTENDEE` | Only if populated | |
| 151 | + |
| 152 | +### All-Day and Multi-Day Events |
| 153 | + |
| 154 | +- **All-day events** use `VALUE=DATE` format (no time component) per RFC 5545 |
| 155 | +- **Multi-day all-day events** set `DTEND` to one day after the last event day (iCal standard uses exclusive end dates for all-day events) |
| 156 | +- **Timed events** use full date-time format with UTC offset |
| 157 | + |
| 158 | +### Recurrences |
| 159 | + |
| 160 | +Each materialized recurrence instance is emitted as a separate `VEVENT`. No `RRULE` properties are used because Resgrid pre-expands recurrences in the database. |
| 161 | + |
| 162 | +### PRODID |
| 163 | + |
| 164 | +The `PRODID` value is configurable via `CalendarConfig.ICalProductId` (default: `-//Resgrid//Calendar//EN`). |
| 165 | + |
| 166 | +## Calendar Items API Enhancement |
| 167 | + |
| 168 | +The existing `GetAllCalendarItems` v4 API endpoint response now includes an `IsMultiDay` boolean property on each calendar item, indicating whether the event spans multiple days (where `Start.Date != End.Date`). |
| 169 | + |
| 170 | +### Updated Response Fields |
| 171 | + |
| 172 | +| Field | Type | Description | |
| 173 | +|-------|------|-------------| |
| 174 | +| `IsMultiDay` | boolean | `true` if the event spans more than one day | |
| 175 | +| `IsAllDay` | boolean | `true` if the event is an all-day event (existing field) | |
| 176 | + |
| 177 | +## FullCalendar JSON Enhancement |
| 178 | + |
| 179 | +The `GetV2CalendarEntriesForCal` endpoint (used by the web calendar view) now includes: |
| 180 | + |
| 181 | +| Field | Type | Description | |
| 182 | +|-------|------|-------------| |
| 183 | +| `allDay` | boolean | `true` for all-day events; enables FullCalendar banner rendering | |
| 184 | + |
| 185 | +For all-day events, the `end` date is set to one day after the last event day to match FullCalendar's exclusive end-date convention. |
0 commit comments