diff --git a/.changeset/calendar-client-expanded-api.md b/.changeset/calendar-client-expanded-api.md new file mode 100644 index 00000000..19b731a9 --- /dev/null +++ b/.changeset/calendar-client-expanded-api.md @@ -0,0 +1,5 @@ +--- +"@epilot/calendar-client": minor +--- + +Update the Calendar API definitions with calendar and event mutations, Outlook calendar integration, event sharing, and absence management operations. diff --git a/clients/calendar-client/src/openapi-runtime.json b/clients/calendar-client/src/openapi-runtime.json index 43320553..70294fca 100644 --- a/clients/calendar-client/src/openapi-runtime.json +++ b/clients/calendar-client/src/openapi-runtime.json @@ -10,6 +10,187 @@ } ], "paths": { + "/v1/calendar/absence/users": { + "get": { + "operationId": "listUsersAbsence", + "parameters": [ + { + "in": "query", + "name": "from", + "required": true + }, + { + "in": "query", + "name": "to", + "required": true + }, + { + "in": "query", + "name": "include_busy" + }, + { + "in": "query", + "name": "query" + }, + { + "in": "query", + "name": "limit" + }, + { + "in": "query", + "name": "offset" + } + ], + "responses": {} + } + }, + "/v1/calendar/absence:search": { + "post": { + "operationId": "searchAbsence", + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, + "/v1/calendar/absence:search-now": { + "post": { + "operationId": "searchNowAbsence", + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, + "/v1/calendar/absence/users/{user_id}/adjustments": { + "get": { + "operationId": "listAbsenceAdjustments", + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": true + }, + { + "in": "query", + "name": "from", + "required": true + }, + { + "in": "query", + "name": "to", + "required": true + } + ], + "responses": {} + }, + "post": { + "operationId": "createAbsenceAdjustment", + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, + "/v1/calendar/absence/users/{user_id}/adjustments/{adjustment_id}": { + "get": { + "operationId": "getAbsenceAdjustment", + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": true + }, + { + "in": "path", + "name": "adjustment_id", + "required": true + } + ], + "responses": {} + }, + "patch": { + "operationId": "patchAbsenceAdjustment", + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": true + }, + { + "in": "path", + "name": "adjustment_id", + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + }, + "delete": { + "operationId": "deleteAbsenceAdjustment", + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": true + }, + { + "in": "path", + "name": "adjustment_id", + "required": true + } + ], + "responses": {} + } + }, + "/v1/calendar/absence/users/{user_id}": { + "get": { + "operationId": "getUserAbsence", + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": true + }, + { + "in": "query", + "name": "from", + "required": true + }, + { + "in": "query", + "name": "to", + "required": true + }, + { + "in": "query", + "name": "include_busy" + } + ], + "responses": {} + } + }, "/v1/calendar": { "get": { "operationId": "listCalendars", @@ -24,6 +205,59 @@ } ], "responses": {} + }, + "post": { + "operationId": "createCalendar", + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, + "/v1/calendar/sources/outlook": { + "post": { + "operationId": "addOutlookCalendar", + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, + "/v1/calendar/sources/outlook/available": { + "get": { + "operationId": "listOutlookCalendars", + "responses": {} + } + }, + "/v1/calendar/sources/outlook/{calendar_id}": { + "delete": { + "operationId": "deleteOutlookCalendar", + "parameters": [ + { + "in": "path", + "name": "calendar_id", + "required": true + } + ], + "responses": {} + } + }, + "/v1/calendar/outlook/webhook": { + "post": { + "operationId": "outlookWebhook", + "parameters": [ + { + "in": "query", + "name": "validationToken" + } + ], + "responses": {} } }, "/v1/calendar/{calendar_id}": { @@ -37,6 +271,34 @@ } ], "responses": {} + }, + "patch": { + "operationId": "updateCalendar", + "parameters": [ + { + "in": "path", + "name": "calendar_id", + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + }, + "delete": { + "operationId": "deleteCalendar", + "parameters": [ + { + "in": "path", + "name": "calendar_id", + "required": true + } + ], + "responses": {} } }, "/v1/calendar/events": { @@ -67,6 +329,16 @@ } ], "responses": {} + }, + "post": { + "operationId": "createEvent", + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} } }, "/v1/calendar/events/{event_id}": { @@ -80,6 +352,71 @@ } ], "responses": {} + }, + "patch": { + "operationId": "updateEvent", + "parameters": [ + { + "in": "path", + "name": "event_id", + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + }, + "delete": { + "operationId": "deleteEvent", + "parameters": [ + { + "in": "path", + "name": "event_id", + "required": true + } + ], + "responses": {} + } + }, + "/v1/calendar/events/{event_id}/share": { + "post": { + "operationId": "shareEvent", + "parameters": [ + { + "in": "path", + "name": "event_id", + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, + "/v1/calendar/events/{event_id}/share/{user_id}": { + "delete": { + "operationId": "unshareEvent", + "parameters": [ + { + "in": "path", + "name": "event_id", + "required": true + }, + { + "in": "path", + "name": "user_id", + "required": true + } + ], + "responses": {} } } }, diff --git a/clients/calendar-client/src/openapi.d.ts b/clients/calendar-client/src/openapi.d.ts index 4c3ae44d..a7d68071 100644 --- a/clients/calendar-client/src/openapi.d.ts +++ b/clients/calendar-client/src/openapi.d.ts @@ -8,12 +8,96 @@ import type { declare namespace Components { namespace Schemas { + export interface AbsenceAdjustment { + /** + * Adjustment start. Maximum adjustment duration: 7 days. + */ + from: string; // date-time + /** + * Adjustment end. Must be after from. + */ + to: string; // date-time + /** + * Manual override state: true means absent, false means not absent. + */ + absent: boolean; + status: /* Calendar status targeted by the absence adjustment. */ AbsenceStatus; + type?: /* Optional producer-defined reference stored with an absence adjustment. */ AbsenceType; + reason?: string | null; + adjustment_id: string; + user_id: string; + created_by: string; + created_at: string; // date-time + updated_at: string; // date-time + } + export interface AbsenceInterval { + /** + * Absence interval start clipped to the requested window. + */ + from: string; // date-time + /** + * Absence interval end clipped to the requested window. + */ + to: string; // date-time + /** + * Original absence interval start before clipping. + */ + original_from: string; // date-time + /** + * Original absence interval end before clipping. + */ + original_to: string; // date-time + /** + * Effective absence state for this interval. + */ + absent: boolean; + source: AbsenceIntervalSource; + /** + * calendar_event ID for calendar-derived intervals. + */ + calendar_event_id?: string; + /** + * absence adjustment ID for manual intervals. + */ + absence_adjustment_id?: string; + reason?: string | null; + } + export type AbsenceIntervalSource = "calendar_event" | "absence_adjustment"; + /** + * Calendar status targeted by the absence adjustment. + */ + export type AbsenceStatus = "busy" | "oof"; + /** + * Optional producer-defined reference stored with an absence adjustment. + */ + export type AbsenceType = string; + export interface AddOutlookCalendarRequest { + provider_calendar_id: string; + /** + * Optional override; defaults to the provider calendar name. + */ + name?: string; + } export interface Attendee { email: string; // email name?: string | null; response: /* Caller's response to the invite */ ResponseStatus; type: "required" | "optional" | "resource"; } + export interface AvailableOutlookCalendar { + /** + * Microsoft Graph calendar id (immutable, e.g. base64-encoded) + */ + provider_calendar_id: string; + name: string; + color?: string | null; + is_default?: boolean; + can_edit?: boolean; + /** + * Owner email address on the Microsoft side + */ + owner?: string | null; + } export interface Calendar { /** * epilot calendar ID @@ -47,6 +131,14 @@ declare namespace Components { _created_at?: string; // date-time _updated_at?: string; // date-time } + export interface CalendarCreateBody { + name: string; + description?: string | null; + /** + * Hex color (e.g. "#3b82f6") or provider-defined named color + */ + color?: string | null; + } export interface CalendarEvent { /** * epilot event ID @@ -117,6 +209,64 @@ declare namespace Components { _created_at?: string; // date-time _updated_at?: string; // date-time } + export interface CalendarEventCreateBody { + /** + * epilot calendar this event belongs to + */ + calendar_id: string; + /** + * Preview of the event body, truncated to 255 chars + */ + description?: string | null; + /** + * Event start in UTC + */ + start_time: string; // date-time + /** + * Event end in UTC + */ + end_time: string; // date-time + /** + * IANA timezone of the original event (for display) + */ + timezone: string; + is_all_day: boolean; + location?: string | null; + status: /* Free/busy state derived from provider `showAs` */ EventStatus; + sensitivity: Sensitivity; + _title: string; + } + export interface CalendarEventPatchBody { + /** + * Preview of the event body, truncated to 255 chars + */ + description?: string | null; + /** + * Event start in UTC + */ + start_time?: string; // date-time + /** + * Event end in UTC + */ + end_time?: string; // date-time + /** + * IANA timezone of the original event (for display) + */ + timezone?: string; + is_all_day?: boolean; + location?: string | null; + status?: /* Free/busy state derived from provider `showAs` */ EventStatus; + sensitivity?: Sensitivity; + _title?: string; + } + export interface CalendarPatchBody { + name?: string; + description?: string | null; + /** + * Hex color (e.g. "#3b82f6") or provider-defined named color + */ + color?: string | null; + } export interface CalendarSource { /** * `native` = hosted by epilot. `synced` = mirrored from an external provider. @@ -126,9 +276,26 @@ declare namespace Components { provider_calendar_id?: string | null; last_synced_at?: string | null; // date-time } + export interface CreateAbsenceAdjustmentBody { + /** + * Adjustment start. Maximum adjustment duration: 7 days. + */ + from: string; // date-time + /** + * Adjustment end. Must be after from. + */ + to: string; // date-time + /** + * Manual override state: true means absent, false means not absent. + */ + absent: boolean; + status: /* Calendar status targeted by the absence adjustment. */ AbsenceStatus; + type?: /* Optional producer-defined reference stored with an absence adjustment. */ AbsenceType; + reason?: string | null; + } export interface Error { - message: string; - code?: string; + status: number; + error: string | any[]; } export interface EventSource { /** @@ -152,7 +319,34 @@ declare namespace Components { */ export type EventStatus = "free" | "tentative" | "busy" | "oof" | "workingElsewhere" | "unknown"; export type EventType = "singleInstance" | "occurrence" | "exception" | "seriesMaster"; + export interface ExternalCalendar { + /** + * External calendar provider. + */ + provider: "outlook" | "google"; + /** + * Last successful sync of this provider calendar, if any. + */ + last_synced_at: string | null; // date-time + } export type Importance = "low" | "normal" | "high"; + export interface PatchAbsenceAdjustmentBody { + /** + * Adjustment start. Maximum adjustment duration: 7 days. + */ + from?: string; // date-time + /** + * Adjustment end. Must be after from. + */ + to?: string; // date-time + /** + * Manual override state: true means absent, false means not absent. + */ + absent?: boolean; + status?: /* Calendar status targeted by the absence adjustment. */ AbsenceStatus; + type?: /* Optional producer-defined reference stored with an absence adjustment. */ AbsenceType; + reason?: string | null; + } /** * Null for native epilot calendars */ @@ -161,10 +355,754 @@ declare namespace Components { * Caller's response to the invite */ export type ResponseStatus = "none" | "organizer" | "tentativelyAccepted" | "accepted" | "declined" | "notResponded"; + export interface SearchAbsenceBody { + /** + * Start of the time window (inclusive). Maximum window: 31 days. + */ + from: string; // date-time + /** + * End of the time window (exclusive). Must be after from. + */ + to: string; // date-time + /** + * Candidate users to check for absence inside the caller organization. + */ + user_ids: [ + string, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string? + ]; + /** + * Include busy inputs in addition to out-of-office absence. Defaults to false. + */ + include_busy?: boolean; + } + export interface SearchNowAbsenceBody { + /** + * Candidate users to check for absence at the current server time inside the caller organization. + */ + user_ids: [ + string, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string?, + string? + ]; + /** + * Include busy inputs in addition to out-of-office absence. Defaults to false. + */ + include_busy?: boolean; + } export type Sensitivity = "normal" | "personal" | "private" | "confidential"; + export interface ShareEventBody { + /** + * epilot user id (same organization) to grant view-only access to this event + */ + user_id: string; + } } } declare namespace Paths { + namespace AddOutlookCalendar { + export type RequestBody = Components.Schemas.AddOutlookCalendarRequest; + namespace Responses { + export type $201 = Components.Schemas.Calendar; + export type $400 = Components.Schemas.Error; + export type $404 = Components.Schemas.Error; + } + } + namespace CreateAbsenceAdjustment { + namespace Parameters { + export type UserId = string; + } + export interface PathParameters { + user_id: Parameters.UserId; + } + export type RequestBody = Components.Schemas.CreateAbsenceAdjustmentBody; + namespace Responses { + export type $201 = Components.Schemas.AbsenceAdjustment; + export type $400 = Components.Schemas.Error; + } + } + namespace CreateCalendar { + export type RequestBody = Components.Schemas.CalendarCreateBody; + namespace Responses { + export type $201 = Components.Schemas.Calendar; + export type $400 = Components.Schemas.Error; + } + } + namespace CreateEvent { + export type RequestBody = Components.Schemas.CalendarEventCreateBody; + namespace Responses { + export type $201 = Components.Schemas.CalendarEvent; + export type $400 = Components.Schemas.Error; + export type $403 = Components.Schemas.Error; + export type $404 = Components.Schemas.Error; + } + } + namespace DeleteAbsenceAdjustment { + namespace Parameters { + export type AdjustmentId = string; + export type UserId = string; + } + export interface PathParameters { + user_id: Parameters.UserId; + adjustment_id: Parameters.AdjustmentId; + } + namespace Responses { + export interface $204 { + } + export type $404 = Components.Schemas.Error; + } + } + namespace DeleteCalendar { + namespace Parameters { + export type CalendarId = string; + } + export interface PathParameters { + calendar_id: Parameters.CalendarId; + } + namespace Responses { + export interface $204 { + } + export type $403 = Components.Schemas.Error; + export type $404 = Components.Schemas.Error; + } + } + namespace DeleteEvent { + namespace Parameters { + export type EventId = string; + } + export interface PathParameters { + event_id: Parameters.EventId; + } + namespace Responses { + export interface $204 { + } + export type $403 = Components.Schemas.Error; + export type $404 = Components.Schemas.Error; + } + } + namespace DeleteOutlookCalendar { + namespace Parameters { + export type CalendarId = string; + } + export interface PathParameters { + calendar_id: Parameters.CalendarId; + } + namespace Responses { + export interface $204 { + } + export type $404 = Components.Schemas.Error; + export type $502 = Components.Schemas.Error; + } + } + namespace GetAbsenceAdjustment { + namespace Parameters { + export type AdjustmentId = string; + export type UserId = string; + } + export interface PathParameters { + user_id: Parameters.UserId; + adjustment_id: Parameters.AdjustmentId; + } + namespace Responses { + export type $200 = Components.Schemas.AbsenceAdjustment; + export type $404 = Components.Schemas.Error; + } + } namespace GetCalendar { namespace Parameters { export type CalendarId = string; @@ -189,6 +1127,73 @@ declare namespace Paths { export type $404 = Components.Schemas.Error; } } + namespace GetUserAbsence { + namespace Parameters { + /** + * Start of the time window (inclusive). Maximum window: 31 days. + */ + export type From = string; // date-time + /** + * Include busy inputs in addition to out-of-office absence. Defaults to false. + */ + export type IncludeBusy = boolean; + /** + * End of the time window (exclusive). Must be after from. + */ + export type To = string; // date-time + export type UserId = string; + } + export interface PathParameters { + user_id: Parameters.UserId; + } + export interface QueryParameters { + from: /* Start of the time window (inclusive). Maximum window: 31 days. */ Parameters.From /* date-time */; + to: /* End of the time window (exclusive). Must be after from. */ Parameters.To /* date-time */; + include_busy?: /* Include busy inputs in addition to out-of-office absence. Defaults to false. */ Parameters.IncludeBusy; + } + namespace Responses { + export interface $200 { + absent: boolean; + absence_intervals: Components.Schemas.AbsenceInterval[]; + from: string; // date-time + to: string; // date-time + user_id: string; + /** + * The user's connected external calendars (Outlook/Google), if any. + */ + external_calendars?: Components.Schemas.ExternalCalendar[]; + } + export type $400 = Components.Schemas.Error; + } + } + namespace ListAbsenceAdjustments { + namespace Parameters { + /** + * Start of the time window (inclusive). Maximum window: 90 days. + */ + export type From = string; // date-time + /** + * End of the time window (exclusive). Must be after from. + */ + export type To = string; // date-time + export type UserId = string; + } + export interface PathParameters { + user_id: Parameters.UserId; + } + export interface QueryParameters { + from: /* Start of the time window (inclusive). Maximum window: 90 days. */ Parameters.From /* date-time */; + to: /* End of the time window (exclusive). Must be after from. */ Parameters.To /* date-time */; + } + namespace Responses { + export interface $200 { + from: string; // date-time + to: string; // date-time + results: Components.Schemas.AbsenceAdjustment[]; + } + export type $400 = Components.Schemas.Error; + } + } namespace ListCalendars { namespace Parameters { /** @@ -240,30 +1245,399 @@ declare namespace Paths { export type $400 = Components.Schemas.Error; } } -} - - -export interface OperationMethods { - /** - * listCalendars - listCalendars - * - * List calendars visible to the caller. - */ - 'listCalendars'( - parameters?: Parameters | null, - data?: any, - config?: AxiosRequestConfig - ): OperationResponse - /** - * getCalendar - getCalendar - * - * Get a single calendar by its epilot ID. - */ - 'getCalendar'( + namespace ListOutlookCalendars { + namespace Responses { + export interface $200 { + calendars: Components.Schemas.AvailableOutlookCalendar[]; + } + export type $404 = Components.Schemas.Error; + } + } + namespace ListUsersAbsence { + namespace Parameters { + /** + * Start of the time window (inclusive). Maximum window: 31 days. + */ + export type From = string; // date-time + /** + * Include busy inputs in addition to out-of-office absence. Defaults to false. + */ + export type IncludeBusy = boolean; + /** + * Maximum users to return. + */ + export type Limit = number; + /** + * User directory offset. + */ + export type Offset = number; + /** + * Optional user directory search query. + */ + export type Query = string; + /** + * End of the time window (exclusive). Must be after from. + */ + export type To = string; // date-time + } + export interface QueryParameters { + from: /* Start of the time window (inclusive). Maximum window: 31 days. */ Parameters.From /* date-time */; + to: /* End of the time window (exclusive). Must be after from. */ Parameters.To /* date-time */; + include_busy?: /* Include busy inputs in addition to out-of-office absence. Defaults to false. */ Parameters.IncludeBusy; + query?: /* Optional user directory search query. */ Parameters.Query; + limit?: /* Maximum users to return. */ Parameters.Limit; + offset?: /* User directory offset. */ Parameters.Offset; + } + namespace Responses { + export interface $200 { + from: string; // date-time + to: string; // date-time + hits: number; + results: { + absent: boolean; + absence_intervals: Components.Schemas.AbsenceInterval[]; + user_id: string; + display_name?: string | null; + email?: string | null; // email + status?: string | null; + }[]; + } + export type $400 = Components.Schemas.Error; + } + } + namespace OutlookWebhook { + namespace Parameters { + /** + * Set by Graph on the subscription-validation handshake; echoed back verbatim. + */ + export type ValidationToken = string; + } + export interface QueryParameters { + validationToken?: /* Set by Graph on the subscription-validation handshake; echoed back verbatim. */ Parameters.ValidationToken; + } + namespace Responses { + export type $200 = string; + export interface $202 { + } + } + } + namespace PatchAbsenceAdjustment { + namespace Parameters { + export type AdjustmentId = string; + export type UserId = string; + } + export interface PathParameters { + user_id: Parameters.UserId; + adjustment_id: Parameters.AdjustmentId; + } + export type RequestBody = Components.Schemas.PatchAbsenceAdjustmentBody; + namespace Responses { + export type $200 = Components.Schemas.AbsenceAdjustment; + export type $400 = Components.Schemas.Error; + export type $404 = Components.Schemas.Error; + } + } + namespace SearchAbsence { + export type RequestBody = Components.Schemas.SearchAbsenceBody; + namespace Responses { + export interface $200 { + from: string; // date-time + to: string; // date-time + users: { + [name: string]: { + absent: boolean; + absence_intervals: Components.Schemas.AbsenceInterval[]; + }; + }; + } + export type $400 = Components.Schemas.Error; + } + } + namespace SearchNowAbsence { + export type RequestBody = Components.Schemas.SearchNowAbsenceBody; + namespace Responses { + export interface $200 { + from: string; // date-time + to: string; // date-time + users: { + [name: string]: { + absent: boolean; + absence_intervals: Components.Schemas.AbsenceInterval[]; + /** + * End of the currently active absence interval, or null when the user is not absent. + */ + absent_until: string | null; // date-time + }; + }; + } + export type $400 = Components.Schemas.Error; + } + } + namespace ShareEvent { + namespace Parameters { + export type EventId = string; + } + export interface PathParameters { + event_id: Parameters.EventId; + } + export type RequestBody = Components.Schemas.ShareEventBody; + namespace Responses { + export interface $204 { + } + export type $400 = Components.Schemas.Error; + export type $403 = Components.Schemas.Error; + export type $404 = Components.Schemas.Error; + } + } + namespace UnshareEvent { + namespace Parameters { + export type EventId = string; + export type UserId = string; + } + export interface PathParameters { + event_id: Parameters.EventId; + user_id: Parameters.UserId; + } + namespace Responses { + export interface $204 { + } + export type $403 = Components.Schemas.Error; + export type $404 = Components.Schemas.Error; + } + } + namespace UpdateCalendar { + namespace Parameters { + export type CalendarId = string; + } + export interface PathParameters { + calendar_id: Parameters.CalendarId; + } + export type RequestBody = Components.Schemas.CalendarPatchBody; + namespace Responses { + export type $200 = Components.Schemas.Calendar; + export type $400 = Components.Schemas.Error; + export type $403 = Components.Schemas.Error; + export type $404 = Components.Schemas.Error; + } + } + namespace UpdateEvent { + namespace Parameters { + export type EventId = string; + } + export interface PathParameters { + event_id: Parameters.EventId; + } + export type RequestBody = Components.Schemas.CalendarEventPatchBody; + namespace Responses { + export type $200 = Components.Schemas.CalendarEvent; + export type $400 = Components.Schemas.Error; + export type $403 = Components.Schemas.Error; + export type $404 = Components.Schemas.Error; + } + } +} + + +export interface OperationMethods { + /** + * listUsersAbsence - listUsersAbsence + * + * List organization users with known absence metadata in the requested time window. + */ + 'listUsersAbsence'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * searchAbsence - searchAbsence + * + * Search known absence for candidate users in the requested time window. + */ + 'searchAbsence'( + parameters?: Parameters | null, + data?: Paths.SearchAbsence.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * searchNowAbsence - searchNowAbsence + * + * Search known absence for candidate users at the current server time. + */ + 'searchNowAbsence'( + parameters?: Parameters | null, + data?: Paths.SearchNowAbsence.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * listAbsenceAdjustments - listAbsenceAdjustments + * + * List absence adjustments for a user in a time window. + */ + 'listAbsenceAdjustments'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * createAbsenceAdjustment - createAbsenceAdjustment + * + * Create a time-bound absence adjustment for a user in the caller organization. + */ + 'createAbsenceAdjustment'( + parameters?: Parameters | null, + data?: Paths.CreateAbsenceAdjustment.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * getAbsenceAdjustment - getAbsenceAdjustment + * + * Get an absence adjustment by ID. + */ + 'getAbsenceAdjustment'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * patchAbsenceAdjustment - patchAbsenceAdjustment + * + * Update an absence adjustment in the caller organization. + */ + 'patchAbsenceAdjustment'( + parameters?: Parameters | null, + data?: Paths.PatchAbsenceAdjustment.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * deleteAbsenceAdjustment - deleteAbsenceAdjustment + * + * Delete an absence adjustment. + */ + 'deleteAbsenceAdjustment'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * getUserAbsence - getUserAbsence + * + * Get known absence for a user in a time window. absent=false means no known absence, not guaranteed availability. + */ + 'getUserAbsence'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * listCalendars - listCalendars + * + * List calendars visible to the caller. + */ + 'listCalendars'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * createCalendar - createCalendar + * + * Create a native epilot calendar. + */ + 'createCalendar'( + parameters?: Parameters | null, + data?: Paths.CreateCalendar.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * addOutlookCalendar - addOutlookCalendar + * + * Registers one of the caller's Outlook calendars as an epilot calendar. + * + * The returned calendar resource carries `source.provider=outlook` and the + * provider calendar id, so subsequent reads through `GET /v1/calendar` / + * `GET /v1/calendar/events` route through the sync provider. + */ + 'addOutlookCalendar'( + parameters?: Parameters | null, + data?: Paths.AddOutlookCalendar.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * listOutlookCalendars - listOutlookCalendars + * + * Lists the calling user's Outlook calendars available to import as epilot calendars. + * + * Requires the caller to have a personal Outlook calendar connection (created via the + * OAuth flow on email-settings: `POST /v2/outlook/connect { "calendar": true }`). + */ + 'listOutlookCalendars'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * deleteOutlookCalendar - deleteOutlookCalendar + * + * Disconnects a previously registered Outlook calendar. + * + * Deletes the Microsoft Graph subscription, removes the backing `calendar` + * entity, and drops the local subscription record. Idempotent: a 404 from + * Graph (subscription already gone) is treated as success. + */ + 'deleteOutlookCalendar'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * outlookWebhook - outlookWebhook + * + * Public Microsoft Graph webhook receiver for per-user Outlook calendar + * subscriptions. Unauthenticated by design (API Gateway `Authorizer: NONE`): + * Graph calls it with no epilot token. + * + * Handles both the subscription-validation handshake (echoes the + * `validationToken` query param as `text/plain`) and change notifications. + * Each notification is trusted only after its HMAC-signed `clientState` is + * verified and matched against the stored subscription — identity is never + * read from the request body. + */ + 'outlookWebhook'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * getCalendar - getCalendar + * + * Get a single calendar by its epilot ID. + */ + 'getCalendar'( parameters?: Parameters | null, data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * updateCalendar - updateCalendar + * + * Update fields on a calendar. + */ + 'updateCalendar'( + parameters?: Parameters | null, + data?: Paths.UpdateCalendar.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * deleteCalendar - deleteCalendar + * + * Delete a native epilot calendar and its events. + */ + 'deleteCalendar'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse /** * listEvents - listEvents * @@ -274,6 +1648,16 @@ export interface OperationMethods { data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * createEvent - createEvent + * + * Create a native epilot calendar event. + */ + 'createEvent'( + parameters?: Parameters | null, + data?: Paths.CreateEvent.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse /** * getEvent - getEvent * @@ -284,9 +1668,151 @@ export interface OperationMethods { data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * updateEvent - updateEvent + * + * Update a native epilot calendar event. + */ + 'updateEvent'( + parameters?: Parameters | null, + data?: Paths.UpdateEvent.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * deleteEvent - deleteEvent + * + * Delete a native epilot calendar event. + */ + 'deleteEvent'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * shareEvent - shareEvent + * + * Share a calendar event with another user of the same organization, view-only. Owner-only: recipients of a share cannot re-share. Sharing an already-shared event is a no-op. + */ + 'shareEvent'( + parameters?: Parameters | null, + data?: Paths.ShareEvent.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * unshareEvent - unshareEvent + * + * Revoke a per-event share. The recipient loses access immediately. Owner-only. + */ + 'unshareEvent'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse } export interface PathsDictionary { + ['/v1/calendar/absence/users']: { + /** + * listUsersAbsence - listUsersAbsence + * + * List organization users with known absence metadata in the requested time window. + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/calendar/absence:search']: { + /** + * searchAbsence - searchAbsence + * + * Search known absence for candidate users in the requested time window. + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.SearchAbsence.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/calendar/absence:search-now']: { + /** + * searchNowAbsence - searchNowAbsence + * + * Search known absence for candidate users at the current server time. + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.SearchNowAbsence.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/calendar/absence/users/{user_id}/adjustments']: { + /** + * listAbsenceAdjustments - listAbsenceAdjustments + * + * List absence adjustments for a user in a time window. + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * createAbsenceAdjustment - createAbsenceAdjustment + * + * Create a time-bound absence adjustment for a user in the caller organization. + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.CreateAbsenceAdjustment.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/calendar/absence/users/{user_id}/adjustments/{adjustment_id}']: { + /** + * getAbsenceAdjustment - getAbsenceAdjustment + * + * Get an absence adjustment by ID. + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * patchAbsenceAdjustment - patchAbsenceAdjustment + * + * Update an absence adjustment in the caller organization. + */ + 'patch'( + parameters?: Parameters | null, + data?: Paths.PatchAbsenceAdjustment.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * deleteAbsenceAdjustment - deleteAbsenceAdjustment + * + * Delete an absence adjustment. + */ + 'delete'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/calendar/absence/users/{user_id}']: { + /** + * getUserAbsence - getUserAbsence + * + * Get known absence for a user in a time window. absent=false means no known absence, not guaranteed availability. + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } ['/v1/calendar']: { /** * listCalendars - listCalendars @@ -298,6 +1824,83 @@ export interface PathsDictionary { data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * createCalendar - createCalendar + * + * Create a native epilot calendar. + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.CreateCalendar.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/calendar/sources/outlook']: { + /** + * addOutlookCalendar - addOutlookCalendar + * + * Registers one of the caller's Outlook calendars as an epilot calendar. + * + * The returned calendar resource carries `source.provider=outlook` and the + * provider calendar id, so subsequent reads through `GET /v1/calendar` / + * `GET /v1/calendar/events` route through the sync provider. + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.AddOutlookCalendar.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/calendar/sources/outlook/available']: { + /** + * listOutlookCalendars - listOutlookCalendars + * + * Lists the calling user's Outlook calendars available to import as epilot calendars. + * + * Requires the caller to have a personal Outlook calendar connection (created via the + * OAuth flow on email-settings: `POST /v2/outlook/connect { "calendar": true }`). + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/calendar/sources/outlook/{calendar_id}']: { + /** + * deleteOutlookCalendar - deleteOutlookCalendar + * + * Disconnects a previously registered Outlook calendar. + * + * Deletes the Microsoft Graph subscription, removes the backing `calendar` + * entity, and drops the local subscription record. Idempotent: a 404 from + * Graph (subscription already gone) is treated as success. + */ + 'delete'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/calendar/outlook/webhook']: { + /** + * outlookWebhook - outlookWebhook + * + * Public Microsoft Graph webhook receiver for per-user Outlook calendar + * subscriptions. Unauthenticated by design (API Gateway `Authorizer: NONE`): + * Graph calls it with no epilot token. + * + * Handles both the subscription-validation handshake (echoes the + * `validationToken` query param as `text/plain`) and change notifications. + * Each notification is trusted only after its HMAC-signed `clientState` is + * verified and matched against the stored subscription — identity is never + * read from the request body. + */ + 'post'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse } ['/v1/calendar/{calendar_id}']: { /** @@ -310,6 +1913,26 @@ export interface PathsDictionary { data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * updateCalendar - updateCalendar + * + * Update fields on a calendar. + */ + 'patch'( + parameters?: Parameters | null, + data?: Paths.UpdateCalendar.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * deleteCalendar - deleteCalendar + * + * Delete a native epilot calendar and its events. + */ + 'delete'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse } ['/v1/calendar/events']: { /** @@ -322,6 +1945,16 @@ export interface PathsDictionary { data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * createEvent - createEvent + * + * Create a native epilot calendar event. + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.CreateEvent.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse } ['/v1/calendar/events/{event_id}']: { /** @@ -334,21 +1967,82 @@ export interface PathsDictionary { data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * updateEvent - updateEvent + * + * Update a native epilot calendar event. + */ + 'patch'( + parameters?: Parameters | null, + data?: Paths.UpdateEvent.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * deleteEvent - deleteEvent + * + * Delete a native epilot calendar event. + */ + 'delete'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/calendar/events/{event_id}/share']: { + /** + * shareEvent - shareEvent + * + * Share a calendar event with another user of the same organization, view-only. Owner-only: recipients of a share cannot re-share. Sharing an already-shared event is a no-op. + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.ShareEvent.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/calendar/events/{event_id}/share/{user_id}']: { + /** + * unshareEvent - unshareEvent + * + * Revoke a per-event share. The recipient loses access immediately. Owner-only. + */ + 'delete'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse } } export type Client = OpenAPIClient +export type AbsenceAdjustment = Components.Schemas.AbsenceAdjustment; +export type AbsenceInterval = Components.Schemas.AbsenceInterval; +export type AbsenceIntervalSource = Components.Schemas.AbsenceIntervalSource; +export type AbsenceStatus = Components.Schemas.AbsenceStatus; +export type AbsenceType = Components.Schemas.AbsenceType; +export type AddOutlookCalendarRequest = Components.Schemas.AddOutlookCalendarRequest; export type Attendee = Components.Schemas.Attendee; +export type AvailableOutlookCalendar = Components.Schemas.AvailableOutlookCalendar; export type Calendar = Components.Schemas.Calendar; +export type CalendarCreateBody = Components.Schemas.CalendarCreateBody; export type CalendarEvent = Components.Schemas.CalendarEvent; +export type CalendarEventCreateBody = Components.Schemas.CalendarEventCreateBody; +export type CalendarEventPatchBody = Components.Schemas.CalendarEventPatchBody; +export type CalendarPatchBody = Components.Schemas.CalendarPatchBody; export type CalendarSource = Components.Schemas.CalendarSource; +export type CreateAbsenceAdjustmentBody = Components.Schemas.CreateAbsenceAdjustmentBody; export type Error = Components.Schemas.Error; export type EventSource = Components.Schemas.EventSource; export type EventStatus = Components.Schemas.EventStatus; export type EventType = Components.Schemas.EventType; +export type ExternalCalendar = Components.Schemas.ExternalCalendar; export type Importance = Components.Schemas.Importance; +export type PatchAbsenceAdjustmentBody = Components.Schemas.PatchAbsenceAdjustmentBody; export type Provider = Components.Schemas.Provider; export type ResponseStatus = Components.Schemas.ResponseStatus; +export type SearchAbsenceBody = Components.Schemas.SearchAbsenceBody; +export type SearchNowAbsenceBody = Components.Schemas.SearchNowAbsenceBody; export type Sensitivity = Components.Schemas.Sensitivity; +export type ShareEventBody = Components.Schemas.ShareEventBody; diff --git a/clients/calendar-client/src/openapi.json b/clients/calendar-client/src/openapi.json index 3107b746..8085b387 100644 --- a/clients/calendar-client/src/openapi.json +++ b/clients/calendar-client/src/openapi.json @@ -3,15 +3,19 @@ "info": { "title": "Calendar API", "version": "0.1.0", - "description": "epilot's calendar API.\n\nExposes calendars and events as first-class epilot resources.\nToday, data is sourced from external providers (Microsoft 365, Google Calendar) synced into epilot.\nIn the future, epilot will also host native calendars created directly in the platform.\n\nThe provider is an implementation detail surfaced only through each resource's `source` field.\nConsumers interact with calendars and events through the same API regardless of origin." + "description": "epilot's calendar API.\n\nExposes calendars and events as first-class epilot resources." }, "tags": [ + { + "name": "Absence", + "description": "User absence from calendar events and absence adjustments" + }, { "name": "Calendars", "description": "User and organization calendars" }, { - "name": "Events", + "name": "Calendar Events", "description": "Calendar events" } ], @@ -29,94 +33,312 @@ } ], "paths": { - "/v1/calendar": { + "/v1/calendar/absence/users": { "get": { - "operationId": "listCalendars", - "summary": "listCalendars", - "description": "List calendars visible to the caller.", + "operationId": "listUsersAbsence", + "summary": "listUsersAbsence", + "description": "List organization users with known absence metadata in the requested time window.", "tags": [ - "Calendars" + "Absence" ], "parameters": [ { "in": "query", - "name": "size", + "name": "from", + "description": "Start of the time window (inclusive). Maximum window: 31 days.", + "schema": { + "type": "string", + "format": "date-time", + "description": "Start of the time window (inclusive). Maximum window: 31 days." + }, + "required": true + }, + { + "in": "query", + "name": "to", + "description": "End of the time window (exclusive). Must be after from.", + "schema": { + "type": "string", + "format": "date-time", + "description": "End of the time window (exclusive). Must be after from." + }, + "required": true + }, + { + "in": "query", + "name": "include_busy", + "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.", + "schema": { + "type": "boolean", + "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.", + "default": false + } + }, + { + "in": "query", + "name": "query", + "description": "Optional user directory search query.", + "schema": { + "type": "string", + "description": "Optional user directory search query." + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum users to return.", "schema": { "type": "integer", "minimum": 1, - "maximum": 250 + "maximum": 100, + "description": "Maximum users to return." } }, { "in": "query", - "name": "cursor", - "description": "Opaque cursor from a previous response", + "name": "offset", + "description": "User directory offset.", "schema": { - "type": "string", - "description": "Opaque cursor from a previous response" + "type": "integer", + "minimum": 0, + "description": "User directory offset." } } ], "responses": { "200": { - "description": "List of calendars", + "description": "Organization users absence", "content": { "application/json": { "schema": { "type": "object", "properties": { + "from": { + "type": "string", + "format": "date-time" + }, + "to": { + "type": "string", + "format": "date-time" + }, + "hits": { + "type": "integer", + "minimum": 0 + }, "results": { "type": "array", "items": { - "$ref": "#/components/schemas/Calendar" + "type": "object", + "properties": { + "absent": { + "type": "boolean" + }, + "absence_intervals": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AbsenceInterval" + } + }, + "user_id": { + "type": "string" + }, + "display_name": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true, + "format": "email" + }, + "status": { + "type": "string", + "nullable": true + } + }, + "required": [ + "absent", + "absence_intervals", + "user_id" + ] } - }, - "next_cursor": { - "type": "string", - "nullable": true } }, "required": [ + "from", + "to", + "hits", "results" ] } } } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } } } }, - "/v1/calendar/{calendar_id}": { - "get": { - "operationId": "getCalendar", - "summary": "getCalendar", - "description": "Get a single calendar by its epilot ID.", + "/v1/calendar/absence:search": { + "post": { + "operationId": "searchAbsence", + "summary": "searchAbsence", + "description": "Search known absence for candidate users in the requested time window.", "tags": [ - "Calendars" + "Absence" ], - "parameters": [ - { - "in": "path", - "name": "calendar_id", - "schema": { - "type": "string" - }, - "required": true + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchAbsenceBody" + } + } + } + }, + "responses": { + "200": { + "description": "Users absence", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time" + }, + "to": { + "type": "string", + "format": "date-time" + }, + "users": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "absent": { + "type": "boolean" + }, + "absence_intervals": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AbsenceInterval" + } + } + }, + "required": [ + "absent", + "absence_intervals" + ] + } + } + }, + "required": [ + "from", + "to", + "users" + ] + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } + } + } + }, + "/v1/calendar/absence:search-now": { + "post": { + "operationId": "searchNowAbsence", + "summary": "searchNowAbsence", + "description": "Search known absence for candidate users at the current server time.", + "tags": [ + "Absence" ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchNowAbsenceBody" + } + } + } + }, "responses": { "200": { - "description": "Calendar", + "description": "Users current absence", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Calendar" + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time" + }, + "to": { + "type": "string", + "format": "date-time" + }, + "users": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "absent": { + "type": "boolean" + }, + "absence_intervals": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AbsenceInterval" + } + }, + "absent_until": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "End of the currently active absence interval, or null when the user is not absent." + } + }, + "required": [ + "absent", + "absence_intervals", + "absent_until" + ] + } + } + }, + "required": [ + "from", + "to", + "users" + ] } } } }, - "404": { - "description": "Resource not found", + "400": { + "description": "Invalid request body", "content": { "application/json": { "schema": { @@ -128,160 +350,1594 @@ } } }, - "/v1/calendar/events": { + "/v1/calendar/absence/users/{user_id}/adjustments": { "get": { - "operationId": "listEvents", - "summary": "listEvents", - "description": "List events in a time window. Recurring events are returned as expanded occurrences – each instance appears as its own event.", + "operationId": "listAbsenceAdjustments", + "summary": "listAbsenceAdjustments", + "description": "List absence adjustments for a user in a time window.", "tags": [ - "Events" + "Absence" ], "parameters": [ { - "in": "query", - "name": "from", - "description": "Start of the time window (inclusive)", + "in": "path", + "name": "user_id", "schema": { "type": "string", - "format": "date-time", - "description": "Start of the time window (inclusive)" + "minLength": 1 }, "required": true }, { "in": "query", - "name": "to", - "description": "End of the time window (exclusive)", + "name": "from", + "description": "Start of the time window (inclusive). Maximum window: 90 days.", "schema": { "type": "string", "format": "date-time", - "description": "End of the time window (exclusive)" + "description": "Start of the time window (inclusive). Maximum window: 90 days." }, "required": true }, { "in": "query", - "name": "calendar_id", - "description": "Filter to a single calendar. Omit to query across all accessible calendars.", + "name": "to", + "description": "End of the time window (exclusive). Must be after from.", "schema": { "type": "string", - "description": "Filter to a single calendar. Omit to query across all accessible calendars." - } - }, - { - "in": "query", - "name": "size", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 250 - } - }, - { - "in": "query", - "name": "cursor", - "schema": { - "type": "string" - } + "format": "date-time", + "description": "End of the time window (exclusive). Must be after from." + }, + "required": true } ], "responses": { "200": { - "description": "List of events", + "description": "Absence adjustments", "content": { "application/json": { "schema": { "type": "object", "properties": { + "from": { + "type": "string", + "format": "date-time" + }, + "to": { + "type": "string", + "format": "date-time" + }, "results": { "type": "array", "items": { - "$ref": "#/components/schemas/CalendarEvent" + "$ref": "#/components/schemas/AbsenceAdjustment" } - }, - "next_cursor": { - "type": "string", - "nullable": true } }, "required": [ + "from", + "to", "results" ] } } } }, - "400": { - "description": "Invalid request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "operationId": "createAbsenceAdjustment", + "summary": "createAbsenceAdjustment", + "description": "Create a time-bound absence adjustment for a user in the caller organization.", + "tags": [ + "Absence" + ], + "parameters": [ + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAbsenceAdjustmentBody" + } + } + } + }, + "responses": { + "201": { + "description": "Created absence adjustment", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AbsenceAdjustment" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/absence/users/{user_id}/adjustments/{adjustment_id}": { + "get": { + "operationId": "getAbsenceAdjustment", + "summary": "getAbsenceAdjustment", + "description": "Get an absence adjustment by ID.", + "tags": [ + "Absence" + ], + "parameters": [ + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + }, + { + "in": "path", + "name": "adjustment_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Absence adjustment", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AbsenceAdjustment" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "operationId": "patchAbsenceAdjustment", + "summary": "patchAbsenceAdjustment", + "description": "Update an absence adjustment in the caller organization.", + "tags": [ + "Absence" + ], + "parameters": [ + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + }, + { + "in": "path", + "name": "adjustment_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchAbsenceAdjustmentBody" + } + } + } + }, + "responses": { + "200": { + "description": "Updated absence adjustment", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AbsenceAdjustment" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "operationId": "deleteAbsenceAdjustment", + "summary": "deleteAbsenceAdjustment", + "description": "Delete an absence adjustment.", + "tags": [ + "Absence" + ], + "parameters": [ + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + }, + { + "in": "path", + "name": "adjustment_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + } + ], + "responses": { + "204": { + "description": "Absence adjustment deleted" + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/absence/users/{user_id}": { + "get": { + "operationId": "getUserAbsence", + "summary": "getUserAbsence", + "description": "Get known absence for a user in a time window. absent=false means no known absence, not guaranteed availability.", + "tags": [ + "Absence" + ], + "parameters": [ + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true + }, + { + "in": "query", + "name": "from", + "description": "Start of the time window (inclusive). Maximum window: 31 days.", + "schema": { + "type": "string", + "format": "date-time", + "description": "Start of the time window (inclusive). Maximum window: 31 days." + }, + "required": true + }, + { + "in": "query", + "name": "to", + "description": "End of the time window (exclusive). Must be after from.", + "schema": { + "type": "string", + "format": "date-time", + "description": "End of the time window (exclusive). Must be after from." + }, + "required": true + }, + { + "in": "query", + "name": "include_busy", + "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.", + "schema": { + "type": "boolean", + "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.", + "default": false + } + } + ], + "responses": { + "200": { + "description": "User absence", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "absent": { + "type": "boolean" + }, + "absence_intervals": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AbsenceInterval" + } + }, + "from": { + "type": "string", + "format": "date-time" + }, + "to": { + "type": "string", + "format": "date-time" + }, + "user_id": { + "type": "string" + }, + "external_calendars": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExternalCalendar" + }, + "description": "The user's connected external calendars (Outlook/Google), if any." + } + }, + "required": [ + "absent", + "absence_intervals", + "from", + "to", + "user_id" + ] + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar": { + "get": { + "operationId": "listCalendars", + "summary": "listCalendars", + "description": "List calendars visible to the caller.", + "tags": [ + "Calendars" + ], + "parameters": [ + { + "in": "query", + "name": "size", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 250 + } + }, + { + "in": "query", + "name": "cursor", + "description": "Opaque cursor from a previous response", + "schema": { + "type": "string", + "description": "Opaque cursor from a previous response" + } + } + ], + "responses": { + "200": { + "description": "List of calendars", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Calendar" + } + }, + "next_cursor": { + "type": "string", + "nullable": true + } + }, + "required": [ + "results" + ] + } + } + } + } + } + }, + "post": { + "operationId": "createCalendar", + "summary": "createCalendar", + "description": "Create a native epilot calendar.", + "tags": [ + "Calendars" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarCreateBody" + } + } + } + }, + "responses": { + "201": { + "description": "Calendar created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Calendar" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/sources/outlook": { + "post": { + "operationId": "addOutlookCalendar", + "summary": "addOutlookCalendar", + "description": "Registers one of the caller's Outlook calendars as an epilot calendar.\n\nThe returned calendar resource carries `source.provider=outlook` and the\nprovider calendar id, so subsequent reads through `GET /v1/calendar` /\n`GET /v1/calendar/events` route through the sync provider.", + "tags": [ + "Calendars" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddOutlookCalendarRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Calendar created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Calendar" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Caller has not connected their personal Outlook calendar yet", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/sources/outlook/available": { + "get": { + "operationId": "listOutlookCalendars", + "summary": "listOutlookCalendars", + "description": "Lists the calling user's Outlook calendars available to import as epilot calendars.\n\nRequires the caller to have a personal Outlook calendar connection (created via the\nOAuth flow on email-settings: `POST /v2/outlook/connect { \"calendar\": true }`).", + "tags": [ + "Calendars" + ], + "responses": { + "200": { + "description": "List of available Outlook calendars", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "calendars": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AvailableOutlookCalendar" + } + } + }, + "required": [ + "calendars" + ] + } + } + } + }, + "404": { + "description": "Caller has not connected their personal Outlook calendar yet", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/sources/outlook/{calendar_id}": { + "delete": { + "operationId": "deleteOutlookCalendar", + "summary": "deleteOutlookCalendar", + "description": "Disconnects a previously registered Outlook calendar.\n\nDeletes the Microsoft Graph subscription, removes the backing `calendar`\nentity, and drops the local subscription record. Idempotent: a 404 from\nGraph (subscription already gone) is treated as success.", + "tags": [ + "Calendars" + ], + "parameters": [ + { + "in": "path", + "name": "calendar_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "Outlook calendar disconnected" + }, + "404": { + "description": "No Outlook calendar registration found for this id", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "502": { + "description": "Microsoft Graph subscription deletion failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/outlook/webhook": { + "post": { + "operationId": "outlookWebhook", + "summary": "outlookWebhook", + "description": "Public Microsoft Graph webhook receiver for per-user Outlook calendar\nsubscriptions. Unauthenticated by design (API Gateway `Authorizer: NONE`):\nGraph calls it with no epilot token.\n\nHandles both the subscription-validation handshake (echoes the\n`validationToken` query param as `text/plain`) and change notifications.\nEach notification is trusted only after its HMAC-signed `clientState` is\nverified and matched against the stored subscription — identity is never\nread from the request body.", + "tags": [ + "Calendars" + ], + "security": [], + "parameters": [ + { + "in": "query", + "name": "validationToken", + "description": "Set by Graph on the subscription-validation handshake; echoed back verbatim.", + "schema": { + "type": "string", + "description": "Set by Graph on the subscription-validation handshake; echoed back verbatim." + } + } + ], + "responses": { + "200": { + "description": "Validation handshake echo (text/plain)", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "202": { + "description": "Notification batch accepted" + } + } + } + }, + "/v1/calendar/{calendar_id}": { + "get": { + "operationId": "getCalendar", + "summary": "getCalendar", + "description": "Get a single calendar by its epilot ID.", + "tags": [ + "Calendars" + ], + "parameters": [ + { + "in": "path", + "name": "calendar_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Calendar", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Calendar" + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "operationId": "updateCalendar", + "summary": "updateCalendar", + "description": "Update fields on a calendar.", + "tags": [ + "Calendars" + ], + "parameters": [ + { + "in": "path", + "name": "calendar_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarPatchBody" + } + } + } + }, + "responses": { + "200": { + "description": "Calendar updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Calendar" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Calendar is read-only", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "operationId": "deleteCalendar", + "summary": "deleteCalendar", + "description": "Delete a native epilot calendar and its events.", + "tags": [ + "Calendars" + ], + "parameters": [ + { + "in": "path", + "name": "calendar_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "Calendar deleted" + }, + "403": { + "description": "Calendar is read-only", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/events": { + "get": { + "operationId": "listEvents", + "summary": "listEvents", + "description": "List events in a time window. Recurring events are returned as expanded occurrences – each instance appears as its own event.", + "tags": [ + "Calendar Events" + ], + "parameters": [ + { + "in": "query", + "name": "from", + "description": "Start of the time window (inclusive)", + "schema": { + "type": "string", + "format": "date-time", + "description": "Start of the time window (inclusive)" + }, + "required": true + }, + { + "in": "query", + "name": "to", + "description": "End of the time window (exclusive)", + "schema": { + "type": "string", + "format": "date-time", + "description": "End of the time window (exclusive)" + }, + "required": true + }, + { + "in": "query", + "name": "calendar_id", + "description": "Filter to a single calendar. Omit to query across all accessible calendars.", + "schema": { + "type": "string", + "description": "Filter to a single calendar. Omit to query across all accessible calendars." + } + }, + { + "in": "query", + "name": "size", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 250 + } + }, + { + "in": "query", + "name": "cursor", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of events", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CalendarEvent" + } + }, + "next_cursor": { + "type": "string", + "nullable": true + } + }, + "required": [ + "results" + ] + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "operationId": "createEvent", + "summary": "createEvent", + "description": "Create a native epilot calendar event.", + "tags": [ + "Calendar Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarEventCreateBody" + } + } + } + }, + "responses": { + "201": { + "description": "Event created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarEvent" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Calendar is read-only", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Calendar not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/events/{event_id}": { + "get": { + "operationId": "getEvent", + "summary": "getEvent", + "description": "Get a single event by its epilot ID.", + "tags": [ + "Calendar Events" + ], + "parameters": [ + { + "in": "path", + "name": "event_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Event", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarEvent" + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "operationId": "updateEvent", + "summary": "updateEvent", + "description": "Update a native epilot calendar event.", + "tags": [ + "Calendar Events" + ], + "parameters": [ + { + "in": "path", + "name": "event_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarEventPatchBody" + } + } + } + }, + "responses": { + "200": { + "description": "Event updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CalendarEvent" + } + } + } + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Event or calendar is read-only", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "operationId": "deleteEvent", + "summary": "deleteEvent", + "description": "Delete a native epilot calendar event.", + "tags": [ + "Calendar Events" + ], + "parameters": [ + { + "in": "path", + "name": "event_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "Event deleted" + }, + "403": { + "description": "Event or calendar is read-only", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/events/{event_id}/share": { + "post": { + "operationId": "shareEvent", + "summary": "shareEvent", + "description": "Share a calendar event with another user of the same organization, view-only. Owner-only: recipients of a share cannot re-share. Sharing an already-shared event is a no-op.", + "tags": [ + "Calendar Events" + ], + "parameters": [ + { + "in": "path", + "name": "event_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShareEventBody" + } + } + } + }, + "responses": { + "204": { + "description": "Event shared" + }, + "400": { + "description": "Invalid request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Caller is not the event owner", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Event or recipient not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/v1/calendar/events/{event_id}/share/{user_id}": { + "delete": { + "operationId": "unshareEvent", + "summary": "unshareEvent", + "description": "Revoke a per-event share. The recipient loses access immediately. Owner-only.", + "tags": [ + "Calendar Events" + ], + "parameters": [ + { + "in": "path", + "name": "event_id", + "schema": { + "type": "string" + }, + "required": true + }, + { + "in": "path", + "name": "user_id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "Share revoked (or was not present)" + }, + "403": { + "description": "Caller is not the event owner", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Event not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "EpilotAuth": { + "type": "http", + "scheme": "bearer", + "description": "Authorization header with epilot OAuth2 bearer token", + "bearerFormat": "JWT" + }, + "EpilotOrg": { + "type": "apiKey", + "in": "header", + "name": "x-epilot-org-id" + } + }, + "schemas": { + "AbsenceInterval": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time", + "description": "Absence interval start clipped to the requested window." + }, + "to": { + "type": "string", + "format": "date-time", + "description": "Absence interval end clipped to the requested window." + }, + "original_from": { + "type": "string", + "format": "date-time", + "description": "Original absence interval start before clipping." + }, + "original_to": { + "type": "string", + "format": "date-time", + "description": "Original absence interval end before clipping." + }, + "absent": { + "type": "boolean", + "description": "Effective absence state for this interval." + }, + "source": { + "$ref": "#/components/schemas/AbsenceIntervalSource" + }, + "calendar_event_id": { + "type": "string", + "description": "calendar_event ID for calendar-derived intervals." + }, + "absence_adjustment_id": { + "type": "string", + "description": "absence adjustment ID for manual intervals." + }, + "reason": { + "type": "string", + "nullable": true + } + }, + "required": [ + "from", + "to", + "original_from", + "original_to", + "absent", + "source" + ] + }, + "AbsenceIntervalSource": { + "type": "string", + "enum": [ + "calendar_event", + "absence_adjustment" + ] + }, + "Error": { + "type": "object", + "properties": { + "status": { + "type": "integer" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": {} + } + ] + } + }, + "required": [ + "status", + "error" + ] + }, + "SearchAbsenceBody": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time", + "description": "Start of the time window (inclusive). Maximum window: 31 days." + }, + "to": { + "type": "string", + "format": "date-time", + "description": "End of the time window (exclusive). Must be after from." + }, + "user_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1, + "maxItems": 300, + "description": "Candidate users to check for absence inside the caller organization." + }, + "include_busy": { + "type": "boolean", + "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.", + "default": false + } + }, + "required": [ + "from", + "to", + "user_ids" + ] + }, + "SearchNowAbsenceBody": { + "type": "object", + "properties": { + "user_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1, + "maxItems": 300, + "description": "Candidate users to check for absence at the current server time inside the caller organization." + }, + "include_busy": { + "type": "boolean", + "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.", + "default": false + } + }, + "required": [ + "user_ids" + ] + }, + "AbsenceAdjustment": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time", + "description": "Adjustment start. Maximum adjustment duration: 7 days." + }, + "to": { + "type": "string", + "format": "date-time", + "description": "Adjustment end. Must be after from." + }, + "absent": { + "type": "boolean", + "description": "Manual override state: true means absent, false means not absent." + }, + "status": { + "$ref": "#/components/schemas/AbsenceStatus" + }, + "type": { + "$ref": "#/components/schemas/AbsenceType" + }, + "reason": { + "type": "string", + "nullable": true, + "maxLength": 500 + }, + "adjustment_id": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "from", + "to", + "absent", + "status", + "adjustment_id", + "user_id", + "created_by", + "created_at", + "updated_at" + ] + }, + "AbsenceStatus": { + "type": "string", + "enum": [ + "busy", + "oof" + ], + "description": "Calendar status targeted by the absence adjustment." + }, + "AbsenceType": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "Optional producer-defined reference stored with an absence adjustment." + }, + "CreateAbsenceAdjustmentBody": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time", + "description": "Adjustment start. Maximum adjustment duration: 7 days." + }, + "to": { + "type": "string", + "format": "date-time", + "description": "Adjustment end. Must be after from." + }, + "absent": { + "type": "boolean", + "description": "Manual override state: true means absent, false means not absent." + }, + "status": { + "$ref": "#/components/schemas/AbsenceStatus" + }, + "type": { + "$ref": "#/components/schemas/AbsenceType" + }, + "reason": { + "type": "string", + "nullable": true, + "maxLength": 500 + } + }, + "required": [ + "from", + "to", + "absent", + "status" + ] + }, + "PatchAbsenceAdjustmentBody": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time", + "description": "Adjustment start. Maximum adjustment duration: 7 days." + }, + "to": { + "type": "string", + "format": "date-time", + "description": "Adjustment end. Must be after from." + }, + "absent": { + "type": "boolean", + "description": "Manual override state: true means absent, false means not absent." + }, + "status": { + "$ref": "#/components/schemas/AbsenceStatus" + }, + "type": { + "$ref": "#/components/schemas/AbsenceType" + }, + "reason": { + "type": "string", + "nullable": true, + "maxLength": 500 } } - } - }, - "/v1/calendar/events/{event_id}": { - "get": { - "operationId": "getEvent", - "summary": "getEvent", - "description": "Get a single event by its epilot ID.", - "tags": [ - "Events" - ], - "parameters": [ - { - "in": "path", - "name": "event_id", - "schema": { - "type": "string" - }, - "required": true - } - ], - "responses": { - "200": { - "description": "Event", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CalendarEvent" - } - } - } + }, + "ExternalCalendar": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "enum": [ + "outlook", + "google" + ], + "description": "External calendar provider." }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } + "last_synced_at": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Last successful sync of this provider calendar, if any." } - } - } - } - }, - "components": { - "securitySchemes": { - "EpilotAuth": { - "type": "http", - "scheme": "bearer", - "description": "Authorization header with epilot OAuth2 bearer token", - "bearerFormat": "JWT" + }, + "required": [ + "provider", + "last_synced_at" + ] }, - "EpilotOrg": { - "type": "apiKey", - "in": "header", - "name": "x-epilot-org-id" - } - }, - "schemas": { "Calendar": { "type": "object", "properties": { @@ -304,7 +1960,8 @@ "description": "Computed display title" }, "name": { - "type": "string" + "type": "string", + "minLength": 1 }, "description": { "type": "string", @@ -388,20 +2045,93 @@ ], "description": "Null for native epilot calendars" }, - "Error": { + "CalendarCreateBody": { "type": "object", "properties": { - "message": { - "type": "string" + "name": { + "type": "string", + "minLength": 1 }, - "code": { + "description": { + "type": "string", + "nullable": true + }, + "color": { + "type": "string", + "nullable": true, + "description": "Hex color (e.g. \"#3b82f6\") or provider-defined named color" + } + }, + "required": [ + "name" + ] + }, + "AddOutlookCalendarRequest": { + "type": "object", + "properties": { + "provider_calendar_id": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Optional override; defaults to the provider calendar name." + } + }, + "required": [ + "provider_calendar_id" + ] + }, + "AvailableOutlookCalendar": { + "type": "object", + "properties": { + "provider_calendar_id": { + "type": "string", + "description": "Microsoft Graph calendar id (immutable, e.g. base64-encoded)" + }, + "name": { "type": "string" + }, + "color": { + "type": "string", + "nullable": true + }, + "is_default": { + "type": "boolean" + }, + "can_edit": { + "type": "boolean" + }, + "owner": { + "type": "string", + "nullable": true, + "description": "Owner email address on the Microsoft side" } }, "required": [ - "message" + "provider_calendar_id", + "name" ] }, + "CalendarPatchBody": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "nullable": true + }, + "color": { + "type": "string", + "nullable": true, + "description": "Hex color (e.g. \"#3b82f6\") or provider-defined named color" + } + } + }, "CalendarEvent": { "type": "object", "properties": { @@ -432,6 +2162,7 @@ }, "calendar_id": { "type": "string", + "minLength": 1, "description": "epilot calendar this event belongs to" }, "event_type": { @@ -455,6 +2186,7 @@ }, "timezone": { "type": "string", + "minLength": 1, "description": "IANA timezone of the original event (for display)" }, "is_all_day": { @@ -669,6 +2401,120 @@ "required": [ "type" ] + }, + "CalendarEventCreateBody": { + "type": "object", + "properties": { + "calendar_id": { + "type": "string", + "minLength": 1, + "description": "epilot calendar this event belongs to" + }, + "description": { + "type": "string", + "nullable": true, + "description": "Preview of the event body, truncated to 255 chars", + "maxLength": 255 + }, + "start_time": { + "type": "string", + "format": "date-time", + "description": "Event start in UTC" + }, + "end_time": { + "type": "string", + "format": "date-time", + "description": "Event end in UTC" + }, + "timezone": { + "type": "string", + "minLength": 1, + "description": "IANA timezone of the original event (for display)" + }, + "is_all_day": { + "type": "boolean" + }, + "location": { + "type": "string", + "nullable": true + }, + "status": { + "$ref": "#/components/schemas/EventStatus" + }, + "sensitivity": { + "$ref": "#/components/schemas/Sensitivity" + }, + "_title": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "calendar_id", + "start_time", + "end_time", + "timezone", + "is_all_day", + "status", + "sensitivity", + "_title" + ] + }, + "CalendarEventPatchBody": { + "type": "object", + "properties": { + "description": { + "type": "string", + "nullable": true, + "description": "Preview of the event body, truncated to 255 chars", + "maxLength": 255 + }, + "start_time": { + "type": "string", + "format": "date-time", + "description": "Event start in UTC" + }, + "end_time": { + "type": "string", + "format": "date-time", + "description": "Event end in UTC" + }, + "timezone": { + "type": "string", + "minLength": 1, + "description": "IANA timezone of the original event (for display)" + }, + "is_all_day": { + "type": "boolean" + }, + "location": { + "type": "string", + "nullable": true + }, + "status": { + "$ref": "#/components/schemas/EventStatus" + }, + "sensitivity": { + "$ref": "#/components/schemas/Sensitivity" + }, + "_title": { + "type": "string", + "minLength": 1 + } + } + }, + "ShareEventBody": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "minLength": 1, + "description": "epilot user id (same organization) to grant view-only access to this event" + } + }, + "required": [ + "user_id" + ] } } }