feat(unified-cal): connection-based unified calendar API with CRUD, freebusy, and list connections (#28387)

* feat(unified-cal): connection-based unified calendar API with CRUD, freebusy, and list connections

- New GET /v2/calendars/connections endpoint returning all calendar connections with connectionId
- Connection-scoped CRUD: GET/POST/PATCH/DELETE /v2/calendars/connections/{connectionId}/events/*
- Connection-scoped free/busy: GET /v2/calendars/connections/{connectionId}/freebusy
- Legacy calendar-type endpoints: GET/POST/DELETE /v2/calendars/{calendar}/events, GET /{calendar}/freebusy
- Backward compat: dual @Patch decorators for singular /event/ (deprecated) and plural /events/
- ConnectedCalendarEntry interface to eliminate inline type annotations
- DRY service layer with shared private helpers (listEventsWithClient, createEventWithClient, etc.)
- Input validation: @IsDefined() on start/end, @IsTimeZone() on timezone fields, cross-field to >= from validation
- All-day event support: Google Calendar date-only events converted to midnight UTC
- New findCredentialByIdAndUserId method in CredentialsRepository for connection-scoped lookups

* style: apply biome formatting to unified calendar API files

* fix: use @IsTimeZone() validator for timeZone field in CreateEventDateTimeWithZone

* fix: add delegation auth support, extract freebusy service layer

- Comment 3: getCalendarClientForUser and getCalendarClientByCredentialId now
  use getAuthorizedCalendarInstance with delegated-auth fallback instead of
  requiring credential.key directly. Added findCredentialWithDelegationByTypeAndUserId
  and expanded findCredentialByIdAndUserId to include delegationCredentialId.

- Comment 5: Extracted freebusy and connections logic from controller into
  UnifiedCalendarsFreebusyService, keeping the controller thin (HTTP-only).
  Moved ConnectedCalendarEntry type and INTEGRATION_TYPE_TO_API mapping into
  the service layer.

- Biome auto-formatting applied to touched files.

* test: add unit and integration tests for unified calendar API

- GoogleCalendarService: 30 tests covering delegation auth, client creation, CRUD
- UnifiedCalendarsFreebusyService: 21 tests covering connections, busy times, filtering
- CalUnifiedCalendarsController: 31 tests covering all endpoints (connection-scoped + legacy)
- Pipe specs: 37 existing tests continue to pass

Total: 98 tests across 5 suites

* fix: address Devin Review feedback - fix JSDoc and validator pattern

- Fix incorrect JSDoc on listEventsForUser (all-day events ARE included, not skipped)
- Fix IsAfterFrom validator to return false instead of throwing BadRequestException
  (preserves standard ValidationPipe error format)

* fix: revert IsAfterFrom to throw BadRequestException per team convention

Cubic AI (confidence 9/10, team feedback): validators should throw
BadRequestException to preserve the API's standard bad-request response
structure, per team convention.

* fix: add calendarId query param to createConnectionEvent for API consistency

All other connection-scoped endpoints accept calendarId; this was the
only one hardcoding 'primary'. Added @ApiQuery decorator and @Query
parameter with ?? 'primary' fallback, plus a test for custom calendarId.

* Update apps/api/v2/src/modules/cal-unified-calendars/controllers/cal-unified-calendars.controller.ts

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* Revert "Update apps/api/v2/src/modules/cal-unified-calendars/controllers/cal-unified-calendars.controller.ts"

This reverts commit e18e4621eff46d8ec49e4d03230783ce50b0c0e4.

* feat: enhance calendar service with connection-specific methods and improve API documentation

* test: complete delegation auth tests, document virtual mocks, fix key leak tests

- Item 3: Add 7 comprehensive delegation auth integration tests covering
  JWT creation params, email cleaning, fallback scenarios, and error handling
- Item 7: Document why virtual mocks are necessary in all test files
  (workspace packages with DB dependencies cannot resolve in Jest)
- Cubic #1: Document getCalendarsForConnection caching and upstream limitation
- Cubic #2+#3: Make credential key leak tests non-vacuous by including
  actual key fields in mocks and verifying they don't leak
- Remove unused BadRequestException import from freebusy service

* fix: add defense-in-depth key stripping in listConnections controller

Controller now destructures only { connectionId, type, email } from each
connection before returning, so credential.key can never leak even if the
service layer has a future regression. Test updated to verify stripping.

* feat: add unified calendar API endpoints for connections and events management

* fix: add try/catch error handling to CRUD helper methods

Wrap Google Calendar API calls in listEventsWithClient, createEventWithClient,
getEventWithClient, updateEventWithClient, and deleteEventWithClient with
try/catch blocks matching the legacy getEventDetails/updateEventDetails pattern.
This ensures proper NestJS exceptions (NotFoundException, BadRequestException)
are returned instead of raw 500 errors when the Google API throws.

* fix: map Google API errors to correct HTTP status codes

Replace blanket NotFoundException/BadRequestException in CRUD catch blocks
with mapGoogleApiError() that inspects the GaxiosError status code and
returns the appropriate NestJS exception (404→NotFoundException,
401/403→UnauthorizedException, 400→BadRequestException, else→500).

* fix: preserve upstream Google API status codes in error mapping

Separate 403 (ForbiddenException) from 401 (UnauthorizedException) and
add 429 rate-limit handling. This ensures permission-denied and throttling
errors are not misreported to API clients.

* fix: distinguish Google quota/rate-limit 403 from permission 403

Check GaxiosError reason field for rateLimitExceeded, userRateLimitExceeded,
and dailyLimitExceeded before mapping 403 to ForbiddenException. Quota
errors are now correctly mapped to 429 (retriable) instead.

* fix: keep dailyLimitExceeded as 403 (non-retriable quota exhaustion)

dailyLimitExceeded is a daily quota cap, not transient throttling.
Only rateLimitExceeded and userRateLimitExceeded are remapped to 429.

* fix: add missing @ApiQuery decorators for calendarId on get/update/delete endpoints

getConnectionEvent, updateConnectionEvent, and deleteConnectionEvent were
missing @ApiQuery({ name: 'calendarId', required: false }) which caused
OpenAPI spec to incorrectly mark calendarId as required.

* ci: retry flaky vitest worker test

* fix: update calendarId query parameter to be optional in OpenAPI specification

* fix: swap dual decorator order so plural /events/ path appears in OpenAPI spec

NestJS Swagger only picks up the first HTTP method decorator. Swapping
the order ensures the preferred plural path (/events/:eventUid) is
generated in the OpenAPI spec, while the deprecated singular path
(/event/:eventUid) still works at runtime.

* fix: split dual decorators into separate methods so both paths appear in OpenAPI spec

NestJS Swagger only picks up the first HTTP method decorator per handler.
Split getCalendarEventDetails and updateCalendarEvent into separate
methods for the singular /event/ (deprecated) and plural /events/ paths,
each delegating to a shared private helper. Both routes now appear in
the generated OpenAPI spec.

* fix: update openapi.json with split dual-decorator paths for GET/PATCH event endpoints

* fix: mapGoogleApiError - coerce string code to number and read errors from response.data

* fix: mapGoogleApiError - guard against NaN from non-numeric error codes

* fix: use read replica for findCredentialWithDelegationByTypeAndUserId query

* refactor: address review comments - UnifiedCalendarService, ParseConnectionIdPipe, thin controller

- Comment 70 (Ryukemeister): Remove 'what' JSDoc from calendars.service.ts
- Comment 71 (Ryukemeister): Use array syntax for dual paths instead of separate methods
- Comments 73-78 (ThyMinimalDev): Create ParseConnectionIdPipe for connectionId validation
- Comments 79-84 (ThyMinimalDev): Create UnifiedCalendarService with strategy pattern
- Comment 85 (ThyMinimalDev): Move getConnections from freebusy to UnifiedCalendarService
- Controller now only handles HTTP concerns, delegates all logic to UnifiedCalendarService
- Updated all test specs to match refactored architecture

* chore: regenerate openapi.json after controller refactor to array syntax paths

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
Sahitya Chandra
2026-03-18 15:15:53 +05:30
committed by GitHub
co-authored by cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
parent fa20f19e54
commit 72acf09ff2
23 changed files with 3531 additions and 104 deletions
+876 -6
View File
@@ -10813,11 +10813,408 @@
"tags": ["Bookings / Guests"]
}
},
"/v2/calendars/{calendar}/event/{eventUid}": {
"/v2/calendars/connections": {
"get": {
"operationId": "CalUnifiedCalendarsController_listConnections",
"summary": "List calendar connections",
"description": "Returns all calendar connections for the authenticated user (Google, Office 365, Apple). Use connectionId in connection-scoped endpoints. Note: Event CRUD (list/create/get/update/delete events) is currently only supported for Google Calendar connections; other types will return 400.",
"parameters": [
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListConnectionsOutput"
}
}
}
}
},
"tags": ["Cal Unified Calendars"]
}
},
"/v2/calendars/connections/{connectionId}/events": {
"get": {
"operationId": "CalUnifiedCalendarsController_listConnectionEvents",
"summary": "List events for a connection",
"description": "List events in a date range for a specific calendar connection. Only supported for Google Calendar connections; other connection types return 400.",
"parameters": [
{
"name": "connectionId",
"required": true,
"in": "path",
"description": "Calendar connection ID from GET /connections",
"schema": {
"type": "string"
}
},
{
"name": "from",
"required": true,
"in": "query",
"description": "Start of the date range (ISO 8601 date or date-time)",
"schema": {
"example": "2026-03-01",
"type": "string"
}
},
{
"name": "to",
"required": true,
"in": "query",
"description": "End of the date range (ISO 8601 date or date-time)",
"schema": {
"example": "2026-03-31",
"type": "string"
}
},
{
"name": "timeZone",
"required": false,
"in": "query",
"description": "IANA time zone for the request (e.g. America/New_York)",
"schema": {
"type": "string"
}
},
{
"name": "calendarId",
"required": false,
"in": "query",
"description": "Calendar ID. Use 'primary' for the user's primary calendar, or the external ID of a connected calendar.",
"schema": {
"default": "primary",
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListUnifiedCalendarEventsOutput"
}
}
}
}
},
"tags": ["Cal Unified Calendars"]
},
"post": {
"operationId": "CalUnifiedCalendarsController_createConnectionEvent",
"summary": "Create event on a connection",
"description": "Create a new event on the specified calendar connection. Only supported for Google Calendar connections; other connection types return 400.",
"parameters": [
{
"name": "connectionId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "calendarId",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateUnifiedCalendarEventInput"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetUnifiedCalendarEventOutput"
}
}
}
}
},
"tags": ["Cal Unified Calendars"]
}
},
"/v2/calendars/connections/{connectionId}/events/{eventId}": {
"get": {
"operationId": "CalUnifiedCalendarsController_getConnectionEvent",
"summary": "Get event for a connection",
"description": "Get a single event by ID for the specified calendar connection. Only supported for Google Calendar connections; other connection types return 400.",
"parameters": [
{
"name": "connectionId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "eventId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "calendarId",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetUnifiedCalendarEventOutput"
}
}
}
}
},
"tags": ["Cal Unified Calendars"]
},
"patch": {
"operationId": "CalUnifiedCalendarsController_updateConnectionEvent",
"summary": "Update event for a connection",
"description": "Update an event on the specified calendar connection. Only supported for Google Calendar connections; other connection types return 400.",
"parameters": [
{
"name": "connectionId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "eventId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "calendarId",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateUnifiedCalendarEventInput"
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetUnifiedCalendarEventOutput"
}
}
}
}
},
"tags": ["Cal Unified Calendars"]
},
"delete": {
"operationId": "CalUnifiedCalendarsController_deleteConnectionEvent",
"summary": "Delete event for a connection",
"description": "Delete/cancel an event on the specified calendar connection. Only supported for Google Calendar connections; other connection types return 400.",
"parameters": [
{
"name": "connectionId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "eventId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "calendarId",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": ""
}
},
"tags": ["Cal Unified Calendars"]
}
},
"/v2/calendars/connections/{connectionId}/freebusy": {
"get": {
"operationId": "CalUnifiedCalendarsController_getConnectionFreeBusy",
"summary": "Get free/busy for a connection",
"description": "Get busy time slots for the specified calendar connection.",
"parameters": [
{
"name": "connectionId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "from",
"required": true,
"in": "query",
"description": "Start of the date range (ISO 8601 date or date-time)",
"schema": {
"example": "2026-03-10",
"type": "string"
}
},
{
"name": "to",
"required": true,
"in": "query",
"description": "End of the date range (ISO 8601 date or date-time)",
"schema": {
"example": "2026-03-10",
"type": "string"
}
},
{
"name": "timeZone",
"required": false,
"in": "query",
"description": "IANA time zone (e.g. America/New_York)",
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetBusyTimesOutput"
}
}
}
}
},
"tags": ["Cal Unified Calendars"]
}
},
"/v2/calendars/{calendar}/events/{eventUid}": {
"get": {
"operationId": "CalUnifiedCalendarsController_getCalendarEventDetails",
"summary": "Get meeting details from calendar",
"description": "Returns detailed information about a meeting including attendance metrics",
"description": "Returns detailed information about a meeting including attendance metrics. The singular /event/ path is deprecated — use /events/ (plural) instead. For connection-scoped access use GET /connections/{connectionId}/events/{eventId}.",
"parameters": [
{
"name": "calendar",
@@ -10860,13 +11257,11 @@
}
},
"tags": ["Cal Unified Calendars"]
}
},
"/v2/calendars/{calendar}/events/{eventUid}": {
},
"patch": {
"operationId": "CalUnifiedCalendarsController_updateCalendarEvent",
"summary": "Update meeting details in calendar",
"description": "Updates event information in the specified calendar provider",
"description": "Updates event information in the specified calendar provider. The singular /event/ path is deprecated — use /events/ (plural) instead. For connection-scoped access use PATCH /connections/{connectionId}/events/{eventId}.",
"parameters": [
{
"name": "calendar",
@@ -10919,6 +11314,348 @@
}
},
"tags": ["Cal Unified Calendars"]
},
"delete": {
"operationId": "CalUnifiedCalendarsController_deleteCalendarEvent",
"summary": "Delete a calendar event",
"description": "Delete/cancel an event on the authenticated user's calendar. Currently only Google Calendar is supported.",
"parameters": [
{
"name": "calendar",
"required": true,
"in": "path",
"schema": {
"enum": ["google", "office365", "apple"],
"type": "string"
}
},
{
"name": "eventUid",
"required": true,
"in": "path",
"description": "The calendar provider's event ID (e.g. Google Calendar event ID)",
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": ""
}
},
"tags": ["Cal Unified Calendars"]
}
},
"/v2/calendars/{calendar}/event/{eventUid}": {
"get": {
"operationId": "CalUnifiedCalendarsController_getCalendarEventDetails",
"summary": "Get meeting details from calendar",
"description": "Returns detailed information about a meeting including attendance metrics. The singular /event/ path is deprecated — use /events/ (plural) instead. For connection-scoped access use GET /connections/{connectionId}/events/{eventId}.",
"parameters": [
{
"name": "calendar",
"required": true,
"in": "path",
"schema": {
"enum": ["google"],
"type": "string"
}
},
{
"name": "eventUid",
"required": true,
"in": "path",
"description": "The Google Calendar event ID. You can retrieve this by getting booking references from the following endpoints:\n\n- For team events: https://cal.com/docs/api-reference/v2/orgs-teams-bookings/get-booking-references-for-a-booking\n\n- For user events: https://cal.com/docs/api-reference/v2/bookings/get-booking-references-for-a-booking",
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetUnifiedCalendarEventOutput"
}
}
}
}
},
"tags": ["Cal Unified Calendars"]
},
"patch": {
"operationId": "CalUnifiedCalendarsController_updateCalendarEvent",
"summary": "Update meeting details in calendar",
"description": "Updates event information in the specified calendar provider. The singular /event/ path is deprecated — use /events/ (plural) instead. For connection-scoped access use PATCH /connections/{connectionId}/events/{eventId}.",
"parameters": [
{
"name": "calendar",
"required": true,
"in": "path",
"schema": {
"enum": ["google"],
"type": "string"
}
},
{
"name": "eventUid",
"required": true,
"in": "path",
"description": "The Google Calendar event ID. You can retrieve this by getting booking references from the following endpoints:\n\n- For team events: https://cal.com/docs/api-reference/v2/orgs-teams-bookings/get-booking-references-for-a-booking\n\n- For user events: https://cal.com/docs/api-reference/v2/bookings/get-booking-references-for-a-booking",
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateUnifiedCalendarEventInput"
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetUnifiedCalendarEventOutput"
}
}
}
}
},
"tags": ["Cal Unified Calendars"]
}
},
"/v2/calendars/{calendar}/events": {
"get": {
"operationId": "CalUnifiedCalendarsController_listCalendarEvents",
"summary": "List calendar events",
"description": "List events in a date range for the authenticated user's calendar. Currently only Google Calendar is supported.",
"parameters": [
{
"name": "calendar",
"required": true,
"in": "path",
"schema": {
"enum": ["google", "office365", "apple"],
"type": "string"
}
},
{
"name": "from",
"required": true,
"in": "query",
"description": "Start of the date range (ISO 8601 date or date-time)",
"schema": {
"example": "2026-03-01",
"type": "string"
}
},
{
"name": "to",
"required": true,
"in": "query",
"description": "End of the date range (ISO 8601 date or date-time)",
"schema": {
"example": "2026-03-31",
"type": "string"
}
},
{
"name": "timeZone",
"required": false,
"in": "query",
"description": "IANA time zone for the request (e.g. America/New_York)",
"schema": {
"type": "string"
}
},
{
"name": "calendarId",
"required": false,
"in": "query",
"description": "Calendar ID. Use 'primary' for the user's primary calendar, or the external ID of a connected calendar.",
"schema": {
"default": "primary",
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListUnifiedCalendarEventsOutput"
}
}
}
}
},
"tags": ["Cal Unified Calendars"]
},
"post": {
"operationId": "CalUnifiedCalendarsController_createCalendarEvent",
"summary": "Create a calendar event",
"description": "Create a new event on the authenticated user's calendar. Currently only Google Calendar is supported.",
"parameters": [
{
"name": "calendar",
"required": true,
"in": "path",
"schema": {
"enum": ["google", "office365", "apple"],
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateUnifiedCalendarEventInput"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetUnifiedCalendarEventOutput"
}
}
}
}
},
"tags": ["Cal Unified Calendars"]
}
},
"/v2/calendars/{calendar}/freebusy": {
"get": {
"operationId": "CalUnifiedCalendarsController_getFreeBusy",
"summary": "Get free/busy times",
"description": "Get busy time slots for the authenticated user's selected calendars in the given date range. Currently only Google Calendar is supported.",
"parameters": [
{
"name": "calendar",
"required": true,
"in": "path",
"schema": {
"enum": ["google", "office365", "apple"],
"type": "string"
}
},
{
"name": "from",
"required": true,
"in": "query",
"description": "Start of the date range (ISO 8601 date or date-time)",
"schema": {
"example": "2026-03-10",
"type": "string"
}
},
{
"name": "to",
"required": true,
"in": "query",
"description": "End of the date range (ISO 8601 date or date-time)",
"schema": {
"example": "2026-03-10",
"type": "string"
}
},
{
"name": "timeZone",
"required": false,
"in": "query",
"description": "IANA time zone (e.g. America/New_York)",
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_ or managed user access token",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetBusyTimesOutput"
}
}
}
}
},
"tags": ["Cal Unified Calendars"]
}
},
"/v2/calendars/ics-feed/save": {
@@ -33440,6 +34177,55 @@
},
"required": ["status", "data"]
},
"CalendarConnectionItem": {
"type": "object",
"properties": {
"connectionId": {
"type": "string",
"description": "Stable ID for this calendar connection (use in connection-scoped endpoints)",
"example": "123"
},
"type": {
"type": "string",
"enum": ["google", "office365", "apple"],
"description": "Calendar provider type",
"example": "google"
},
"email": {
"type": "string",
"nullable": true,
"description": "Primary email for this connection (null if unavailable)",
"example": "user@gmail.com"
}
},
"required": ["connectionId", "type"]
},
"ListConnectionsData": {
"type": "object",
"properties": {
"connections": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CalendarConnectionItem"
}
}
},
"required": ["connections"]
},
"ListConnectionsOutput": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": ["success", "error"]
},
"data": {
"$ref": "#/components/schemas/ListConnectionsData"
}
},
"required": ["status", "data"]
},
"CalendarEventVideoLocation": {
"type": "object",
"properties": {
@@ -33756,6 +34542,90 @@
},
"required": ["start", "end", "id", "title", "source"]
},
"ListUnifiedCalendarEventsOutput": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": ["success", "error"]
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/UnifiedCalendarEventOutput"
}
}
},
"required": ["status", "data"]
},
"CreateEventDateTimeWithZone": {
"type": "object",
"properties": {
"time": {
"type": "string",
"format": "date-time",
"description": "Start or end time in ISO 8601 format"
},
"timeZone": {
"type": "string",
"description": "IANA time zone (e.g. America/New_York)"
}
},
"required": ["time", "timeZone"]
},
"CreateEventAttendee": {
"type": "object",
"properties": {
"email": {
"type": "string",
"description": "Email address of the attendee"
},
"name": {
"type": "string",
"description": "Display name of the attendee"
}
},
"required": ["email"]
},
"CreateUnifiedCalendarEventInput": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Title of the calendar event"
},
"start": {
"description": "Start date and time with time zone",
"allOf": [
{
"$ref": "#/components/schemas/CreateEventDateTimeWithZone"
}
]
},
"end": {
"description": "End date and time with time zone",
"allOf": [
{
"$ref": "#/components/schemas/CreateEventDateTimeWithZone"
}
]
},
"description": {
"type": "string",
"nullable": true,
"description": "Description of the event"
},
"attendees": {
"description": "List of attendees",
"type": "array",
"items": {
"$ref": "#/components/schemas/CreateEventAttendee"
}
}
},
"required": ["title", "start", "end"]
},
"GetUnifiedCalendarEventOutput": {
"type": "object",
"properties": {