@epilot/cli 0.1.90 → 0.1.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,15 +3,19 @@
3
3
  "info": {
4
4
  "title": "Calendar API",
5
5
  "version": "0.1.0",
6
- "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."
6
+ "description": "epilot's calendar API.\n\nExposes calendars and events as first-class epilot resources."
7
7
  },
8
8
  "tags": [
9
+ {
10
+ "name": "Absence",
11
+ "description": "User absence from calendar events and absence adjustments"
12
+ },
9
13
  {
10
14
  "name": "Calendars",
11
15
  "description": "User and organization calendars"
12
16
  },
13
17
  {
14
- "name": "Events",
18
+ "name": "Calendar Events",
15
19
  "description": "Calendar events"
16
20
  }
17
21
  ],
@@ -29,94 +33,312 @@
29
33
  }
30
34
  ],
31
35
  "paths": {
32
- "/v1/calendar": {
36
+ "/v1/calendar/absence/users": {
33
37
  "get": {
34
- "operationId": "listCalendars",
35
- "summary": "listCalendars",
36
- "description": "List calendars visible to the caller.",
38
+ "operationId": "listUsersAbsence",
39
+ "summary": "listUsersAbsence",
40
+ "description": "List organization users with known absence metadata in the requested time window.",
37
41
  "tags": [
38
- "Calendars"
42
+ "Absence"
39
43
  ],
40
44
  "parameters": [
41
45
  {
42
46
  "in": "query",
43
- "name": "size",
47
+ "name": "from",
48
+ "description": "Start of the time window (inclusive). Maximum window: 31 days.",
49
+ "schema": {
50
+ "type": "string",
51
+ "format": "date-time",
52
+ "description": "Start of the time window (inclusive). Maximum window: 31 days."
53
+ },
54
+ "required": true
55
+ },
56
+ {
57
+ "in": "query",
58
+ "name": "to",
59
+ "description": "End of the time window (exclusive). Must be after from.",
60
+ "schema": {
61
+ "type": "string",
62
+ "format": "date-time",
63
+ "description": "End of the time window (exclusive). Must be after from."
64
+ },
65
+ "required": true
66
+ },
67
+ {
68
+ "in": "query",
69
+ "name": "include_busy",
70
+ "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.",
71
+ "schema": {
72
+ "type": "boolean",
73
+ "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.",
74
+ "default": false
75
+ }
76
+ },
77
+ {
78
+ "in": "query",
79
+ "name": "query",
80
+ "description": "Optional user directory search query.",
81
+ "schema": {
82
+ "type": "string",
83
+ "description": "Optional user directory search query."
84
+ }
85
+ },
86
+ {
87
+ "in": "query",
88
+ "name": "limit",
89
+ "description": "Maximum users to return.",
44
90
  "schema": {
45
91
  "type": "integer",
46
92
  "minimum": 1,
47
- "maximum": 250
93
+ "maximum": 100,
94
+ "description": "Maximum users to return."
48
95
  }
49
96
  },
50
97
  {
51
98
  "in": "query",
52
- "name": "cursor",
53
- "description": "Opaque cursor from a previous response",
99
+ "name": "offset",
100
+ "description": "User directory offset.",
54
101
  "schema": {
55
- "type": "string",
56
- "description": "Opaque cursor from a previous response"
102
+ "type": "integer",
103
+ "minimum": 0,
104
+ "description": "User directory offset."
57
105
  }
58
106
  }
59
107
  ],
60
108
  "responses": {
61
109
  "200": {
62
- "description": "List of calendars",
110
+ "description": "Organization users absence",
63
111
  "content": {
64
112
  "application/json": {
65
113
  "schema": {
66
114
  "type": "object",
67
115
  "properties": {
116
+ "from": {
117
+ "type": "string",
118
+ "format": "date-time"
119
+ },
120
+ "to": {
121
+ "type": "string",
122
+ "format": "date-time"
123
+ },
124
+ "hits": {
125
+ "type": "integer",
126
+ "minimum": 0
127
+ },
68
128
  "results": {
69
129
  "type": "array",
70
130
  "items": {
71
- "$ref": "#/components/schemas/Calendar"
131
+ "type": "object",
132
+ "properties": {
133
+ "absent": {
134
+ "type": "boolean"
135
+ },
136
+ "absence_intervals": {
137
+ "type": "array",
138
+ "items": {
139
+ "$ref": "#/components/schemas/AbsenceInterval"
140
+ }
141
+ },
142
+ "user_id": {
143
+ "type": "string"
144
+ },
145
+ "display_name": {
146
+ "type": "string",
147
+ "nullable": true
148
+ },
149
+ "email": {
150
+ "type": "string",
151
+ "nullable": true,
152
+ "format": "email"
153
+ },
154
+ "status": {
155
+ "type": "string",
156
+ "nullable": true
157
+ }
158
+ },
159
+ "required": [
160
+ "absent",
161
+ "absence_intervals",
162
+ "user_id"
163
+ ]
72
164
  }
73
- },
74
- "next_cursor": {
75
- "type": "string",
76
- "nullable": true
77
165
  }
78
166
  },
79
167
  "required": [
168
+ "from",
169
+ "to",
170
+ "hits",
80
171
  "results"
81
172
  ]
82
173
  }
83
174
  }
84
175
  }
176
+ },
177
+ "400": {
178
+ "description": "Invalid request",
179
+ "content": {
180
+ "application/json": {
181
+ "schema": {
182
+ "$ref": "#/components/schemas/Error"
183
+ }
184
+ }
185
+ }
85
186
  }
86
187
  }
87
188
  }
88
189
  },
89
- "/v1/calendar/{calendar_id}": {
90
- "get": {
91
- "operationId": "getCalendar",
92
- "summary": "getCalendar",
93
- "description": "Get a single calendar by its epilot ID.",
190
+ "/v1/calendar/absence:search": {
191
+ "post": {
192
+ "operationId": "searchAbsence",
193
+ "summary": "searchAbsence",
194
+ "description": "Search known absence for candidate users in the requested time window.",
94
195
  "tags": [
95
- "Calendars"
196
+ "Absence"
96
197
  ],
97
- "parameters": [
98
- {
99
- "in": "path",
100
- "name": "calendar_id",
101
- "schema": {
102
- "type": "string"
103
- },
104
- "required": true
198
+ "requestBody": {
199
+ "required": true,
200
+ "content": {
201
+ "application/json": {
202
+ "schema": {
203
+ "$ref": "#/components/schemas/SearchAbsenceBody"
204
+ }
205
+ }
206
+ }
207
+ },
208
+ "responses": {
209
+ "200": {
210
+ "description": "Users absence",
211
+ "content": {
212
+ "application/json": {
213
+ "schema": {
214
+ "type": "object",
215
+ "properties": {
216
+ "from": {
217
+ "type": "string",
218
+ "format": "date-time"
219
+ },
220
+ "to": {
221
+ "type": "string",
222
+ "format": "date-time"
223
+ },
224
+ "users": {
225
+ "type": "object",
226
+ "additionalProperties": {
227
+ "type": "object",
228
+ "properties": {
229
+ "absent": {
230
+ "type": "boolean"
231
+ },
232
+ "absence_intervals": {
233
+ "type": "array",
234
+ "items": {
235
+ "$ref": "#/components/schemas/AbsenceInterval"
236
+ }
237
+ }
238
+ },
239
+ "required": [
240
+ "absent",
241
+ "absence_intervals"
242
+ ]
243
+ }
244
+ }
245
+ },
246
+ "required": [
247
+ "from",
248
+ "to",
249
+ "users"
250
+ ]
251
+ }
252
+ }
253
+ }
254
+ },
255
+ "400": {
256
+ "description": "Invalid request body",
257
+ "content": {
258
+ "application/json": {
259
+ "schema": {
260
+ "$ref": "#/components/schemas/Error"
261
+ }
262
+ }
263
+ }
105
264
  }
265
+ }
266
+ }
267
+ },
268
+ "/v1/calendar/absence:search-now": {
269
+ "post": {
270
+ "operationId": "searchNowAbsence",
271
+ "summary": "searchNowAbsence",
272
+ "description": "Search known absence for candidate users at the current server time.",
273
+ "tags": [
274
+ "Absence"
106
275
  ],
276
+ "requestBody": {
277
+ "required": true,
278
+ "content": {
279
+ "application/json": {
280
+ "schema": {
281
+ "$ref": "#/components/schemas/SearchNowAbsenceBody"
282
+ }
283
+ }
284
+ }
285
+ },
107
286
  "responses": {
108
287
  "200": {
109
- "description": "Calendar",
288
+ "description": "Users current absence",
110
289
  "content": {
111
290
  "application/json": {
112
291
  "schema": {
113
- "$ref": "#/components/schemas/Calendar"
292
+ "type": "object",
293
+ "properties": {
294
+ "from": {
295
+ "type": "string",
296
+ "format": "date-time"
297
+ },
298
+ "to": {
299
+ "type": "string",
300
+ "format": "date-time"
301
+ },
302
+ "users": {
303
+ "type": "object",
304
+ "additionalProperties": {
305
+ "type": "object",
306
+ "properties": {
307
+ "absent": {
308
+ "type": "boolean"
309
+ },
310
+ "absence_intervals": {
311
+ "type": "array",
312
+ "items": {
313
+ "$ref": "#/components/schemas/AbsenceInterval"
314
+ }
315
+ },
316
+ "absent_until": {
317
+ "type": "string",
318
+ "nullable": true,
319
+ "format": "date-time",
320
+ "description": "End of the currently active absence interval, or null when the user is not absent."
321
+ }
322
+ },
323
+ "required": [
324
+ "absent",
325
+ "absence_intervals",
326
+ "absent_until"
327
+ ]
328
+ }
329
+ }
330
+ },
331
+ "required": [
332
+ "from",
333
+ "to",
334
+ "users"
335
+ ]
114
336
  }
115
337
  }
116
338
  }
117
339
  },
118
- "404": {
119
- "description": "Resource not found",
340
+ "400": {
341
+ "description": "Invalid request body",
120
342
  "content": {
121
343
  "application/json": {
122
344
  "schema": {
@@ -128,160 +350,1594 @@
128
350
  }
129
351
  }
130
352
  },
131
- "/v1/calendar/events": {
353
+ "/v1/calendar/absence/users/{user_id}/adjustments": {
132
354
  "get": {
133
- "operationId": "listEvents",
134
- "summary": "listEvents",
135
- "description": "List events in a time window. Recurring events are returned as expanded occurrences – each instance appears as its own event.",
355
+ "operationId": "listAbsenceAdjustments",
356
+ "summary": "listAbsenceAdjustments",
357
+ "description": "List absence adjustments for a user in a time window.",
136
358
  "tags": [
137
- "Events"
359
+ "Absence"
138
360
  ],
139
361
  "parameters": [
140
362
  {
141
- "in": "query",
142
- "name": "from",
143
- "description": "Start of the time window (inclusive)",
363
+ "in": "path",
364
+ "name": "user_id",
144
365
  "schema": {
145
366
  "type": "string",
146
- "format": "date-time",
147
- "description": "Start of the time window (inclusive)"
367
+ "minLength": 1
148
368
  },
149
369
  "required": true
150
370
  },
151
371
  {
152
372
  "in": "query",
153
- "name": "to",
154
- "description": "End of the time window (exclusive)",
373
+ "name": "from",
374
+ "description": "Start of the time window (inclusive). Maximum window: 90 days.",
155
375
  "schema": {
156
376
  "type": "string",
157
377
  "format": "date-time",
158
- "description": "End of the time window (exclusive)"
378
+ "description": "Start of the time window (inclusive). Maximum window: 90 days."
159
379
  },
160
380
  "required": true
161
381
  },
162
382
  {
163
383
  "in": "query",
164
- "name": "calendar_id",
165
- "description": "Filter to a single calendar. Omit to query across all accessible calendars.",
384
+ "name": "to",
385
+ "description": "End of the time window (exclusive). Must be after from.",
166
386
  "schema": {
167
387
  "type": "string",
168
- "description": "Filter to a single calendar. Omit to query across all accessible calendars."
169
- }
170
- },
171
- {
172
- "in": "query",
173
- "name": "size",
174
- "schema": {
175
- "type": "integer",
176
- "minimum": 1,
177
- "maximum": 250
178
- }
179
- },
180
- {
181
- "in": "query",
182
- "name": "cursor",
183
- "schema": {
184
- "type": "string"
185
- }
388
+ "format": "date-time",
389
+ "description": "End of the time window (exclusive). Must be after from."
390
+ },
391
+ "required": true
186
392
  }
187
393
  ],
188
394
  "responses": {
189
395
  "200": {
190
- "description": "List of events",
396
+ "description": "Absence adjustments",
191
397
  "content": {
192
398
  "application/json": {
193
399
  "schema": {
194
400
  "type": "object",
195
401
  "properties": {
402
+ "from": {
403
+ "type": "string",
404
+ "format": "date-time"
405
+ },
406
+ "to": {
407
+ "type": "string",
408
+ "format": "date-time"
409
+ },
196
410
  "results": {
197
411
  "type": "array",
198
412
  "items": {
199
- "$ref": "#/components/schemas/CalendarEvent"
413
+ "$ref": "#/components/schemas/AbsenceAdjustment"
200
414
  }
201
- },
202
- "next_cursor": {
203
- "type": "string",
204
- "nullable": true
205
415
  }
206
416
  },
207
417
  "required": [
418
+ "from",
419
+ "to",
208
420
  "results"
209
421
  ]
210
422
  }
211
423
  }
212
424
  }
213
425
  },
214
- "400": {
215
- "description": "Invalid request",
216
- "content": {
217
- "application/json": {
218
- "schema": {
219
- "$ref": "#/components/schemas/Error"
220
- }
221
- }
222
- }
426
+ "400": {
427
+ "description": "Invalid request",
428
+ "content": {
429
+ "application/json": {
430
+ "schema": {
431
+ "$ref": "#/components/schemas/Error"
432
+ }
433
+ }
434
+ }
435
+ }
436
+ }
437
+ },
438
+ "post": {
439
+ "operationId": "createAbsenceAdjustment",
440
+ "summary": "createAbsenceAdjustment",
441
+ "description": "Create a time-bound absence adjustment for a user in the caller organization.",
442
+ "tags": [
443
+ "Absence"
444
+ ],
445
+ "parameters": [
446
+ {
447
+ "in": "path",
448
+ "name": "user_id",
449
+ "schema": {
450
+ "type": "string",
451
+ "minLength": 1
452
+ },
453
+ "required": true
454
+ }
455
+ ],
456
+ "requestBody": {
457
+ "required": true,
458
+ "content": {
459
+ "application/json": {
460
+ "schema": {
461
+ "$ref": "#/components/schemas/CreateAbsenceAdjustmentBody"
462
+ }
463
+ }
464
+ }
465
+ },
466
+ "responses": {
467
+ "201": {
468
+ "description": "Created absence adjustment",
469
+ "content": {
470
+ "application/json": {
471
+ "schema": {
472
+ "$ref": "#/components/schemas/AbsenceAdjustment"
473
+ }
474
+ }
475
+ }
476
+ },
477
+ "400": {
478
+ "description": "Invalid request body",
479
+ "content": {
480
+ "application/json": {
481
+ "schema": {
482
+ "$ref": "#/components/schemas/Error"
483
+ }
484
+ }
485
+ }
486
+ }
487
+ }
488
+ }
489
+ },
490
+ "/v1/calendar/absence/users/{user_id}/adjustments/{adjustment_id}": {
491
+ "get": {
492
+ "operationId": "getAbsenceAdjustment",
493
+ "summary": "getAbsenceAdjustment",
494
+ "description": "Get an absence adjustment by ID.",
495
+ "tags": [
496
+ "Absence"
497
+ ],
498
+ "parameters": [
499
+ {
500
+ "in": "path",
501
+ "name": "user_id",
502
+ "schema": {
503
+ "type": "string",
504
+ "minLength": 1
505
+ },
506
+ "required": true
507
+ },
508
+ {
509
+ "in": "path",
510
+ "name": "adjustment_id",
511
+ "schema": {
512
+ "type": "string",
513
+ "minLength": 1
514
+ },
515
+ "required": true
516
+ }
517
+ ],
518
+ "responses": {
519
+ "200": {
520
+ "description": "Absence adjustment",
521
+ "content": {
522
+ "application/json": {
523
+ "schema": {
524
+ "$ref": "#/components/schemas/AbsenceAdjustment"
525
+ }
526
+ }
527
+ }
528
+ },
529
+ "404": {
530
+ "description": "Not found",
531
+ "content": {
532
+ "application/json": {
533
+ "schema": {
534
+ "$ref": "#/components/schemas/Error"
535
+ }
536
+ }
537
+ }
538
+ }
539
+ }
540
+ },
541
+ "patch": {
542
+ "operationId": "patchAbsenceAdjustment",
543
+ "summary": "patchAbsenceAdjustment",
544
+ "description": "Update an absence adjustment in the caller organization.",
545
+ "tags": [
546
+ "Absence"
547
+ ],
548
+ "parameters": [
549
+ {
550
+ "in": "path",
551
+ "name": "user_id",
552
+ "schema": {
553
+ "type": "string",
554
+ "minLength": 1
555
+ },
556
+ "required": true
557
+ },
558
+ {
559
+ "in": "path",
560
+ "name": "adjustment_id",
561
+ "schema": {
562
+ "type": "string",
563
+ "minLength": 1
564
+ },
565
+ "required": true
566
+ }
567
+ ],
568
+ "requestBody": {
569
+ "required": true,
570
+ "content": {
571
+ "application/json": {
572
+ "schema": {
573
+ "$ref": "#/components/schemas/PatchAbsenceAdjustmentBody"
574
+ }
575
+ }
576
+ }
577
+ },
578
+ "responses": {
579
+ "200": {
580
+ "description": "Updated absence adjustment",
581
+ "content": {
582
+ "application/json": {
583
+ "schema": {
584
+ "$ref": "#/components/schemas/AbsenceAdjustment"
585
+ }
586
+ }
587
+ }
588
+ },
589
+ "400": {
590
+ "description": "Invalid request body",
591
+ "content": {
592
+ "application/json": {
593
+ "schema": {
594
+ "$ref": "#/components/schemas/Error"
595
+ }
596
+ }
597
+ }
598
+ },
599
+ "404": {
600
+ "description": "Not found",
601
+ "content": {
602
+ "application/json": {
603
+ "schema": {
604
+ "$ref": "#/components/schemas/Error"
605
+ }
606
+ }
607
+ }
608
+ }
609
+ }
610
+ },
611
+ "delete": {
612
+ "operationId": "deleteAbsenceAdjustment",
613
+ "summary": "deleteAbsenceAdjustment",
614
+ "description": "Delete an absence adjustment.",
615
+ "tags": [
616
+ "Absence"
617
+ ],
618
+ "parameters": [
619
+ {
620
+ "in": "path",
621
+ "name": "user_id",
622
+ "schema": {
623
+ "type": "string",
624
+ "minLength": 1
625
+ },
626
+ "required": true
627
+ },
628
+ {
629
+ "in": "path",
630
+ "name": "adjustment_id",
631
+ "schema": {
632
+ "type": "string",
633
+ "minLength": 1
634
+ },
635
+ "required": true
636
+ }
637
+ ],
638
+ "responses": {
639
+ "204": {
640
+ "description": "Absence adjustment deleted"
641
+ },
642
+ "404": {
643
+ "description": "Not found",
644
+ "content": {
645
+ "application/json": {
646
+ "schema": {
647
+ "$ref": "#/components/schemas/Error"
648
+ }
649
+ }
650
+ }
651
+ }
652
+ }
653
+ }
654
+ },
655
+ "/v1/calendar/absence/users/{user_id}": {
656
+ "get": {
657
+ "operationId": "getUserAbsence",
658
+ "summary": "getUserAbsence",
659
+ "description": "Get known absence for a user in a time window. absent=false means no known absence, not guaranteed availability.",
660
+ "tags": [
661
+ "Absence"
662
+ ],
663
+ "parameters": [
664
+ {
665
+ "in": "path",
666
+ "name": "user_id",
667
+ "schema": {
668
+ "type": "string",
669
+ "minLength": 1
670
+ },
671
+ "required": true
672
+ },
673
+ {
674
+ "in": "query",
675
+ "name": "from",
676
+ "description": "Start of the time window (inclusive). Maximum window: 31 days.",
677
+ "schema": {
678
+ "type": "string",
679
+ "format": "date-time",
680
+ "description": "Start of the time window (inclusive). Maximum window: 31 days."
681
+ },
682
+ "required": true
683
+ },
684
+ {
685
+ "in": "query",
686
+ "name": "to",
687
+ "description": "End of the time window (exclusive). Must be after from.",
688
+ "schema": {
689
+ "type": "string",
690
+ "format": "date-time",
691
+ "description": "End of the time window (exclusive). Must be after from."
692
+ },
693
+ "required": true
694
+ },
695
+ {
696
+ "in": "query",
697
+ "name": "include_busy",
698
+ "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.",
699
+ "schema": {
700
+ "type": "boolean",
701
+ "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.",
702
+ "default": false
703
+ }
704
+ }
705
+ ],
706
+ "responses": {
707
+ "200": {
708
+ "description": "User absence",
709
+ "content": {
710
+ "application/json": {
711
+ "schema": {
712
+ "type": "object",
713
+ "properties": {
714
+ "absent": {
715
+ "type": "boolean"
716
+ },
717
+ "absence_intervals": {
718
+ "type": "array",
719
+ "items": {
720
+ "$ref": "#/components/schemas/AbsenceInterval"
721
+ }
722
+ },
723
+ "from": {
724
+ "type": "string",
725
+ "format": "date-time"
726
+ },
727
+ "to": {
728
+ "type": "string",
729
+ "format": "date-time"
730
+ },
731
+ "user_id": {
732
+ "type": "string"
733
+ },
734
+ "external_calendars": {
735
+ "type": "array",
736
+ "items": {
737
+ "$ref": "#/components/schemas/ExternalCalendar"
738
+ },
739
+ "description": "The user's connected external calendars (Outlook/Google), if any."
740
+ }
741
+ },
742
+ "required": [
743
+ "absent",
744
+ "absence_intervals",
745
+ "from",
746
+ "to",
747
+ "user_id"
748
+ ]
749
+ }
750
+ }
751
+ }
752
+ },
753
+ "400": {
754
+ "description": "Invalid request",
755
+ "content": {
756
+ "application/json": {
757
+ "schema": {
758
+ "$ref": "#/components/schemas/Error"
759
+ }
760
+ }
761
+ }
762
+ }
763
+ }
764
+ }
765
+ },
766
+ "/v1/calendar": {
767
+ "get": {
768
+ "operationId": "listCalendars",
769
+ "summary": "listCalendars",
770
+ "description": "List calendars visible to the caller.",
771
+ "tags": [
772
+ "Calendars"
773
+ ],
774
+ "parameters": [
775
+ {
776
+ "in": "query",
777
+ "name": "size",
778
+ "schema": {
779
+ "type": "integer",
780
+ "minimum": 1,
781
+ "maximum": 250
782
+ }
783
+ },
784
+ {
785
+ "in": "query",
786
+ "name": "cursor",
787
+ "description": "Opaque cursor from a previous response",
788
+ "schema": {
789
+ "type": "string",
790
+ "description": "Opaque cursor from a previous response"
791
+ }
792
+ }
793
+ ],
794
+ "responses": {
795
+ "200": {
796
+ "description": "List of calendars",
797
+ "content": {
798
+ "application/json": {
799
+ "schema": {
800
+ "type": "object",
801
+ "properties": {
802
+ "results": {
803
+ "type": "array",
804
+ "items": {
805
+ "$ref": "#/components/schemas/Calendar"
806
+ }
807
+ },
808
+ "next_cursor": {
809
+ "type": "string",
810
+ "nullable": true
811
+ }
812
+ },
813
+ "required": [
814
+ "results"
815
+ ]
816
+ }
817
+ }
818
+ }
819
+ }
820
+ }
821
+ },
822
+ "post": {
823
+ "operationId": "createCalendar",
824
+ "summary": "createCalendar",
825
+ "description": "Create a native epilot calendar.",
826
+ "tags": [
827
+ "Calendars"
828
+ ],
829
+ "requestBody": {
830
+ "required": true,
831
+ "content": {
832
+ "application/json": {
833
+ "schema": {
834
+ "$ref": "#/components/schemas/CalendarCreateBody"
835
+ }
836
+ }
837
+ }
838
+ },
839
+ "responses": {
840
+ "201": {
841
+ "description": "Calendar created",
842
+ "content": {
843
+ "application/json": {
844
+ "schema": {
845
+ "$ref": "#/components/schemas/Calendar"
846
+ }
847
+ }
848
+ }
849
+ },
850
+ "400": {
851
+ "description": "Invalid request body",
852
+ "content": {
853
+ "application/json": {
854
+ "schema": {
855
+ "$ref": "#/components/schemas/Error"
856
+ }
857
+ }
858
+ }
859
+ }
860
+ }
861
+ }
862
+ },
863
+ "/v1/calendar/sources/outlook": {
864
+ "post": {
865
+ "operationId": "addOutlookCalendar",
866
+ "summary": "addOutlookCalendar",
867
+ "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.",
868
+ "tags": [
869
+ "Calendars"
870
+ ],
871
+ "requestBody": {
872
+ "required": true,
873
+ "content": {
874
+ "application/json": {
875
+ "schema": {
876
+ "$ref": "#/components/schemas/AddOutlookCalendarRequest"
877
+ }
878
+ }
879
+ }
880
+ },
881
+ "responses": {
882
+ "201": {
883
+ "description": "Calendar created",
884
+ "content": {
885
+ "application/json": {
886
+ "schema": {
887
+ "$ref": "#/components/schemas/Calendar"
888
+ }
889
+ }
890
+ }
891
+ },
892
+ "400": {
893
+ "description": "Invalid request body",
894
+ "content": {
895
+ "application/json": {
896
+ "schema": {
897
+ "$ref": "#/components/schemas/Error"
898
+ }
899
+ }
900
+ }
901
+ },
902
+ "404": {
903
+ "description": "Caller has not connected their personal Outlook calendar yet",
904
+ "content": {
905
+ "application/json": {
906
+ "schema": {
907
+ "$ref": "#/components/schemas/Error"
908
+ }
909
+ }
910
+ }
911
+ }
912
+ }
913
+ }
914
+ },
915
+ "/v1/calendar/sources/outlook/available": {
916
+ "get": {
917
+ "operationId": "listOutlookCalendars",
918
+ "summary": "listOutlookCalendars",
919
+ "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 }`).",
920
+ "tags": [
921
+ "Calendars"
922
+ ],
923
+ "responses": {
924
+ "200": {
925
+ "description": "List of available Outlook calendars",
926
+ "content": {
927
+ "application/json": {
928
+ "schema": {
929
+ "type": "object",
930
+ "properties": {
931
+ "calendars": {
932
+ "type": "array",
933
+ "items": {
934
+ "$ref": "#/components/schemas/AvailableOutlookCalendar"
935
+ }
936
+ }
937
+ },
938
+ "required": [
939
+ "calendars"
940
+ ]
941
+ }
942
+ }
943
+ }
944
+ },
945
+ "404": {
946
+ "description": "Caller has not connected their personal Outlook calendar yet",
947
+ "content": {
948
+ "application/json": {
949
+ "schema": {
950
+ "$ref": "#/components/schemas/Error"
951
+ }
952
+ }
953
+ }
954
+ }
955
+ }
956
+ }
957
+ },
958
+ "/v1/calendar/sources/outlook/{calendar_id}": {
959
+ "delete": {
960
+ "operationId": "deleteOutlookCalendar",
961
+ "summary": "deleteOutlookCalendar",
962
+ "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.",
963
+ "tags": [
964
+ "Calendars"
965
+ ],
966
+ "parameters": [
967
+ {
968
+ "in": "path",
969
+ "name": "calendar_id",
970
+ "schema": {
971
+ "type": "string"
972
+ },
973
+ "required": true
974
+ }
975
+ ],
976
+ "responses": {
977
+ "204": {
978
+ "description": "Outlook calendar disconnected"
979
+ },
980
+ "404": {
981
+ "description": "No Outlook calendar registration found for this id",
982
+ "content": {
983
+ "application/json": {
984
+ "schema": {
985
+ "$ref": "#/components/schemas/Error"
986
+ }
987
+ }
988
+ }
989
+ },
990
+ "502": {
991
+ "description": "Microsoft Graph subscription deletion failed",
992
+ "content": {
993
+ "application/json": {
994
+ "schema": {
995
+ "$ref": "#/components/schemas/Error"
996
+ }
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ }
1002
+ },
1003
+ "/v1/calendar/outlook/webhook": {
1004
+ "post": {
1005
+ "operationId": "outlookWebhook",
1006
+ "summary": "outlookWebhook",
1007
+ "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.",
1008
+ "tags": [
1009
+ "Calendars"
1010
+ ],
1011
+ "security": [],
1012
+ "parameters": [
1013
+ {
1014
+ "in": "query",
1015
+ "name": "validationToken",
1016
+ "description": "Set by Graph on the subscription-validation handshake; echoed back verbatim.",
1017
+ "schema": {
1018
+ "type": "string",
1019
+ "description": "Set by Graph on the subscription-validation handshake; echoed back verbatim."
1020
+ }
1021
+ }
1022
+ ],
1023
+ "responses": {
1024
+ "200": {
1025
+ "description": "Validation handshake echo (text/plain)",
1026
+ "content": {
1027
+ "text/plain": {
1028
+ "schema": {
1029
+ "type": "string"
1030
+ }
1031
+ }
1032
+ }
1033
+ },
1034
+ "202": {
1035
+ "description": "Notification batch accepted"
1036
+ }
1037
+ }
1038
+ }
1039
+ },
1040
+ "/v1/calendar/{calendar_id}": {
1041
+ "get": {
1042
+ "operationId": "getCalendar",
1043
+ "summary": "getCalendar",
1044
+ "description": "Get a single calendar by its epilot ID.",
1045
+ "tags": [
1046
+ "Calendars"
1047
+ ],
1048
+ "parameters": [
1049
+ {
1050
+ "in": "path",
1051
+ "name": "calendar_id",
1052
+ "schema": {
1053
+ "type": "string"
1054
+ },
1055
+ "required": true
1056
+ }
1057
+ ],
1058
+ "responses": {
1059
+ "200": {
1060
+ "description": "Calendar",
1061
+ "content": {
1062
+ "application/json": {
1063
+ "schema": {
1064
+ "$ref": "#/components/schemas/Calendar"
1065
+ }
1066
+ }
1067
+ }
1068
+ },
1069
+ "404": {
1070
+ "description": "Resource not found",
1071
+ "content": {
1072
+ "application/json": {
1073
+ "schema": {
1074
+ "$ref": "#/components/schemas/Error"
1075
+ }
1076
+ }
1077
+ }
1078
+ }
1079
+ }
1080
+ },
1081
+ "patch": {
1082
+ "operationId": "updateCalendar",
1083
+ "summary": "updateCalendar",
1084
+ "description": "Update fields on a calendar.",
1085
+ "tags": [
1086
+ "Calendars"
1087
+ ],
1088
+ "parameters": [
1089
+ {
1090
+ "in": "path",
1091
+ "name": "calendar_id",
1092
+ "schema": {
1093
+ "type": "string"
1094
+ },
1095
+ "required": true
1096
+ }
1097
+ ],
1098
+ "requestBody": {
1099
+ "required": true,
1100
+ "content": {
1101
+ "application/json": {
1102
+ "schema": {
1103
+ "$ref": "#/components/schemas/CalendarPatchBody"
1104
+ }
1105
+ }
1106
+ }
1107
+ },
1108
+ "responses": {
1109
+ "200": {
1110
+ "description": "Calendar updated",
1111
+ "content": {
1112
+ "application/json": {
1113
+ "schema": {
1114
+ "$ref": "#/components/schemas/Calendar"
1115
+ }
1116
+ }
1117
+ }
1118
+ },
1119
+ "400": {
1120
+ "description": "Invalid request body",
1121
+ "content": {
1122
+ "application/json": {
1123
+ "schema": {
1124
+ "$ref": "#/components/schemas/Error"
1125
+ }
1126
+ }
1127
+ }
1128
+ },
1129
+ "403": {
1130
+ "description": "Calendar is read-only",
1131
+ "content": {
1132
+ "application/json": {
1133
+ "schema": {
1134
+ "$ref": "#/components/schemas/Error"
1135
+ }
1136
+ }
1137
+ }
1138
+ },
1139
+ "404": {
1140
+ "description": "Resource not found",
1141
+ "content": {
1142
+ "application/json": {
1143
+ "schema": {
1144
+ "$ref": "#/components/schemas/Error"
1145
+ }
1146
+ }
1147
+ }
1148
+ }
1149
+ }
1150
+ },
1151
+ "delete": {
1152
+ "operationId": "deleteCalendar",
1153
+ "summary": "deleteCalendar",
1154
+ "description": "Delete a native epilot calendar and its events.",
1155
+ "tags": [
1156
+ "Calendars"
1157
+ ],
1158
+ "parameters": [
1159
+ {
1160
+ "in": "path",
1161
+ "name": "calendar_id",
1162
+ "schema": {
1163
+ "type": "string"
1164
+ },
1165
+ "required": true
1166
+ }
1167
+ ],
1168
+ "responses": {
1169
+ "204": {
1170
+ "description": "Calendar deleted"
1171
+ },
1172
+ "403": {
1173
+ "description": "Calendar is read-only",
1174
+ "content": {
1175
+ "application/json": {
1176
+ "schema": {
1177
+ "$ref": "#/components/schemas/Error"
1178
+ }
1179
+ }
1180
+ }
1181
+ },
1182
+ "404": {
1183
+ "description": "Resource not found",
1184
+ "content": {
1185
+ "application/json": {
1186
+ "schema": {
1187
+ "$ref": "#/components/schemas/Error"
1188
+ }
1189
+ }
1190
+ }
1191
+ }
1192
+ }
1193
+ }
1194
+ },
1195
+ "/v1/calendar/events": {
1196
+ "get": {
1197
+ "operationId": "listEvents",
1198
+ "summary": "listEvents",
1199
+ "description": "List events in a time window. Recurring events are returned as expanded occurrences – each instance appears as its own event.",
1200
+ "tags": [
1201
+ "Calendar Events"
1202
+ ],
1203
+ "parameters": [
1204
+ {
1205
+ "in": "query",
1206
+ "name": "from",
1207
+ "description": "Start of the time window (inclusive)",
1208
+ "schema": {
1209
+ "type": "string",
1210
+ "format": "date-time",
1211
+ "description": "Start of the time window (inclusive)"
1212
+ },
1213
+ "required": true
1214
+ },
1215
+ {
1216
+ "in": "query",
1217
+ "name": "to",
1218
+ "description": "End of the time window (exclusive)",
1219
+ "schema": {
1220
+ "type": "string",
1221
+ "format": "date-time",
1222
+ "description": "End of the time window (exclusive)"
1223
+ },
1224
+ "required": true
1225
+ },
1226
+ {
1227
+ "in": "query",
1228
+ "name": "calendar_id",
1229
+ "description": "Filter to a single calendar. Omit to query across all accessible calendars.",
1230
+ "schema": {
1231
+ "type": "string",
1232
+ "description": "Filter to a single calendar. Omit to query across all accessible calendars."
1233
+ }
1234
+ },
1235
+ {
1236
+ "in": "query",
1237
+ "name": "size",
1238
+ "schema": {
1239
+ "type": "integer",
1240
+ "minimum": 1,
1241
+ "maximum": 250
1242
+ }
1243
+ },
1244
+ {
1245
+ "in": "query",
1246
+ "name": "cursor",
1247
+ "schema": {
1248
+ "type": "string"
1249
+ }
1250
+ }
1251
+ ],
1252
+ "responses": {
1253
+ "200": {
1254
+ "description": "List of events",
1255
+ "content": {
1256
+ "application/json": {
1257
+ "schema": {
1258
+ "type": "object",
1259
+ "properties": {
1260
+ "results": {
1261
+ "type": "array",
1262
+ "items": {
1263
+ "$ref": "#/components/schemas/CalendarEvent"
1264
+ }
1265
+ },
1266
+ "next_cursor": {
1267
+ "type": "string",
1268
+ "nullable": true
1269
+ }
1270
+ },
1271
+ "required": [
1272
+ "results"
1273
+ ]
1274
+ }
1275
+ }
1276
+ }
1277
+ },
1278
+ "400": {
1279
+ "description": "Invalid request",
1280
+ "content": {
1281
+ "application/json": {
1282
+ "schema": {
1283
+ "$ref": "#/components/schemas/Error"
1284
+ }
1285
+ }
1286
+ }
1287
+ }
1288
+ }
1289
+ },
1290
+ "post": {
1291
+ "operationId": "createEvent",
1292
+ "summary": "createEvent",
1293
+ "description": "Create a native epilot calendar event.",
1294
+ "tags": [
1295
+ "Calendar Events"
1296
+ ],
1297
+ "requestBody": {
1298
+ "required": true,
1299
+ "content": {
1300
+ "application/json": {
1301
+ "schema": {
1302
+ "$ref": "#/components/schemas/CalendarEventCreateBody"
1303
+ }
1304
+ }
1305
+ }
1306
+ },
1307
+ "responses": {
1308
+ "201": {
1309
+ "description": "Event created",
1310
+ "content": {
1311
+ "application/json": {
1312
+ "schema": {
1313
+ "$ref": "#/components/schemas/CalendarEvent"
1314
+ }
1315
+ }
1316
+ }
1317
+ },
1318
+ "400": {
1319
+ "description": "Invalid request body",
1320
+ "content": {
1321
+ "application/json": {
1322
+ "schema": {
1323
+ "$ref": "#/components/schemas/Error"
1324
+ }
1325
+ }
1326
+ }
1327
+ },
1328
+ "403": {
1329
+ "description": "Calendar is read-only",
1330
+ "content": {
1331
+ "application/json": {
1332
+ "schema": {
1333
+ "$ref": "#/components/schemas/Error"
1334
+ }
1335
+ }
1336
+ }
1337
+ },
1338
+ "404": {
1339
+ "description": "Calendar not found",
1340
+ "content": {
1341
+ "application/json": {
1342
+ "schema": {
1343
+ "$ref": "#/components/schemas/Error"
1344
+ }
1345
+ }
1346
+ }
1347
+ }
1348
+ }
1349
+ }
1350
+ },
1351
+ "/v1/calendar/events/{event_id}": {
1352
+ "get": {
1353
+ "operationId": "getEvent",
1354
+ "summary": "getEvent",
1355
+ "description": "Get a single event by its epilot ID.",
1356
+ "tags": [
1357
+ "Calendar Events"
1358
+ ],
1359
+ "parameters": [
1360
+ {
1361
+ "in": "path",
1362
+ "name": "event_id",
1363
+ "schema": {
1364
+ "type": "string"
1365
+ },
1366
+ "required": true
1367
+ }
1368
+ ],
1369
+ "responses": {
1370
+ "200": {
1371
+ "description": "Event",
1372
+ "content": {
1373
+ "application/json": {
1374
+ "schema": {
1375
+ "$ref": "#/components/schemas/CalendarEvent"
1376
+ }
1377
+ }
1378
+ }
1379
+ },
1380
+ "404": {
1381
+ "description": "Resource not found",
1382
+ "content": {
1383
+ "application/json": {
1384
+ "schema": {
1385
+ "$ref": "#/components/schemas/Error"
1386
+ }
1387
+ }
1388
+ }
1389
+ }
1390
+ }
1391
+ },
1392
+ "patch": {
1393
+ "operationId": "updateEvent",
1394
+ "summary": "updateEvent",
1395
+ "description": "Update a native epilot calendar event.",
1396
+ "tags": [
1397
+ "Calendar Events"
1398
+ ],
1399
+ "parameters": [
1400
+ {
1401
+ "in": "path",
1402
+ "name": "event_id",
1403
+ "schema": {
1404
+ "type": "string"
1405
+ },
1406
+ "required": true
1407
+ }
1408
+ ],
1409
+ "requestBody": {
1410
+ "required": true,
1411
+ "content": {
1412
+ "application/json": {
1413
+ "schema": {
1414
+ "$ref": "#/components/schemas/CalendarEventPatchBody"
1415
+ }
1416
+ }
1417
+ }
1418
+ },
1419
+ "responses": {
1420
+ "200": {
1421
+ "description": "Event updated",
1422
+ "content": {
1423
+ "application/json": {
1424
+ "schema": {
1425
+ "$ref": "#/components/schemas/CalendarEvent"
1426
+ }
1427
+ }
1428
+ }
1429
+ },
1430
+ "400": {
1431
+ "description": "Invalid request body",
1432
+ "content": {
1433
+ "application/json": {
1434
+ "schema": {
1435
+ "$ref": "#/components/schemas/Error"
1436
+ }
1437
+ }
1438
+ }
1439
+ },
1440
+ "403": {
1441
+ "description": "Event or calendar is read-only",
1442
+ "content": {
1443
+ "application/json": {
1444
+ "schema": {
1445
+ "$ref": "#/components/schemas/Error"
1446
+ }
1447
+ }
1448
+ }
1449
+ },
1450
+ "404": {
1451
+ "description": "Resource not found",
1452
+ "content": {
1453
+ "application/json": {
1454
+ "schema": {
1455
+ "$ref": "#/components/schemas/Error"
1456
+ }
1457
+ }
1458
+ }
1459
+ }
1460
+ }
1461
+ },
1462
+ "delete": {
1463
+ "operationId": "deleteEvent",
1464
+ "summary": "deleteEvent",
1465
+ "description": "Delete a native epilot calendar event.",
1466
+ "tags": [
1467
+ "Calendar Events"
1468
+ ],
1469
+ "parameters": [
1470
+ {
1471
+ "in": "path",
1472
+ "name": "event_id",
1473
+ "schema": {
1474
+ "type": "string"
1475
+ },
1476
+ "required": true
1477
+ }
1478
+ ],
1479
+ "responses": {
1480
+ "204": {
1481
+ "description": "Event deleted"
1482
+ },
1483
+ "403": {
1484
+ "description": "Event or calendar is read-only",
1485
+ "content": {
1486
+ "application/json": {
1487
+ "schema": {
1488
+ "$ref": "#/components/schemas/Error"
1489
+ }
1490
+ }
1491
+ }
1492
+ },
1493
+ "404": {
1494
+ "description": "Resource not found",
1495
+ "content": {
1496
+ "application/json": {
1497
+ "schema": {
1498
+ "$ref": "#/components/schemas/Error"
1499
+ }
1500
+ }
1501
+ }
1502
+ }
1503
+ }
1504
+ }
1505
+ },
1506
+ "/v1/calendar/events/{event_id}/share": {
1507
+ "post": {
1508
+ "operationId": "shareEvent",
1509
+ "summary": "shareEvent",
1510
+ "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.",
1511
+ "tags": [
1512
+ "Calendar Events"
1513
+ ],
1514
+ "parameters": [
1515
+ {
1516
+ "in": "path",
1517
+ "name": "event_id",
1518
+ "schema": {
1519
+ "type": "string"
1520
+ },
1521
+ "required": true
1522
+ }
1523
+ ],
1524
+ "requestBody": {
1525
+ "required": true,
1526
+ "content": {
1527
+ "application/json": {
1528
+ "schema": {
1529
+ "$ref": "#/components/schemas/ShareEventBody"
1530
+ }
1531
+ }
1532
+ }
1533
+ },
1534
+ "responses": {
1535
+ "204": {
1536
+ "description": "Event shared"
1537
+ },
1538
+ "400": {
1539
+ "description": "Invalid request body",
1540
+ "content": {
1541
+ "application/json": {
1542
+ "schema": {
1543
+ "$ref": "#/components/schemas/Error"
1544
+ }
1545
+ }
1546
+ }
1547
+ },
1548
+ "403": {
1549
+ "description": "Caller is not the event owner",
1550
+ "content": {
1551
+ "application/json": {
1552
+ "schema": {
1553
+ "$ref": "#/components/schemas/Error"
1554
+ }
1555
+ }
1556
+ }
1557
+ },
1558
+ "404": {
1559
+ "description": "Event or recipient not found",
1560
+ "content": {
1561
+ "application/json": {
1562
+ "schema": {
1563
+ "$ref": "#/components/schemas/Error"
1564
+ }
1565
+ }
1566
+ }
1567
+ }
1568
+ }
1569
+ }
1570
+ },
1571
+ "/v1/calendar/events/{event_id}/share/{user_id}": {
1572
+ "delete": {
1573
+ "operationId": "unshareEvent",
1574
+ "summary": "unshareEvent",
1575
+ "description": "Revoke a per-event share. The recipient loses access immediately. Owner-only.",
1576
+ "tags": [
1577
+ "Calendar Events"
1578
+ ],
1579
+ "parameters": [
1580
+ {
1581
+ "in": "path",
1582
+ "name": "event_id",
1583
+ "schema": {
1584
+ "type": "string"
1585
+ },
1586
+ "required": true
1587
+ },
1588
+ {
1589
+ "in": "path",
1590
+ "name": "user_id",
1591
+ "schema": {
1592
+ "type": "string"
1593
+ },
1594
+ "required": true
1595
+ }
1596
+ ],
1597
+ "responses": {
1598
+ "204": {
1599
+ "description": "Share revoked (or was not present)"
1600
+ },
1601
+ "403": {
1602
+ "description": "Caller is not the event owner",
1603
+ "content": {
1604
+ "application/json": {
1605
+ "schema": {
1606
+ "$ref": "#/components/schemas/Error"
1607
+ }
1608
+ }
1609
+ }
1610
+ },
1611
+ "404": {
1612
+ "description": "Event not found",
1613
+ "content": {
1614
+ "application/json": {
1615
+ "schema": {
1616
+ "$ref": "#/components/schemas/Error"
1617
+ }
1618
+ }
1619
+ }
1620
+ }
1621
+ }
1622
+ }
1623
+ }
1624
+ },
1625
+ "components": {
1626
+ "securitySchemes": {
1627
+ "EpilotAuth": {
1628
+ "type": "http",
1629
+ "scheme": "bearer",
1630
+ "description": "Authorization header with epilot OAuth2 bearer token",
1631
+ "bearerFormat": "JWT"
1632
+ },
1633
+ "EpilotOrg": {
1634
+ "type": "apiKey",
1635
+ "in": "header",
1636
+ "name": "x-epilot-org-id"
1637
+ }
1638
+ },
1639
+ "schemas": {
1640
+ "AbsenceInterval": {
1641
+ "type": "object",
1642
+ "properties": {
1643
+ "from": {
1644
+ "type": "string",
1645
+ "format": "date-time",
1646
+ "description": "Absence interval start clipped to the requested window."
1647
+ },
1648
+ "to": {
1649
+ "type": "string",
1650
+ "format": "date-time",
1651
+ "description": "Absence interval end clipped to the requested window."
1652
+ },
1653
+ "original_from": {
1654
+ "type": "string",
1655
+ "format": "date-time",
1656
+ "description": "Original absence interval start before clipping."
1657
+ },
1658
+ "original_to": {
1659
+ "type": "string",
1660
+ "format": "date-time",
1661
+ "description": "Original absence interval end before clipping."
1662
+ },
1663
+ "absent": {
1664
+ "type": "boolean",
1665
+ "description": "Effective absence state for this interval."
1666
+ },
1667
+ "source": {
1668
+ "$ref": "#/components/schemas/AbsenceIntervalSource"
1669
+ },
1670
+ "calendar_event_id": {
1671
+ "type": "string",
1672
+ "description": "calendar_event ID for calendar-derived intervals."
1673
+ },
1674
+ "absence_adjustment_id": {
1675
+ "type": "string",
1676
+ "description": "absence adjustment ID for manual intervals."
1677
+ },
1678
+ "reason": {
1679
+ "type": "string",
1680
+ "nullable": true
1681
+ }
1682
+ },
1683
+ "required": [
1684
+ "from",
1685
+ "to",
1686
+ "original_from",
1687
+ "original_to",
1688
+ "absent",
1689
+ "source"
1690
+ ]
1691
+ },
1692
+ "AbsenceIntervalSource": {
1693
+ "type": "string",
1694
+ "enum": [
1695
+ "calendar_event",
1696
+ "absence_adjustment"
1697
+ ]
1698
+ },
1699
+ "Error": {
1700
+ "type": "object",
1701
+ "properties": {
1702
+ "status": {
1703
+ "type": "integer"
1704
+ },
1705
+ "error": {
1706
+ "anyOf": [
1707
+ {
1708
+ "type": "string"
1709
+ },
1710
+ {
1711
+ "type": "array",
1712
+ "items": {}
1713
+ }
1714
+ ]
1715
+ }
1716
+ },
1717
+ "required": [
1718
+ "status",
1719
+ "error"
1720
+ ]
1721
+ },
1722
+ "SearchAbsenceBody": {
1723
+ "type": "object",
1724
+ "properties": {
1725
+ "from": {
1726
+ "type": "string",
1727
+ "format": "date-time",
1728
+ "description": "Start of the time window (inclusive). Maximum window: 31 days."
1729
+ },
1730
+ "to": {
1731
+ "type": "string",
1732
+ "format": "date-time",
1733
+ "description": "End of the time window (exclusive). Must be after from."
1734
+ },
1735
+ "user_ids": {
1736
+ "type": "array",
1737
+ "items": {
1738
+ "type": "string",
1739
+ "minLength": 1
1740
+ },
1741
+ "minItems": 1,
1742
+ "maxItems": 300,
1743
+ "description": "Candidate users to check for absence inside the caller organization."
1744
+ },
1745
+ "include_busy": {
1746
+ "type": "boolean",
1747
+ "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.",
1748
+ "default": false
1749
+ }
1750
+ },
1751
+ "required": [
1752
+ "from",
1753
+ "to",
1754
+ "user_ids"
1755
+ ]
1756
+ },
1757
+ "SearchNowAbsenceBody": {
1758
+ "type": "object",
1759
+ "properties": {
1760
+ "user_ids": {
1761
+ "type": "array",
1762
+ "items": {
1763
+ "type": "string",
1764
+ "minLength": 1
1765
+ },
1766
+ "minItems": 1,
1767
+ "maxItems": 300,
1768
+ "description": "Candidate users to check for absence at the current server time inside the caller organization."
1769
+ },
1770
+ "include_busy": {
1771
+ "type": "boolean",
1772
+ "description": "Include busy inputs in addition to out-of-office absence. Defaults to false.",
1773
+ "default": false
1774
+ }
1775
+ },
1776
+ "required": [
1777
+ "user_ids"
1778
+ ]
1779
+ },
1780
+ "AbsenceAdjustment": {
1781
+ "type": "object",
1782
+ "properties": {
1783
+ "from": {
1784
+ "type": "string",
1785
+ "format": "date-time",
1786
+ "description": "Adjustment start. Maximum adjustment duration: 7 days."
1787
+ },
1788
+ "to": {
1789
+ "type": "string",
1790
+ "format": "date-time",
1791
+ "description": "Adjustment end. Must be after from."
1792
+ },
1793
+ "absent": {
1794
+ "type": "boolean",
1795
+ "description": "Manual override state: true means absent, false means not absent."
1796
+ },
1797
+ "status": {
1798
+ "$ref": "#/components/schemas/AbsenceStatus"
1799
+ },
1800
+ "type": {
1801
+ "$ref": "#/components/schemas/AbsenceType"
1802
+ },
1803
+ "reason": {
1804
+ "type": "string",
1805
+ "nullable": true,
1806
+ "maxLength": 500
1807
+ },
1808
+ "adjustment_id": {
1809
+ "type": "string"
1810
+ },
1811
+ "user_id": {
1812
+ "type": "string"
1813
+ },
1814
+ "created_by": {
1815
+ "type": "string"
1816
+ },
1817
+ "created_at": {
1818
+ "type": "string",
1819
+ "format": "date-time"
1820
+ },
1821
+ "updated_at": {
1822
+ "type": "string",
1823
+ "format": "date-time"
1824
+ }
1825
+ },
1826
+ "required": [
1827
+ "from",
1828
+ "to",
1829
+ "absent",
1830
+ "status",
1831
+ "adjustment_id",
1832
+ "user_id",
1833
+ "created_by",
1834
+ "created_at",
1835
+ "updated_at"
1836
+ ]
1837
+ },
1838
+ "AbsenceStatus": {
1839
+ "type": "string",
1840
+ "enum": [
1841
+ "busy",
1842
+ "oof"
1843
+ ],
1844
+ "description": "Calendar status targeted by the absence adjustment."
1845
+ },
1846
+ "AbsenceType": {
1847
+ "type": "string",
1848
+ "minLength": 1,
1849
+ "maxLength": 128,
1850
+ "description": "Optional producer-defined reference stored with an absence adjustment."
1851
+ },
1852
+ "CreateAbsenceAdjustmentBody": {
1853
+ "type": "object",
1854
+ "properties": {
1855
+ "from": {
1856
+ "type": "string",
1857
+ "format": "date-time",
1858
+ "description": "Adjustment start. Maximum adjustment duration: 7 days."
1859
+ },
1860
+ "to": {
1861
+ "type": "string",
1862
+ "format": "date-time",
1863
+ "description": "Adjustment end. Must be after from."
1864
+ },
1865
+ "absent": {
1866
+ "type": "boolean",
1867
+ "description": "Manual override state: true means absent, false means not absent."
1868
+ },
1869
+ "status": {
1870
+ "$ref": "#/components/schemas/AbsenceStatus"
1871
+ },
1872
+ "type": {
1873
+ "$ref": "#/components/schemas/AbsenceType"
1874
+ },
1875
+ "reason": {
1876
+ "type": "string",
1877
+ "nullable": true,
1878
+ "maxLength": 500
1879
+ }
1880
+ },
1881
+ "required": [
1882
+ "from",
1883
+ "to",
1884
+ "absent",
1885
+ "status"
1886
+ ]
1887
+ },
1888
+ "PatchAbsenceAdjustmentBody": {
1889
+ "type": "object",
1890
+ "properties": {
1891
+ "from": {
1892
+ "type": "string",
1893
+ "format": "date-time",
1894
+ "description": "Adjustment start. Maximum adjustment duration: 7 days."
1895
+ },
1896
+ "to": {
1897
+ "type": "string",
1898
+ "format": "date-time",
1899
+ "description": "Adjustment end. Must be after from."
1900
+ },
1901
+ "absent": {
1902
+ "type": "boolean",
1903
+ "description": "Manual override state: true means absent, false means not absent."
1904
+ },
1905
+ "status": {
1906
+ "$ref": "#/components/schemas/AbsenceStatus"
1907
+ },
1908
+ "type": {
1909
+ "$ref": "#/components/schemas/AbsenceType"
1910
+ },
1911
+ "reason": {
1912
+ "type": "string",
1913
+ "nullable": true,
1914
+ "maxLength": 500
223
1915
  }
224
1916
  }
225
- }
226
- },
227
- "/v1/calendar/events/{event_id}": {
228
- "get": {
229
- "operationId": "getEvent",
230
- "summary": "getEvent",
231
- "description": "Get a single event by its epilot ID.",
232
- "tags": [
233
- "Events"
234
- ],
235
- "parameters": [
236
- {
237
- "in": "path",
238
- "name": "event_id",
239
- "schema": {
240
- "type": "string"
241
- },
242
- "required": true
243
- }
244
- ],
245
- "responses": {
246
- "200": {
247
- "description": "Event",
248
- "content": {
249
- "application/json": {
250
- "schema": {
251
- "$ref": "#/components/schemas/CalendarEvent"
252
- }
253
- }
254
- }
1917
+ },
1918
+ "ExternalCalendar": {
1919
+ "type": "object",
1920
+ "properties": {
1921
+ "provider": {
1922
+ "type": "string",
1923
+ "enum": [
1924
+ "outlook",
1925
+ "google"
1926
+ ],
1927
+ "description": "External calendar provider."
255
1928
  },
256
- "404": {
257
- "description": "Resource not found",
258
- "content": {
259
- "application/json": {
260
- "schema": {
261
- "$ref": "#/components/schemas/Error"
262
- }
263
- }
264
- }
1929
+ "last_synced_at": {
1930
+ "type": "string",
1931
+ "nullable": true,
1932
+ "format": "date-time",
1933
+ "description": "Last successful sync of this provider calendar, if any."
265
1934
  }
266
- }
267
- }
268
- }
269
- },
270
- "components": {
271
- "securitySchemes": {
272
- "EpilotAuth": {
273
- "type": "http",
274
- "scheme": "bearer",
275
- "description": "Authorization header with epilot OAuth2 bearer token",
276
- "bearerFormat": "JWT"
1935
+ },
1936
+ "required": [
1937
+ "provider",
1938
+ "last_synced_at"
1939
+ ]
277
1940
  },
278
- "EpilotOrg": {
279
- "type": "apiKey",
280
- "in": "header",
281
- "name": "x-epilot-org-id"
282
- }
283
- },
284
- "schemas": {
285
1941
  "Calendar": {
286
1942
  "type": "object",
287
1943
  "properties": {
@@ -304,7 +1960,8 @@
304
1960
  "description": "Computed display title"
305
1961
  },
306
1962
  "name": {
307
- "type": "string"
1963
+ "type": "string",
1964
+ "minLength": 1
308
1965
  },
309
1966
  "description": {
310
1967
  "type": "string",
@@ -388,20 +2045,93 @@
388
2045
  ],
389
2046
  "description": "Null for native epilot calendars"
390
2047
  },
391
- "Error": {
2048
+ "CalendarCreateBody": {
392
2049
  "type": "object",
393
2050
  "properties": {
394
- "message": {
395
- "type": "string"
2051
+ "name": {
2052
+ "type": "string",
2053
+ "minLength": 1
396
2054
  },
397
- "code": {
2055
+ "description": {
2056
+ "type": "string",
2057
+ "nullable": true
2058
+ },
2059
+ "color": {
2060
+ "type": "string",
2061
+ "nullable": true,
2062
+ "description": "Hex color (e.g. \"#3b82f6\") or provider-defined named color"
2063
+ }
2064
+ },
2065
+ "required": [
2066
+ "name"
2067
+ ]
2068
+ },
2069
+ "AddOutlookCalendarRequest": {
2070
+ "type": "object",
2071
+ "properties": {
2072
+ "provider_calendar_id": {
2073
+ "type": "string",
2074
+ "minLength": 1
2075
+ },
2076
+ "name": {
2077
+ "type": "string",
2078
+ "minLength": 1,
2079
+ "description": "Optional override; defaults to the provider calendar name."
2080
+ }
2081
+ },
2082
+ "required": [
2083
+ "provider_calendar_id"
2084
+ ]
2085
+ },
2086
+ "AvailableOutlookCalendar": {
2087
+ "type": "object",
2088
+ "properties": {
2089
+ "provider_calendar_id": {
2090
+ "type": "string",
2091
+ "description": "Microsoft Graph calendar id (immutable, e.g. base64-encoded)"
2092
+ },
2093
+ "name": {
398
2094
  "type": "string"
2095
+ },
2096
+ "color": {
2097
+ "type": "string",
2098
+ "nullable": true
2099
+ },
2100
+ "is_default": {
2101
+ "type": "boolean"
2102
+ },
2103
+ "can_edit": {
2104
+ "type": "boolean"
2105
+ },
2106
+ "owner": {
2107
+ "type": "string",
2108
+ "nullable": true,
2109
+ "description": "Owner email address on the Microsoft side"
399
2110
  }
400
2111
  },
401
2112
  "required": [
402
- "message"
2113
+ "provider_calendar_id",
2114
+ "name"
403
2115
  ]
404
2116
  },
2117
+ "CalendarPatchBody": {
2118
+ "type": "object",
2119
+ "properties": {
2120
+ "name": {
2121
+ "type": "string",
2122
+ "minLength": 1
2123
+ },
2124
+ "description": {
2125
+ "type": "string",
2126
+ "nullable": true
2127
+ },
2128
+ "color": {
2129
+ "type": "string",
2130
+ "nullable": true,
2131
+ "description": "Hex color (e.g. \"#3b82f6\") or provider-defined named color"
2132
+ }
2133
+ }
2134
+ },
405
2135
  "CalendarEvent": {
406
2136
  "type": "object",
407
2137
  "properties": {
@@ -432,6 +2162,7 @@
432
2162
  },
433
2163
  "calendar_id": {
434
2164
  "type": "string",
2165
+ "minLength": 1,
435
2166
  "description": "epilot calendar this event belongs to"
436
2167
  },
437
2168
  "event_type": {
@@ -455,6 +2186,7 @@
455
2186
  },
456
2187
  "timezone": {
457
2188
  "type": "string",
2189
+ "minLength": 1,
458
2190
  "description": "IANA timezone of the original event (for display)"
459
2191
  },
460
2192
  "is_all_day": {
@@ -669,6 +2401,120 @@
669
2401
  "required": [
670
2402
  "type"
671
2403
  ]
2404
+ },
2405
+ "CalendarEventCreateBody": {
2406
+ "type": "object",
2407
+ "properties": {
2408
+ "calendar_id": {
2409
+ "type": "string",
2410
+ "minLength": 1,
2411
+ "description": "epilot calendar this event belongs to"
2412
+ },
2413
+ "description": {
2414
+ "type": "string",
2415
+ "nullable": true,
2416
+ "description": "Preview of the event body, truncated to 255 chars",
2417
+ "maxLength": 255
2418
+ },
2419
+ "start_time": {
2420
+ "type": "string",
2421
+ "format": "date-time",
2422
+ "description": "Event start in UTC"
2423
+ },
2424
+ "end_time": {
2425
+ "type": "string",
2426
+ "format": "date-time",
2427
+ "description": "Event end in UTC"
2428
+ },
2429
+ "timezone": {
2430
+ "type": "string",
2431
+ "minLength": 1,
2432
+ "description": "IANA timezone of the original event (for display)"
2433
+ },
2434
+ "is_all_day": {
2435
+ "type": "boolean"
2436
+ },
2437
+ "location": {
2438
+ "type": "string",
2439
+ "nullable": true
2440
+ },
2441
+ "status": {
2442
+ "$ref": "#/components/schemas/EventStatus"
2443
+ },
2444
+ "sensitivity": {
2445
+ "$ref": "#/components/schemas/Sensitivity"
2446
+ },
2447
+ "_title": {
2448
+ "type": "string",
2449
+ "minLength": 1
2450
+ }
2451
+ },
2452
+ "required": [
2453
+ "calendar_id",
2454
+ "start_time",
2455
+ "end_time",
2456
+ "timezone",
2457
+ "is_all_day",
2458
+ "status",
2459
+ "sensitivity",
2460
+ "_title"
2461
+ ]
2462
+ },
2463
+ "CalendarEventPatchBody": {
2464
+ "type": "object",
2465
+ "properties": {
2466
+ "description": {
2467
+ "type": "string",
2468
+ "nullable": true,
2469
+ "description": "Preview of the event body, truncated to 255 chars",
2470
+ "maxLength": 255
2471
+ },
2472
+ "start_time": {
2473
+ "type": "string",
2474
+ "format": "date-time",
2475
+ "description": "Event start in UTC"
2476
+ },
2477
+ "end_time": {
2478
+ "type": "string",
2479
+ "format": "date-time",
2480
+ "description": "Event end in UTC"
2481
+ },
2482
+ "timezone": {
2483
+ "type": "string",
2484
+ "minLength": 1,
2485
+ "description": "IANA timezone of the original event (for display)"
2486
+ },
2487
+ "is_all_day": {
2488
+ "type": "boolean"
2489
+ },
2490
+ "location": {
2491
+ "type": "string",
2492
+ "nullable": true
2493
+ },
2494
+ "status": {
2495
+ "$ref": "#/components/schemas/EventStatus"
2496
+ },
2497
+ "sensitivity": {
2498
+ "$ref": "#/components/schemas/Sensitivity"
2499
+ },
2500
+ "_title": {
2501
+ "type": "string",
2502
+ "minLength": 1
2503
+ }
2504
+ }
2505
+ },
2506
+ "ShareEventBody": {
2507
+ "type": "object",
2508
+ "properties": {
2509
+ "user_id": {
2510
+ "type": "string",
2511
+ "minLength": 1,
2512
+ "description": "epilot user id (same organization) to grant view-only access to this event"
2513
+ }
2514
+ },
2515
+ "required": [
2516
+ "user_id"
2517
+ ]
672
2518
  }
673
2519
  }
674
2520
  }