@remit/api-openapi-spec 0.0.36 → 0.0.38

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.
Files changed (2) hide show
  1. package/openapi.json +2297 -634
  2. package/package.json +1 -1
package/openapi.json CHANGED
@@ -1413,18 +1413,69 @@
1413
1413
  }
1414
1414
  }
1415
1415
  },
1416
- "/config": {
1416
+ "/calendar-events": {
1417
1417
  "get": {
1418
- "operationId": "ConfigOperations_getConfig",
1419
- "description": "Get the current user's configuration including accounts and addresses",
1420
- "parameters": [],
1418
+ "operationId": "CalendarEventOperations_listCalendarEvents",
1419
+ "description": "Expanded event occurrences in a window. The server does the recurrence\nexpansion and the client renders instances — no RRULE is ever evaluated\nin a browser, so a week grid and a day strip agree with each other and\nwith what a native client will later read over CalDAV.\n\nOccurrences are served from the stored occurrence index. A series whose\nindex stops before `to` — an open-ended event older than the expansion\nhorizon — is expanded live for this window instead, and nothing\nunbounded is written to serve a read.\n\nRefused with 400 when `from` and `to` are not ISO 8601 date-times, when\n`to` is not after `from`, or when the window is wider than a year.",
1420
+ "parameters": [
1421
+ {
1422
+ "name": "from",
1423
+ "in": "query",
1424
+ "required": true,
1425
+ "description": "Start of the window, an ISO 8601 date-time with zone offset. Inclusive.",
1426
+ "schema": {
1427
+ "type": "string"
1428
+ }
1429
+ },
1430
+ {
1431
+ "name": "to",
1432
+ "in": "query",
1433
+ "required": true,
1434
+ "description": "End of the window, an ISO 8601 date-time with zone offset. Exclusive.",
1435
+ "schema": {
1436
+ "type": "string"
1437
+ }
1438
+ },
1439
+ {
1440
+ "name": "calendarId",
1441
+ "in": "query",
1442
+ "required": false,
1443
+ "description": "Restrict to these collections, repeated once per calendar. Omitted means every calendar the caller holds.",
1444
+ "schema": {
1445
+ "type": "array",
1446
+ "items": {
1447
+ "$ref": "#/components/schemas/UUID"
1448
+ }
1449
+ }
1450
+ }
1451
+ ],
1421
1452
  "responses": {
1422
1453
  "200": {
1423
1454
  "description": "The request has succeeded.",
1424
1455
  "content": {
1425
1456
  "application/json": {
1426
1457
  "schema": {
1427
- "$ref": "#/components/schemas/RemitImap.ConfigDescriptionResponse"
1458
+ "$ref": "#/components/schemas/RemitImap.CalendarEventListResponse"
1459
+ }
1460
+ }
1461
+ }
1462
+ },
1463
+ "400": {
1464
+ "description": "Validation error response",
1465
+ "content": {
1466
+ "application/json": {
1467
+ "schema": {
1468
+ "$ref": "#/components/schemas/ApiError"
1469
+ }
1470
+ }
1471
+ }
1472
+ },
1473
+ "404": {
1474
+ "description": "Not found error response",
1475
+ "content": {
1476
+ "application/json": {
1477
+ "schema": {
1478
+ "$ref": "#/components/schemas/ApiError"
1428
1479
  }
1429
1480
  }
1430
1481
  }
@@ -1438,12 +1489,10 @@
1438
1489
  "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
1439
1490
  }
1440
1491
  }
1441
- }
1442
- },
1443
- "/config/export": {
1444
- "get": {
1445
- "operationId": "ConfigOperations_exportConfig",
1446
- "description": "Export every configured setting for the caller as one versioned JSON document. Contains no credentials: no IMAP/SMTP password and no OAuth token, encrypted or otherwise, and the credential fields are structurally absent rather than blank. The document's own shape is not defined here - it is defined by the published JSON Schema for its `schemaVersion` (build/config-schema/reader-config.v<N>.schema.json), so a file keeps validating independent of this API's models.",
1492
+ },
1493
+ "post": {
1494
+ "operationId": "CalendarEventOperations_createCalendarEvent",
1495
+ "description": "Create an event. The server builds the VEVENT and writes it through the single calendar write path — the same one an accepted invitation and a later CalDAV PUT go through — so the object, its expanded occurrences and the collection's sync sequence land together or not at all.",
1447
1496
  "parameters": [],
1448
1497
  "responses": {
1449
1498
  "200": {
@@ -1451,12 +1500,42 @@
1451
1500
  "content": {
1452
1501
  "application/json": {
1453
1502
  "schema": {
1454
- "$ref": "#/components/schemas/RemitImap.ConfigExportResponse"
1503
+ "$ref": "#/components/schemas/RemitImap.CalendarEventResponse"
1504
+ }
1505
+ }
1506
+ }
1507
+ },
1508
+ "400": {
1509
+ "description": "Validation error response",
1510
+ "content": {
1511
+ "application/json": {
1512
+ "schema": {
1513
+ "$ref": "#/components/schemas/ApiError"
1514
+ }
1515
+ }
1516
+ }
1517
+ },
1518
+ "404": {
1519
+ "description": "Not found error response",
1520
+ "content": {
1521
+ "application/json": {
1522
+ "schema": {
1523
+ "$ref": "#/components/schemas/ApiError"
1455
1524
  }
1456
1525
  }
1457
1526
  }
1458
1527
  }
1459
1528
  },
1529
+ "requestBody": {
1530
+ "required": true,
1531
+ "content": {
1532
+ "application/json": {
1533
+ "schema": {
1534
+ "$ref": "#/components/schemas/RemitImap.CreateCalendarEventInput"
1535
+ }
1536
+ }
1537
+ }
1538
+ },
1460
1539
  "x-amazon-apigateway-integration": {
1461
1540
  "type": "aws_proxy",
1462
1541
  "httpMethod": "POST",
@@ -1467,12 +1546,13 @@
1467
1546
  }
1468
1547
  }
1469
1548
  },
1470
- "/mailboxes/{mailboxId}/threads": {
1549
+ "/calendar-events/{calendarObjectId}": {
1471
1550
  "get": {
1472
- "operationId": "ThreadOperations_listThreads",
1551
+ "operationId": "CalendarEventDetailOperations_getCalendarEvent",
1552
+ "description": "Get one stored event, including its canonical iCalendar text and its etag.",
1473
1553
  "parameters": [
1474
1554
  {
1475
- "name": "mailboxId",
1555
+ "name": "calendarObjectId",
1476
1556
  "in": "path",
1477
1557
  "required": true,
1478
1558
  "schema": {
@@ -1480,19 +1560,12 @@
1480
1560
  }
1481
1561
  },
1482
1562
  {
1483
- "name": "continuationToken",
1484
- "in": "query",
1485
- "required": false,
1486
- "schema": {
1487
- "type": "string"
1488
- }
1489
- },
1490
- {
1491
- "name": "order",
1563
+ "name": "calendarId",
1492
1564
  "in": "query",
1493
- "required": false,
1565
+ "required": true,
1566
+ "description": "Collection the resource lives in. A resource is addressed by its collection and its own id, which is what a client holds: every occurrence a listing returns carries both.",
1494
1567
  "schema": {
1495
- "$ref": "#/components/schemas/RemitImap.SortOrder"
1568
+ "$ref": "#/components/schemas/UUID"
1496
1569
  }
1497
1570
  }
1498
1571
  ],
@@ -1502,26 +1575,27 @@
1502
1575
  "content": {
1503
1576
  "application/json": {
1504
1577
  "schema": {
1505
- "type": "object",
1506
- "required": [
1507
- "items"
1508
- ],
1509
- "properties": {
1510
- "continuationToken": {
1511
- "allOf": [
1512
- {
1513
- "$ref": "#/components/schemas/String800"
1514
- }
1515
- ],
1516
- "description": "Opaque, server-minted cursor: absent means the first (or last) page, present means resume from here. Echo it back unchanged to fetch the next page. Every listing endpoint that accepts it rejects a token it cannot decode with 400 ValidationError rather than silently restarting from the first page (#136, #172)."
1517
- },
1518
- "items": {
1519
- "type": "array",
1520
- "items": {
1521
- "$ref": "#/components/schemas/RemitImap.ThreadMessageResponse"
1522
- }
1523
- }
1524
- }
1578
+ "$ref": "#/components/schemas/RemitImap.CalendarEventResponse"
1579
+ }
1580
+ }
1581
+ }
1582
+ },
1583
+ "400": {
1584
+ "description": "Validation error response",
1585
+ "content": {
1586
+ "application/json": {
1587
+ "schema": {
1588
+ "$ref": "#/components/schemas/ApiError"
1589
+ }
1590
+ }
1591
+ }
1592
+ },
1593
+ "404": {
1594
+ "description": "Not found error response",
1595
+ "content": {
1596
+ "application/json": {
1597
+ "schema": {
1598
+ "$ref": "#/components/schemas/ApiError"
1525
1599
  }
1526
1600
  }
1527
1601
  }
@@ -1535,14 +1609,13 @@
1535
1609
  "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
1536
1610
  }
1537
1611
  }
1538
- }
1539
- },
1540
- "/mailboxes/{mailboxId}/threads/search": {
1541
- "get": {
1542
- "operationId": "ThreadOperations_searchThreads",
1612
+ },
1613
+ "patch": {
1614
+ "operationId": "CalendarEventDetailOperations_updateCalendarEvent",
1615
+ "description": "Update an event. `scope` says what the caller meant by editing one\ndrawing of a series: `All` rewrites the master, `This` writes a\nRECURRENCE-ID override for the named occurrence, and `Following` splits\nthe series — the master's rule is truncated and a second resource carries\nthe remainder under a UID of its own.\n\n`recurrenceId` is required for `This` and `Following` and is refused with\n400 otherwise, along with a scope other than `All` on an event that does\nnot recur, and a `recurrenceId` naming no occurrence of the series.\n\n`If-Match` makes the write conditional on the etag the caller read. A\nmismatch is 412: somebody replaced the resource in between, and applying\nthis patch would discard their change without either of them seeing it.",
1543
1616
  "parameters": [
1544
1617
  {
1545
- "name": "mailboxId",
1618
+ "name": "calendarObjectId",
1546
1619
  "in": "path",
1547
1620
  "required": true,
1548
1621
  "schema": {
@@ -1550,128 +1623,216 @@
1550
1623
  }
1551
1624
  },
1552
1625
  {
1553
- "name": "continuationToken",
1626
+ "name": "calendarId",
1554
1627
  "in": "query",
1555
- "required": false,
1628
+ "required": true,
1629
+ "description": "Collection the resource lives in",
1556
1630
  "schema": {
1557
- "type": "string"
1631
+ "$ref": "#/components/schemas/UUID"
1558
1632
  }
1559
1633
  },
1560
1634
  {
1561
- "name": "order",
1635
+ "name": "scope",
1562
1636
  "in": "query",
1563
1637
  "required": false,
1638
+ "description": "Which occurrences this update applies to. Defaults to `All`.",
1564
1639
  "schema": {
1565
- "$ref": "#/components/schemas/RemitImap.SortOrder"
1640
+ "$ref": "#/components/schemas/RemitImap.RecurrenceScope"
1566
1641
  }
1567
1642
  },
1568
1643
  {
1569
- "name": "query",
1644
+ "name": "recurrenceId",
1570
1645
  "in": "query",
1571
1646
  "required": false,
1647
+ "description": "RECURRENCE-ID of the occurrence the update is anchored at, as the ISO 8601 UTC instant a listing returned. Required for `This` and `Following`.",
1572
1648
  "schema": {
1573
1649
  "type": "string"
1574
1650
  }
1575
1651
  },
1576
1652
  {
1577
- "name": "subject",
1578
- "in": "query",
1653
+ "name": "If-Match",
1654
+ "in": "header",
1579
1655
  "required": false,
1580
1656
  "schema": {
1581
1657
  "type": "string"
1582
1658
  }
1659
+ }
1660
+ ],
1661
+ "responses": {
1662
+ "200": {
1663
+ "description": "The request has succeeded.",
1664
+ "content": {
1665
+ "application/json": {
1666
+ "schema": {
1667
+ "$ref": "#/components/schemas/RemitImap.CalendarEventResponse"
1668
+ }
1669
+ }
1670
+ }
1583
1671
  },
1584
- {
1585
- "name": "from",
1586
- "in": "query",
1587
- "required": false,
1588
- "schema": {
1589
- "type": "string"
1672
+ "400": {
1673
+ "description": "Validation error response",
1674
+ "content": {
1675
+ "application/json": {
1676
+ "schema": {
1677
+ "$ref": "#/components/schemas/ApiError"
1678
+ }
1679
+ }
1590
1680
  }
1591
1681
  },
1592
- {
1593
- "name": "unread",
1594
- "in": "query",
1595
- "required": false,
1596
- "schema": {
1597
- "type": "boolean"
1682
+ "404": {
1683
+ "description": "Not found error response",
1684
+ "content": {
1685
+ "application/json": {
1686
+ "schema": {
1687
+ "$ref": "#/components/schemas/ApiError"
1688
+ }
1689
+ }
1598
1690
  }
1599
1691
  },
1692
+ "412": {
1693
+ "description": "The condition the request stated is no longer true. Answered when an\n`If-Match` names an etag the stored resource no longer carries: somebody else\nwrote it in between, so applying this request would silently discard their\nchange. The client re-reads the resource and decides what to do with the\nversion it now has.",
1694
+ "content": {
1695
+ "application/json": {
1696
+ "schema": {
1697
+ "$ref": "#/components/schemas/ApiError"
1698
+ }
1699
+ }
1700
+ }
1701
+ }
1702
+ },
1703
+ "requestBody": {
1704
+ "required": true,
1705
+ "content": {
1706
+ "application/json": {
1707
+ "schema": {
1708
+ "$ref": "#/components/schemas/RemitImap.UpdateCalendarEventInput"
1709
+ }
1710
+ }
1711
+ }
1712
+ },
1713
+ "x-amazon-apigateway-integration": {
1714
+ "type": "aws_proxy",
1715
+ "httpMethod": "POST",
1716
+ "passthroughBehavior": "when_no_match",
1717
+ "uri": {
1718
+ "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
1719
+ }
1720
+ }
1721
+ },
1722
+ "delete": {
1723
+ "operationId": "CalendarEventDetailOperations_deleteCalendarEvent",
1724
+ "description": "Delete an event. `All` removes the resource and every occurrence it\nproduced; `This` writes an EXDATE for the named occurrence and keeps the\nrest of the series; `Following` truncates the series to end just before\nit.\n\nSame refusals and the same `If-Match` precondition as the update.",
1725
+ "parameters": [
1600
1726
  {
1601
- "name": "starred",
1602
- "in": "query",
1603
- "required": false,
1727
+ "name": "calendarObjectId",
1728
+ "in": "path",
1729
+ "required": true,
1604
1730
  "schema": {
1605
- "type": "boolean"
1731
+ "$ref": "#/components/schemas/UUID"
1606
1732
  }
1607
1733
  },
1608
1734
  {
1609
- "name": "attachments",
1735
+ "name": "calendarId",
1610
1736
  "in": "query",
1611
- "required": false,
1737
+ "required": true,
1738
+ "description": "Collection the resource lives in",
1612
1739
  "schema": {
1613
- "type": "boolean"
1740
+ "$ref": "#/components/schemas/UUID"
1614
1741
  }
1615
1742
  },
1616
1743
  {
1617
- "name": "senderTrust",
1744
+ "name": "scope",
1618
1745
  "in": "query",
1619
1746
  "required": false,
1620
- "description": "Filter to messages whose From address has one of these trust levels. Off-row criterion, resolved by in-handler enrichment over the capped window.",
1747
+ "description": "Which occurrences this delete applies to. Defaults to `All`.",
1621
1748
  "schema": {
1622
- "type": "array",
1623
- "items": {
1624
- "$ref": "#/components/schemas/RemitImap.SenderTrust"
1625
- }
1749
+ "$ref": "#/components/schemas/RemitImap.RecurrenceScope"
1626
1750
  }
1627
1751
  },
1628
1752
  {
1629
- "name": "category",
1753
+ "name": "recurrenceId",
1630
1754
  "in": "query",
1631
1755
  "required": false,
1632
- "description": "Filter to messages with one of these header-derived categories. Applied as a SQL predicate over the whole mailbox, so a page comes back full of matches however rare the category is. `uncategorized` is one of them.",
1756
+ "description": "RECURRENCE-ID of the occurrence the delete is anchored at. Required for `This` and `Following`.",
1633
1757
  "schema": {
1634
- "type": "array",
1635
- "items": {
1636
- "$ref": "#/components/schemas/RemitImap.MessageCategory"
1637
- }
1758
+ "type": "string"
1638
1759
  }
1639
1760
  },
1640
1761
  {
1641
- "name": "dkimMismatch",
1642
- "in": "query",
1762
+ "name": "If-Match",
1763
+ "in": "header",
1643
1764
  "required": false,
1644
- "description": "Filter on the DKIM-alignment signal (true = DKIM mismatch). Off-row criterion; rows without an authenticity signal never match.",
1645
1765
  "schema": {
1646
- "type": "boolean"
1766
+ "type": "string"
1647
1767
  }
1648
- },
1649
- {
1650
- "name": "count",
1651
- "in": "query",
1652
- "required": false,
1653
- "description": "Include how many messages the query matches. Counted over the whole mailbox, independent of `limit`.",
1654
- "schema": {
1655
- "type": "boolean"
1768
+ }
1769
+ ],
1770
+ "responses": {
1771
+ "204": {
1772
+ "description": "Nothing to return. The resource, or the occurrence, is gone."
1773
+ },
1774
+ "400": {
1775
+ "description": "Validation error response",
1776
+ "content": {
1777
+ "application/json": {
1778
+ "schema": {
1779
+ "$ref": "#/components/schemas/ApiError"
1780
+ }
1781
+ }
1782
+ }
1783
+ },
1784
+ "404": {
1785
+ "description": "Not found error response",
1786
+ "content": {
1787
+ "application/json": {
1788
+ "schema": {
1789
+ "$ref": "#/components/schemas/ApiError"
1790
+ }
1791
+ }
1656
1792
  }
1657
1793
  },
1794
+ "412": {
1795
+ "description": "The condition the request stated is no longer true. Answered when an\n`If-Match` names an etag the stored resource no longer carries: somebody else\nwrote it in between, so applying this request would silently discard their\nchange. The client re-reads the resource and decides what to do with the\nversion it now has.",
1796
+ "content": {
1797
+ "application/json": {
1798
+ "schema": {
1799
+ "$ref": "#/components/schemas/ApiError"
1800
+ }
1801
+ }
1802
+ }
1803
+ }
1804
+ },
1805
+ "x-amazon-apigateway-integration": {
1806
+ "type": "aws_proxy",
1807
+ "httpMethod": "POST",
1808
+ "passthroughBehavior": "when_no_match",
1809
+ "uri": {
1810
+ "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
1811
+ }
1812
+ }
1813
+ }
1814
+ },
1815
+ "/calendar-free-busy": {
1816
+ "get": {
1817
+ "operationId": "CalendarFreeBusyOperations_listCalendarFreeBusy",
1818
+ "description": "Busy spans over a window, merged across every calendar the caller holds. Backs the clash check beside a mail invitation and the free-stretch maths in the day strip, neither of which should have to fetch every event and merge them itself. Refused with 400 on the same window rules as the event listing.",
1819
+ "parameters": [
1658
1820
  {
1659
- "name": "results",
1821
+ "name": "from",
1660
1822
  "in": "query",
1661
- "required": false,
1662
- "description": "Return matching rows. Default true; set false for a count-only response.",
1823
+ "required": true,
1824
+ "description": "Start of the window, an ISO 8601 date-time with zone offset. Inclusive.",
1663
1825
  "schema": {
1664
- "type": "boolean"
1826
+ "type": "string"
1665
1827
  }
1666
1828
  },
1667
1829
  {
1668
- "name": "limit",
1830
+ "name": "to",
1669
1831
  "in": "query",
1670
- "required": false,
1671
- "description": "Page size for the rows returned, capped server-side at 500. Bounds the read, not the `count`.",
1832
+ "required": true,
1833
+ "description": "End of the window, an ISO 8601 date-time with zone offset. Exclusive.",
1672
1834
  "schema": {
1673
- "type": "integer",
1674
- "format": "int32"
1835
+ "type": "string"
1675
1836
  }
1676
1837
  }
1677
1838
  ],
@@ -1681,7 +1842,17 @@
1681
1842
  "content": {
1682
1843
  "application/json": {
1683
1844
  "schema": {
1684
- "$ref": "#/components/schemas/RemitImap.ThreadSearchResponse"
1845
+ "$ref": "#/components/schemas/RemitImap.CalendarFreeBusyResponse"
1846
+ }
1847
+ }
1848
+ }
1849
+ },
1850
+ "400": {
1851
+ "description": "Validation error response",
1852
+ "content": {
1853
+ "application/json": {
1854
+ "schema": {
1855
+ "$ref": "#/components/schemas/ApiError"
1685
1856
  }
1686
1857
  }
1687
1858
  }
@@ -1697,18 +1868,43 @@
1697
1868
  }
1698
1869
  }
1699
1870
  },
1700
- "/me": {
1701
- "delete": {
1702
- "operationId": "MeOperations_deleteMe",
1703
- "description": "Request full erasure of the current user's account and all associated data",
1871
+ "/calendars": {
1872
+ "get": {
1873
+ "operationId": "CalendarOperations_listCalendars",
1874
+ "description": "List the caller's calendar collections. Provisions the account config's default collection when it has none, so a first read never comes back empty and no client has to know how a calendar gets made.",
1704
1875
  "parameters": [],
1705
1876
  "responses": {
1706
- "202": {
1707
- "description": "Response for DELETE /me (account erasure request)",
1877
+ "200": {
1878
+ "description": "The request has succeeded.",
1708
1879
  "content": {
1709
1880
  "application/json": {
1710
1881
  "schema": {
1711
- "$ref": "#/components/schemas/RemitImap.DeleteAccountConfigResponse"
1882
+ "$ref": "#/components/schemas/RemitImap.CalendarListResponse"
1883
+ }
1884
+ }
1885
+ }
1886
+ }
1887
+ },
1888
+ "x-amazon-apigateway-integration": {
1889
+ "type": "aws_proxy",
1890
+ "httpMethod": "POST",
1891
+ "passthroughBehavior": "when_no_match",
1892
+ "uri": {
1893
+ "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
1894
+ }
1895
+ }
1896
+ },
1897
+ "post": {
1898
+ "operationId": "CalendarOperations_createCalendar",
1899
+ "description": "Create a calendar collection. Refused with 400 when `urlSegment` is empty or already taken in this account config — the segment is the collection's identity, so a second create under the same one would otherwise silently return the collection that already exists.",
1900
+ "parameters": [],
1901
+ "responses": {
1902
+ "200": {
1903
+ "description": "The request has succeeded.",
1904
+ "content": {
1905
+ "application/json": {
1906
+ "schema": {
1907
+ "$ref": "#/components/schemas/RemitImap.CalendarResponse"
1712
1908
  }
1713
1909
  }
1714
1910
  }
@@ -1729,7 +1925,7 @@
1729
1925
  "content": {
1730
1926
  "application/json": {
1731
1927
  "schema": {
1732
- "$ref": "#/components/schemas/RemitImap.DeleteMeInput"
1928
+ "$ref": "#/components/schemas/RemitImap.CreateCalendarInput"
1733
1929
  }
1734
1930
  }
1735
1931
  }
@@ -1744,18 +1940,36 @@
1744
1940
  }
1745
1941
  }
1746
1942
  },
1747
- "/me/export": {
1748
- "post": {
1749
- "operationId": "MeOperations_createExport",
1750
- "description": "Request an export of all account data as a ZIP archive of .eml files. Returns immediately with an exportId; check GET /me/export/{exportId} for status and downloadUrl.",
1751
- "parameters": [],
1943
+ "/calendars/{calendarId}": {
1944
+ "get": {
1945
+ "operationId": "CalendarDetailOperations_getCalendar",
1946
+ "parameters": [
1947
+ {
1948
+ "name": "calendarId",
1949
+ "in": "path",
1950
+ "required": true,
1951
+ "schema": {
1952
+ "$ref": "#/components/schemas/UUID"
1953
+ }
1954
+ }
1955
+ ],
1752
1956
  "responses": {
1753
- "202": {
1754
- "description": "Response body for POST /me/export",
1957
+ "200": {
1958
+ "description": "The request has succeeded.",
1755
1959
  "content": {
1756
1960
  "application/json": {
1757
1961
  "schema": {
1758
- "$ref": "#/components/schemas/RemitImap.CreateExportResponse"
1962
+ "$ref": "#/components/schemas/RemitImap.CalendarResponse"
1963
+ }
1964
+ }
1965
+ }
1966
+ },
1967
+ "404": {
1968
+ "description": "Not found error response",
1969
+ "content": {
1970
+ "application/json": {
1971
+ "schema": {
1972
+ "$ref": "#/components/schemas/ApiError"
1759
1973
  }
1760
1974
  }
1761
1975
  }
@@ -1769,19 +1983,16 @@
1769
1983
  "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
1770
1984
  }
1771
1985
  }
1772
- }
1773
- },
1774
- "/me/export/{exportId}": {
1775
- "get": {
1776
- "operationId": "MeOperations_getExport",
1777
- "description": "Get the status of a previously requested data export. When state is Ready, downloadUrl holds a 30-day presigned S3 URL.",
1986
+ },
1987
+ "patch": {
1988
+ "operationId": "CalendarDetailOperations_updateCalendar",
1778
1989
  "parameters": [
1779
1990
  {
1780
- "name": "exportId",
1991
+ "name": "calendarId",
1781
1992
  "in": "path",
1782
1993
  "required": true,
1783
1994
  "schema": {
1784
- "type": "string"
1995
+ "$ref": "#/components/schemas/UUID"
1785
1996
  }
1786
1997
  }
1787
1998
  ],
@@ -1791,7 +2002,84 @@
1791
2002
  "content": {
1792
2003
  "application/json": {
1793
2004
  "schema": {
1794
- "$ref": "#/components/schemas/RemitImap.AccountExportRequestResponse"
2005
+ "$ref": "#/components/schemas/RemitImap.CalendarResponse"
2006
+ }
2007
+ }
2008
+ }
2009
+ },
2010
+ "400": {
2011
+ "description": "Validation error response",
2012
+ "content": {
2013
+ "application/json": {
2014
+ "schema": {
2015
+ "$ref": "#/components/schemas/ApiError"
2016
+ }
2017
+ }
2018
+ }
2019
+ },
2020
+ "404": {
2021
+ "description": "Not found error response",
2022
+ "content": {
2023
+ "application/json": {
2024
+ "schema": {
2025
+ "$ref": "#/components/schemas/ApiError"
2026
+ }
2027
+ }
2028
+ }
2029
+ }
2030
+ },
2031
+ "requestBody": {
2032
+ "required": true,
2033
+ "content": {
2034
+ "application/json": {
2035
+ "schema": {
2036
+ "$ref": "#/components/schemas/RemitImap.UpdateCalendarInput"
2037
+ }
2038
+ }
2039
+ }
2040
+ },
2041
+ "x-amazon-apigateway-integration": {
2042
+ "type": "aws_proxy",
2043
+ "httpMethod": "POST",
2044
+ "passthroughBehavior": "when_no_match",
2045
+ "uri": {
2046
+ "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
2047
+ }
2048
+ }
2049
+ },
2050
+ "delete": {
2051
+ "operationId": "CalendarDetailOperations_deleteCalendar",
2052
+ "description": "Delete a calendar collection and every event in it. Refused with 400 for the account's default collection: it is the one place an accepted invitation or a first event can land, and an account with no calendar has nowhere to put an event.",
2053
+ "parameters": [
2054
+ {
2055
+ "name": "calendarId",
2056
+ "in": "path",
2057
+ "required": true,
2058
+ "schema": {
2059
+ "$ref": "#/components/schemas/UUID"
2060
+ }
2061
+ }
2062
+ ],
2063
+ "responses": {
2064
+ "204": {
2065
+ "description": "Nothing to return. The resource, or the occurrence, is gone."
2066
+ },
2067
+ "400": {
2068
+ "description": "Validation error response",
2069
+ "content": {
2070
+ "application/json": {
2071
+ "schema": {
2072
+ "$ref": "#/components/schemas/ApiError"
2073
+ }
2074
+ }
2075
+ }
2076
+ },
2077
+ "404": {
2078
+ "description": "Not found error response",
2079
+ "content": {
2080
+ "application/json": {
2081
+ "schema": {
2082
+ "$ref": "#/components/schemas/ApiError"
1795
2083
  }
1796
2084
  }
1797
2085
  }
@@ -1807,10 +2095,10 @@
1807
2095
  }
1808
2096
  }
1809
2097
  },
1810
- "/me/quarantine": {
2098
+ "/config": {
1811
2099
  "get": {
1812
- "operationId": "MeOperations_listQuarantine",
1813
- "description": "List every message the sync path could not read, for the current user across all of their mail accounts (issue #72). Backs the quarantine section in settings. Unpaginated: the list is small by design, and an entry is a defect waiting to be reported rather than a page of mail.",
2100
+ "operationId": "ConfigOperations_getConfig",
2101
+ "description": "Get the current user's configuration including accounts and addresses",
1814
2102
  "parameters": [],
1815
2103
  "responses": {
1816
2104
  "200": {
@@ -1818,7 +2106,7 @@
1818
2106
  "content": {
1819
2107
  "application/json": {
1820
2108
  "schema": {
1821
- "$ref": "#/components/schemas/RemitImap.QuarantineListResponse"
2109
+ "$ref": "#/components/schemas/RemitImap.ConfigDescriptionResponse"
1822
2110
  }
1823
2111
  }
1824
2112
  }
@@ -1834,10 +2122,10 @@
1834
2122
  }
1835
2123
  }
1836
2124
  },
1837
- "/me/vip-suggestions": {
2125
+ "/config/export": {
1838
2126
  "get": {
1839
- "operationId": "MeOperations_listVipSuggestions",
1840
- "description": "List wellknown senders the user has not yet promoted to VIP. Ranked by `replyCount + inboundCount` desc with `lastInboundAt` desc as the tie-breaker. Capped at 10.",
2127
+ "operationId": "ConfigOperations_exportConfig",
2128
+ "description": "Export every configured setting for the caller as one versioned JSON document. Contains no credentials: no IMAP/SMTP password and no OAuth token, encrypted or otherwise, and the credential fields are structurally absent rather than blank. The document's own shape is not defined here - it is defined by the published JSON Schema for its `schemaVersion` (build/config-schema/reader-config.v<N>.schema.json), so a file keeps validating independent of this API's models.",
1841
2129
  "parameters": [],
1842
2130
  "responses": {
1843
2131
  "200": {
@@ -1845,7 +2133,7 @@
1845
2133
  "content": {
1846
2134
  "application/json": {
1847
2135
  "schema": {
1848
- "$ref": "#/components/schemas/RemitImap.VipSuggestionsResponse"
2136
+ "$ref": "#/components/schemas/RemitImap.ConfigExportResponse"
1849
2137
  }
1850
2138
  }
1851
2139
  }
@@ -1861,10 +2149,10 @@
1861
2149
  }
1862
2150
  }
1863
2151
  },
1864
- "/messages/copy": {
2152
+ "/config/import": {
1865
2153
  "post": {
1866
- "operationId": "MessageBulkOperations_copyMessages",
1867
- "description": "Copy messages to a destination mailbox",
2154
+ "operationId": "ConfigOperations_importConfig",
2155
+ "description": "Validate, or validate and apply, a config document. `mode: validate` (the default) writes nothing and returns the same report an apply would. `onExisting: abort` (the default) refuses with 409 when this configuration already holds accounts, labels, filters or address flags. Nothing is written until the whole document has validated; a write failure after that aborts the remainder and the report names exactly what landed. Imported accounts arrive inactive with `connectionState: credentials_missing` (or `reauth_required` for OAuth) - the credentials the file deliberately omits are re-entered per account afterwards.",
1868
2156
  "parameters": [],
1869
2157
  "responses": {
1870
2158
  "200": {
@@ -1872,13 +2160,23 @@
1872
2160
  "content": {
1873
2161
  "application/json": {
1874
2162
  "schema": {
1875
- "$ref": "#/components/schemas/RemitImap.MessageBulkOperationResult"
2163
+ "$ref": "#/components/schemas/RemitImap.ConfigImportReport"
2164
+ }
2165
+ }
2166
+ }
2167
+ },
2168
+ "400": {
2169
+ "description": "Validation error response",
2170
+ "content": {
2171
+ "application/json": {
2172
+ "schema": {
2173
+ "$ref": "#/components/schemas/ApiError"
1876
2174
  }
1877
2175
  }
1878
2176
  }
1879
2177
  },
1880
2178
  "409": {
1881
- "description": "The same flat 409 body, for a message whose folder and uid do not name the same message.\n\n`code: \"message_placement_unsettled\"` — the message is mid-move, so its row names the destination folder while its uid still belongs to the source; acting on that pair would hit whatever the destination holds at that uid. `details` carries `accountId`, `messageId` and `reason`, one of: `in_flight` (the move is still running, and the same request succeeds once it confirms) or `unverified` (the move gave up without confirming, so where the message sits is unknown until the folder is resynced).",
2179
+ "description": "The import was refused because this configuration is not empty. The body is\nflat: `code`, `message` and `details` sit at the top level.\n\n`code: \"config_not_empty\"` — the configuration already holds an account, a\nlabel, a filter or an address flag. `details` names how many of each. Re-send\nwith `onExisting: merge` to upsert instead.",
1882
2180
  "content": {
1883
2181
  "application/json": {
1884
2182
  "schema": {
@@ -1893,7 +2191,7 @@
1893
2191
  "content": {
1894
2192
  "application/json": {
1895
2193
  "schema": {
1896
- "$ref": "#/components/schemas/RemitImap.CopyMessagesInput"
2194
+ "$ref": "#/components/schemas/RemitImap.ImportConfigInput"
1897
2195
  }
1898
2196
  }
1899
2197
  }
@@ -1908,43 +2206,66 @@
1908
2206
  }
1909
2207
  }
1910
2208
  },
1911
- "/messages/delete": {
1912
- "post": {
1913
- "operationId": "MessageBulkOperations_deleteMessages",
1914
- "description": "Delete messages (move to Trash or permanent delete)",
1915
- "parameters": [],
1916
- "responses": {
1917
- "200": {
1918
- "description": "The request has succeeded.",
1919
- "content": {
1920
- "application/json": {
1921
- "schema": {
1922
- "$ref": "#/components/schemas/RemitImap.MessageBulkOperationResult"
1923
- }
1924
- }
2209
+ "/mailboxes/{mailboxId}/threads": {
2210
+ "get": {
2211
+ "operationId": "ThreadOperations_listThreads",
2212
+ "parameters": [
2213
+ {
2214
+ "name": "mailboxId",
2215
+ "in": "path",
2216
+ "required": true,
2217
+ "schema": {
2218
+ "$ref": "#/components/schemas/UUID"
1925
2219
  }
1926
2220
  },
1927
- "409": {
1928
- "description": "The same flat 409 body, with the two codes this endpoint can answer with.\n\n`code: \"folder_role_unresolved\"` — as everywhere else, with `details` carrying `role`, `accountId` and `reason`.\n\n`code: \"message_placement_unsettled\"` — as `MessagePlacementConflict`, with `details` carrying `accountId`, `messageId` and `reason`.",
2221
+ {
2222
+ "name": "continuationToken",
2223
+ "in": "query",
2224
+ "required": false,
2225
+ "schema": {
2226
+ "type": "string"
2227
+ }
2228
+ },
2229
+ {
2230
+ "name": "order",
2231
+ "in": "query",
2232
+ "required": false,
2233
+ "schema": {
2234
+ "$ref": "#/components/schemas/RemitImap.SortOrder"
2235
+ }
2236
+ }
2237
+ ],
2238
+ "responses": {
2239
+ "200": {
2240
+ "description": "The request has succeeded.",
1929
2241
  "content": {
1930
2242
  "application/json": {
1931
2243
  "schema": {
1932
- "$ref": "#/components/schemas/ApiError"
2244
+ "type": "object",
2245
+ "required": [
2246
+ "items"
2247
+ ],
2248
+ "properties": {
2249
+ "continuationToken": {
2250
+ "allOf": [
2251
+ {
2252
+ "$ref": "#/components/schemas/String800"
2253
+ }
2254
+ ],
2255
+ "description": "Opaque, server-minted cursor: absent means the first (or last) page, present means resume from here. Echo it back unchanged to fetch the next page. Every listing endpoint that accepts it rejects a token it cannot decode with 400 ValidationError rather than silently restarting from the first page (#136, #172)."
2256
+ },
2257
+ "items": {
2258
+ "type": "array",
2259
+ "items": {
2260
+ "$ref": "#/components/schemas/RemitImap.ThreadMessageResponse"
2261
+ }
2262
+ }
2263
+ }
1933
2264
  }
1934
2265
  }
1935
2266
  }
1936
2267
  }
1937
2268
  },
1938
- "requestBody": {
1939
- "required": true,
1940
- "content": {
1941
- "application/json": {
1942
- "schema": {
1943
- "$ref": "#/components/schemas/RemitImap.DeleteMessagesInput"
1944
- }
1945
- }
1946
- }
1947
- },
1948
2269
  "x-amazon-apigateway-integration": {
1949
2270
  "type": "aws_proxy",
1950
2271
  "httpMethod": "POST",
@@ -1955,33 +2276,156 @@
1955
2276
  }
1956
2277
  }
1957
2278
  },
1958
- "/messages/flags": {
1959
- "post": {
1960
- "operationId": "MessageBulkOperations_updateFlags",
1961
- "description": "Update flags on multiple messages",
1962
- "parameters": [],
2279
+ "/mailboxes/{mailboxId}/threads/search": {
2280
+ "get": {
2281
+ "operationId": "ThreadOperations_searchThreads",
2282
+ "parameters": [
2283
+ {
2284
+ "name": "mailboxId",
2285
+ "in": "path",
2286
+ "required": true,
2287
+ "schema": {
2288
+ "$ref": "#/components/schemas/UUID"
2289
+ }
2290
+ },
2291
+ {
2292
+ "name": "continuationToken",
2293
+ "in": "query",
2294
+ "required": false,
2295
+ "schema": {
2296
+ "type": "string"
2297
+ }
2298
+ },
2299
+ {
2300
+ "name": "order",
2301
+ "in": "query",
2302
+ "required": false,
2303
+ "schema": {
2304
+ "$ref": "#/components/schemas/RemitImap.SortOrder"
2305
+ }
2306
+ },
2307
+ {
2308
+ "name": "query",
2309
+ "in": "query",
2310
+ "required": false,
2311
+ "schema": {
2312
+ "type": "string"
2313
+ }
2314
+ },
2315
+ {
2316
+ "name": "subject",
2317
+ "in": "query",
2318
+ "required": false,
2319
+ "schema": {
2320
+ "type": "string"
2321
+ }
2322
+ },
2323
+ {
2324
+ "name": "from",
2325
+ "in": "query",
2326
+ "required": false,
2327
+ "schema": {
2328
+ "type": "string"
2329
+ }
2330
+ },
2331
+ {
2332
+ "name": "unread",
2333
+ "in": "query",
2334
+ "required": false,
2335
+ "schema": {
2336
+ "type": "boolean"
2337
+ }
2338
+ },
2339
+ {
2340
+ "name": "starred",
2341
+ "in": "query",
2342
+ "required": false,
2343
+ "schema": {
2344
+ "type": "boolean"
2345
+ }
2346
+ },
2347
+ {
2348
+ "name": "attachments",
2349
+ "in": "query",
2350
+ "required": false,
2351
+ "schema": {
2352
+ "type": "boolean"
2353
+ }
2354
+ },
2355
+ {
2356
+ "name": "senderTrust",
2357
+ "in": "query",
2358
+ "required": false,
2359
+ "description": "Filter to messages whose From address has one of these trust levels. Off-row criterion, resolved by in-handler enrichment over the capped window.",
2360
+ "schema": {
2361
+ "type": "array",
2362
+ "items": {
2363
+ "$ref": "#/components/schemas/RemitImap.SenderTrust"
2364
+ }
2365
+ }
2366
+ },
2367
+ {
2368
+ "name": "category",
2369
+ "in": "query",
2370
+ "required": false,
2371
+ "description": "Filter to messages with one of these header-derived categories. Applied as a SQL predicate over the whole mailbox, so a page comes back full of matches however rare the category is. `uncategorized` is one of them.",
2372
+ "schema": {
2373
+ "type": "array",
2374
+ "items": {
2375
+ "$ref": "#/components/schemas/RemitImap.MessageCategory"
2376
+ }
2377
+ }
2378
+ },
2379
+ {
2380
+ "name": "dkimMismatch",
2381
+ "in": "query",
2382
+ "required": false,
2383
+ "description": "Filter on the DKIM-alignment signal (true = DKIM mismatch). Off-row criterion; rows without an authenticity signal never match.",
2384
+ "schema": {
2385
+ "type": "boolean"
2386
+ }
2387
+ },
2388
+ {
2389
+ "name": "count",
2390
+ "in": "query",
2391
+ "required": false,
2392
+ "description": "Include how many messages the query matches. Counted over the whole mailbox, independent of `limit`.",
2393
+ "schema": {
2394
+ "type": "boolean"
2395
+ }
2396
+ },
2397
+ {
2398
+ "name": "results",
2399
+ "in": "query",
2400
+ "required": false,
2401
+ "description": "Return matching rows. Default true; set false for a count-only response.",
2402
+ "schema": {
2403
+ "type": "boolean"
2404
+ }
2405
+ },
2406
+ {
2407
+ "name": "limit",
2408
+ "in": "query",
2409
+ "required": false,
2410
+ "description": "Page size for the rows returned, capped server-side at 500. Bounds the read, not the `count`.",
2411
+ "schema": {
2412
+ "type": "integer",
2413
+ "format": "int32"
2414
+ }
2415
+ }
2416
+ ],
1963
2417
  "responses": {
1964
2418
  "200": {
1965
2419
  "description": "The request has succeeded.",
1966
2420
  "content": {
1967
2421
  "application/json": {
1968
2422
  "schema": {
1969
- "$ref": "#/components/schemas/RemitImap.MessageBulkOperationResult"
2423
+ "$ref": "#/components/schemas/RemitImap.ThreadSearchResponse"
1970
2424
  }
1971
2425
  }
1972
2426
  }
1973
2427
  }
1974
2428
  },
1975
- "requestBody": {
1976
- "required": true,
1977
- "content": {
1978
- "application/json": {
1979
- "schema": {
1980
- "$ref": "#/components/schemas/RemitImap.BulkUpdateFlagsInput"
1981
- }
1982
- }
1983
- }
1984
- },
1985
2429
  "x-amazon-apigateway-integration": {
1986
2430
  "type": "aws_proxy",
1987
2431
  "httpMethod": "POST",
@@ -1992,18 +2436,28 @@
1992
2436
  }
1993
2437
  }
1994
2438
  },
1995
- "/messages/labels": {
1996
- "post": {
1997
- "operationId": "MessageBulkOperations_updateMessageLabels",
1998
- "description": "Apply or remove a label on multiple messages — the manual \"just these\" scope (RFC 034 recap, issue #26). `appliedByFilterId` stays absent on the resulting `MessageLabel` rows; that provenance is reserved for filter/organize applies.",
2439
+ "/me": {
2440
+ "delete": {
2441
+ "operationId": "MeOperations_deleteMe",
2442
+ "description": "Request full erasure of the current user's account and all associated data",
1999
2443
  "parameters": [],
2000
2444
  "responses": {
2001
- "200": {
2002
- "description": "The request has succeeded.",
2445
+ "202": {
2446
+ "description": "Response for DELETE /me (account erasure request)",
2003
2447
  "content": {
2004
2448
  "application/json": {
2005
2449
  "schema": {
2006
- "$ref": "#/components/schemas/RemitImap.MessageBulkOperationResult"
2450
+ "$ref": "#/components/schemas/RemitImap.DeleteAccountConfigResponse"
2451
+ }
2452
+ }
2453
+ }
2454
+ },
2455
+ "400": {
2456
+ "description": "Validation error response",
2457
+ "content": {
2458
+ "application/json": {
2459
+ "schema": {
2460
+ "$ref": "#/components/schemas/ApiError"
2007
2461
  }
2008
2462
  }
2009
2463
  }
@@ -2014,7 +2468,7 @@
2014
2468
  "content": {
2015
2469
  "application/json": {
2016
2470
  "schema": {
2017
- "$ref": "#/components/schemas/RemitImap.UpdateMessageLabelsInput"
2471
+ "$ref": "#/components/schemas/RemitImap.DeleteMeInput"
2018
2472
  }
2019
2473
  }
2020
2474
  }
@@ -2029,32 +2483,23 @@
2029
2483
  }
2030
2484
  }
2031
2485
  },
2032
- "/messages/move": {
2486
+ "/me/export": {
2033
2487
  "post": {
2034
- "operationId": "MessageBulkOperations_moveMessages",
2488
+ "operationId": "MeOperations_createExport",
2489
+ "description": "Request an export of all account data as a ZIP archive of .eml files. Returns immediately with an exportId; check GET /me/export/{exportId} for status and downloadUrl.",
2035
2490
  "parameters": [],
2036
2491
  "responses": {
2037
- "200": {
2038
- "description": "The request has succeeded.",
2039
- "content": {
2492
+ "202": {
2493
+ "description": "Response body for POST /me/export",
2494
+ "content": {
2040
2495
  "application/json": {
2041
2496
  "schema": {
2042
- "$ref": "#/components/schemas/RemitImap.MessageBulkOperationResult"
2497
+ "$ref": "#/components/schemas/RemitImap.CreateExportResponse"
2043
2498
  }
2044
2499
  }
2045
2500
  }
2046
2501
  }
2047
2502
  },
2048
- "requestBody": {
2049
- "required": true,
2050
- "content": {
2051
- "application/json": {
2052
- "schema": {
2053
- "$ref": "#/components/schemas/RemitImap.MoveMessagesInput"
2054
- }
2055
- }
2056
- }
2057
- },
2058
2503
  "x-amazon-apigateway-integration": {
2059
2504
  "type": "aws_proxy",
2060
2505
  "httpMethod": "POST",
@@ -2065,33 +2510,32 @@
2065
2510
  }
2066
2511
  }
2067
2512
  },
2068
- "/messages/not-spam": {
2069
- "post": {
2070
- "operationId": "MessageBulkOperations_notSpam",
2071
- "description": "Undo a spam report: clear the sender's `blocked` flag (never promotes to trusted/wellknown) and move each message back to `Message.originalMailboxId`. Idempotent under a repeated call. Each message is processed independently and concurrently, so the request's total wait is bounded by one message's settle timeout rather than accumulating across the batch.",
2072
- "parameters": [],
2513
+ "/me/export/{exportId}": {
2514
+ "get": {
2515
+ "operationId": "MeOperations_getExport",
2516
+ "description": "Get the status of a previously requested data export. When state is Ready, downloadUrl holds a 30-day presigned S3 URL.",
2517
+ "parameters": [
2518
+ {
2519
+ "name": "exportId",
2520
+ "in": "path",
2521
+ "required": true,
2522
+ "schema": {
2523
+ "type": "string"
2524
+ }
2525
+ }
2526
+ ],
2073
2527
  "responses": {
2074
2528
  "200": {
2075
2529
  "description": "The request has succeeded.",
2076
2530
  "content": {
2077
2531
  "application/json": {
2078
2532
  "schema": {
2079
- "$ref": "#/components/schemas/RemitImap.SpamReportBulkResult"
2533
+ "$ref": "#/components/schemas/RemitImap.AccountExportRequestResponse"
2080
2534
  }
2081
2535
  }
2082
2536
  }
2083
2537
  }
2084
2538
  },
2085
- "requestBody": {
2086
- "required": true,
2087
- "content": {
2088
- "application/json": {
2089
- "schema": {
2090
- "$ref": "#/components/schemas/RemitImap.BulkMessageInput"
2091
- }
2092
- }
2093
- }
2094
- },
2095
2539
  "x-amazon-apigateway-integration": {
2096
2540
  "type": "aws_proxy",
2097
2541
  "httpMethod": "POST",
@@ -2102,10 +2546,10 @@
2102
2546
  }
2103
2547
  }
2104
2548
  },
2105
- "/messages/report-spam": {
2106
- "post": {
2107
- "operationId": "MessageBulkOperations_reportSpam",
2108
- "description": "Report messages as spam: block the sender (Address.flags.blocked) and move each message to the account's Junk folder. Skips the block (but still moves the message) when the sender is the account's own address. One call replaces composing the old separate `Block` and `Mark spam` actions — a single user intent, a single failure story. The sender block is written first and does not wait on the move; a failed move never rolls it back. Idempotent under a repeated call.",
2549
+ "/me/quarantine": {
2550
+ "get": {
2551
+ "operationId": "MeOperations_listQuarantine",
2552
+ "description": "List every message the sync path could not read, for the current user across all of their mail accounts (issue #72). Backs the quarantine section in settings. Unpaginated: the list is small by design, and an entry is a defect waiting to be reported rather than a page of mail.",
2109
2553
  "parameters": [],
2110
2554
  "responses": {
2111
2555
  "200": {
@@ -2113,22 +2557,12 @@
2113
2557
  "content": {
2114
2558
  "application/json": {
2115
2559
  "schema": {
2116
- "$ref": "#/components/schemas/RemitImap.SpamReportBulkResult"
2560
+ "$ref": "#/components/schemas/RemitImap.QuarantineListResponse"
2117
2561
  }
2118
2562
  }
2119
2563
  }
2120
2564
  }
2121
2565
  },
2122
- "requestBody": {
2123
- "required": true,
2124
- "content": {
2125
- "application/json": {
2126
- "schema": {
2127
- "$ref": "#/components/schemas/RemitImap.BulkMessageInput"
2128
- }
2129
- }
2130
- }
2131
- },
2132
2566
  "x-amazon-apigateway-integration": {
2133
2567
  "type": "aws_proxy",
2134
2568
  "httpMethod": "POST",
@@ -2139,26 +2573,18 @@
2139
2573
  }
2140
2574
  }
2141
2575
  },
2142
- "/messages/{messageId}": {
2576
+ "/me/vip-suggestions": {
2143
2577
  "get": {
2144
- "operationId": "MessageOperations_describeMessage",
2145
- "parameters": [
2146
- {
2147
- "name": "messageId",
2148
- "in": "path",
2149
- "required": true,
2150
- "schema": {
2151
- "$ref": "#/components/schemas/UUID"
2152
- }
2153
- }
2154
- ],
2578
+ "operationId": "MeOperations_listVipSuggestions",
2579
+ "description": "List wellknown senders the user has not yet promoted to VIP. Ranked by `replyCount + inboundCount` desc with `lastInboundAt` desc as the tie-breaker. Capped at 10.",
2580
+ "parameters": [],
2155
2581
  "responses": {
2156
2582
  "200": {
2157
2583
  "description": "The request has succeeded.",
2158
2584
  "content": {
2159
2585
  "application/json": {
2160
2586
  "schema": {
2161
- "$ref": "#/components/schemas/RemitImap.DescribeMessageResponse"
2587
+ "$ref": "#/components/schemas/RemitImap.VipSuggestionsResponse"
2162
2588
  }
2163
2589
  }
2164
2590
  }
@@ -2174,42 +2600,28 @@
2174
2600
  }
2175
2601
  }
2176
2602
  },
2177
- "/messages/{messageId}/flags": {
2178
- "patch": {
2179
- "operationId": "MessageOperations_updateMessageFlags",
2180
- "parameters": [
2181
- {
2182
- "name": "messageId",
2183
- "in": "path",
2184
- "required": true,
2185
- "schema": {
2186
- "$ref": "#/components/schemas/UUID"
2187
- }
2188
- }
2189
- ],
2603
+ "/messages/copy": {
2604
+ "post": {
2605
+ "operationId": "MessageBulkOperations_copyMessages",
2606
+ "description": "Copy messages to a destination mailbox",
2607
+ "parameters": [],
2190
2608
  "responses": {
2191
2609
  "200": {
2192
2610
  "description": "The request has succeeded.",
2193
2611
  "content": {
2194
2612
  "application/json": {
2195
2613
  "schema": {
2196
- "type": "object",
2197
- "properties": {
2198
- "messageId": {
2199
- "$ref": "#/components/schemas/UUID"
2200
- },
2201
- "isRead": {
2202
- "type": "boolean"
2203
- },
2204
- "isStarred": {
2205
- "type": "boolean"
2206
- }
2207
- },
2208
- "required": [
2209
- "messageId",
2210
- "isRead",
2211
- "isStarred"
2212
- ]
2614
+ "$ref": "#/components/schemas/RemitImap.MessageBulkOperationResult"
2615
+ }
2616
+ }
2617
+ }
2618
+ },
2619
+ "409": {
2620
+ "description": "The same flat 409 body, for a message whose folder and uid do not name the same message.\n\n`code: \"message_placement_unsettled\"` — the message is mid-move, so its row names the destination folder while its uid still belongs to the source; acting on that pair would hit whatever the destination holds at that uid. `details` carries `accountId`, `messageId` and `reason`, one of: `in_flight` (the move is still running, and the same request succeeds once it confirms) or `unverified` (the move gave up without confirming, so where the message sits is unknown until the folder is resynced).",
2621
+ "content": {
2622
+ "application/json": {
2623
+ "schema": {
2624
+ "$ref": "#/components/schemas/ApiError"
2213
2625
  }
2214
2626
  }
2215
2627
  }
@@ -2220,7 +2632,7 @@
2220
2632
  "content": {
2221
2633
  "application/json": {
2222
2634
  "schema": {
2223
- "$ref": "#/components/schemas/RemitImap.UpdateFlagsInput"
2635
+ "$ref": "#/components/schemas/RemitImap.CopyMessagesInput"
2224
2636
  }
2225
2637
  }
2226
2638
  }
@@ -2235,32 +2647,43 @@
2235
2647
  }
2236
2648
  }
2237
2649
  },
2238
- "/messages/{messageId}/raw": {
2239
- "get": {
2240
- "operationId": "MessageOperations_getRawMessage",
2241
- "description": "Get the raw RFC822/MIME source of a message (headers and body) for inspection.",
2242
- "parameters": [
2243
- {
2244
- "name": "messageId",
2245
- "in": "path",
2246
- "required": true,
2247
- "schema": {
2248
- "$ref": "#/components/schemas/UUID"
2249
- }
2250
- }
2251
- ],
2650
+ "/messages/delete": {
2651
+ "post": {
2652
+ "operationId": "MessageBulkOperations_deleteMessages",
2653
+ "description": "Delete messages (move to Trash or permanent delete)",
2654
+ "parameters": [],
2252
2655
  "responses": {
2253
2656
  "200": {
2254
2657
  "description": "The request has succeeded.",
2255
2658
  "content": {
2256
2659
  "application/json": {
2257
2660
  "schema": {
2258
- "$ref": "#/components/schemas/RemitImap.RawMessageResponse"
2661
+ "$ref": "#/components/schemas/RemitImap.MessageBulkOperationResult"
2662
+ }
2663
+ }
2664
+ }
2665
+ },
2666
+ "409": {
2667
+ "description": "The same flat 409 body, with the two codes this endpoint can answer with.\n\n`code: \"folder_role_unresolved\"` — as everywhere else, with `details` carrying `role`, `accountId` and `reason`.\n\n`code: \"message_placement_unsettled\"` — as `MessagePlacementConflict`, with `details` carrying `accountId`, `messageId` and `reason`.",
2668
+ "content": {
2669
+ "application/json": {
2670
+ "schema": {
2671
+ "$ref": "#/components/schemas/ApiError"
2259
2672
  }
2260
2673
  }
2261
2674
  }
2262
2675
  }
2263
2676
  },
2677
+ "requestBody": {
2678
+ "required": true,
2679
+ "content": {
2680
+ "application/json": {
2681
+ "schema": {
2682
+ "$ref": "#/components/schemas/RemitImap.DeleteMessagesInput"
2683
+ }
2684
+ }
2685
+ }
2686
+ },
2264
2687
  "x-amazon-apigateway-integration": {
2265
2688
  "type": "aws_proxy",
2266
2689
  "httpMethod": "POST",
@@ -2271,10 +2694,10 @@
2271
2694
  }
2272
2695
  }
2273
2696
  },
2274
- "/outbox": {
2697
+ "/messages/flags": {
2275
2698
  "post": {
2276
- "operationId": "OutboxOperations_createOutboxMessage",
2277
- "description": "Create a new outbox message (draft or send immediately)",
2699
+ "operationId": "MessageBulkOperations_updateFlags",
2700
+ "description": "Update flags on multiple messages",
2278
2701
  "parameters": [],
2279
2702
  "responses": {
2280
2703
  "200": {
@@ -2282,7 +2705,7 @@
2282
2705
  "content": {
2283
2706
  "application/json": {
2284
2707
  "schema": {
2285
- "$ref": "#/components/schemas/RemitImap.OutboxMessageResponse"
2708
+ "$ref": "#/components/schemas/RemitImap.MessageBulkOperationResult"
2286
2709
  }
2287
2710
  }
2288
2711
  }
@@ -2293,7 +2716,7 @@
2293
2716
  "content": {
2294
2717
  "application/json": {
2295
2718
  "schema": {
2296
- "$ref": "#/components/schemas/RemitImap.CreateOutboxMessageInput"
2719
+ "$ref": "#/components/schemas/RemitImap.BulkUpdateFlagsInput"
2297
2720
  }
2298
2721
  }
2299
2722
  }
@@ -2306,51 +2729,35 @@
2306
2729
  "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
2307
2730
  }
2308
2731
  }
2309
- },
2310
- "get": {
2311
- "operationId": "OutboxOperations_listOutboxMessages",
2312
- "description": "List outbox messages for the current account",
2313
- "parameters": [
2314
- {
2315
- "name": "continuationToken",
2316
- "in": "query",
2317
- "required": false,
2318
- "schema": {
2319
- "type": "string"
2320
- }
2321
- }
2322
- ],
2732
+ }
2733
+ },
2734
+ "/messages/labels": {
2735
+ "post": {
2736
+ "operationId": "MessageBulkOperations_updateMessageLabels",
2737
+ "description": "Apply or remove a label on multiple messages — the manual \"just these\" scope (RFC 034 recap, issue #26). `appliedByFilterId` stays absent on the resulting `MessageLabel` rows; that provenance is reserved for filter/organize applies.",
2738
+ "parameters": [],
2323
2739
  "responses": {
2324
2740
  "200": {
2325
2741
  "description": "The request has succeeded.",
2326
2742
  "content": {
2327
2743
  "application/json": {
2328
2744
  "schema": {
2329
- "type": "object",
2330
- "required": [
2331
- "items"
2332
- ],
2333
- "properties": {
2334
- "continuationToken": {
2335
- "allOf": [
2336
- {
2337
- "$ref": "#/components/schemas/String800"
2338
- }
2339
- ],
2340
- "description": "Opaque, server-minted cursor: absent means the first (or last) page, present means resume from here. Echo it back unchanged to fetch the next page. Every listing endpoint that accepts it rejects a token it cannot decode with 400 ValidationError rather than silently restarting from the first page (#136, #172)."
2341
- },
2342
- "items": {
2343
- "type": "array",
2344
- "items": {
2345
- "$ref": "#/components/schemas/RemitImap.OutboxMessageResponse"
2346
- }
2347
- }
2348
- }
2745
+ "$ref": "#/components/schemas/RemitImap.MessageBulkOperationResult"
2349
2746
  }
2350
2747
  }
2351
2748
  }
2352
2749
  }
2353
2750
  },
2751
+ "requestBody": {
2752
+ "required": true,
2753
+ "content": {
2754
+ "application/json": {
2755
+ "schema": {
2756
+ "$ref": "#/components/schemas/RemitImap.UpdateMessageLabelsInput"
2757
+ }
2758
+ }
2759
+ }
2760
+ },
2354
2761
  "x-amazon-apigateway-integration": {
2355
2762
  "type": "aws_proxy",
2356
2763
  "httpMethod": "POST",
@@ -2361,33 +2768,33 @@
2361
2768
  }
2362
2769
  }
2363
2770
  },
2364
- "/outbox/{outboxMessageId}": {
2365
- "get": {
2366
- "operationId": "OutboxDetailOperations_getOutboxMessage",
2367
- "description": "Get an outbox message by ID",
2368
- "parameters": [
2369
- {
2370
- "name": "outboxMessageId",
2371
- "in": "path",
2372
- "required": true,
2373
- "schema": {
2374
- "$ref": "#/components/schemas/UUID"
2375
- }
2376
- }
2377
- ],
2771
+ "/messages/move": {
2772
+ "post": {
2773
+ "operationId": "MessageBulkOperations_moveMessages",
2774
+ "parameters": [],
2378
2775
  "responses": {
2379
2776
  "200": {
2380
2777
  "description": "The request has succeeded.",
2381
2778
  "content": {
2382
2779
  "application/json": {
2383
2780
  "schema": {
2384
- "$ref": "#/components/schemas/RemitImap.OutboxMessageResponse"
2781
+ "$ref": "#/components/schemas/RemitImap.MessageBulkOperationResult"
2385
2782
  }
2386
2783
  }
2387
2784
  }
2388
2785
  }
2389
2786
  },
2390
- "x-amazon-apigateway-integration": {
2787
+ "requestBody": {
2788
+ "required": true,
2789
+ "content": {
2790
+ "application/json": {
2791
+ "schema": {
2792
+ "$ref": "#/components/schemas/RemitImap.MoveMessagesInput"
2793
+ }
2794
+ }
2795
+ }
2796
+ },
2797
+ "x-amazon-apigateway-integration": {
2391
2798
  "type": "aws_proxy",
2392
2799
  "httpMethod": "POST",
2393
2800
  "passthroughBehavior": "when_no_match",
@@ -2395,27 +2802,57 @@
2395
2802
  "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
2396
2803
  }
2397
2804
  }
2398
- },
2399
- "patch": {
2400
- "operationId": "OutboxDetailOperations_updateOutboxMessage",
2401
- "description": "Update a draft outbox message",
2402
- "parameters": [
2403
- {
2404
- "name": "outboxMessageId",
2405
- "in": "path",
2406
- "required": true,
2407
- "schema": {
2408
- "$ref": "#/components/schemas/UUID"
2805
+ }
2806
+ },
2807
+ "/messages/not-spam": {
2808
+ "post": {
2809
+ "operationId": "MessageBulkOperations_notSpam",
2810
+ "description": "Undo a spam report: clear the sender's `blocked` flag (never promotes to trusted/wellknown) and move each message back to `Message.originalMailboxId`. Idempotent under a repeated call. Each message is processed independently and concurrently, so the request's total wait is bounded by one message's settle timeout rather than accumulating across the batch.",
2811
+ "parameters": [],
2812
+ "responses": {
2813
+ "200": {
2814
+ "description": "The request has succeeded.",
2815
+ "content": {
2816
+ "application/json": {
2817
+ "schema": {
2818
+ "$ref": "#/components/schemas/RemitImap.SpamReportBulkResult"
2819
+ }
2820
+ }
2409
2821
  }
2410
2822
  }
2411
- ],
2823
+ },
2824
+ "requestBody": {
2825
+ "required": true,
2826
+ "content": {
2827
+ "application/json": {
2828
+ "schema": {
2829
+ "$ref": "#/components/schemas/RemitImap.BulkMessageInput"
2830
+ }
2831
+ }
2832
+ }
2833
+ },
2834
+ "x-amazon-apigateway-integration": {
2835
+ "type": "aws_proxy",
2836
+ "httpMethod": "POST",
2837
+ "passthroughBehavior": "when_no_match",
2838
+ "uri": {
2839
+ "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
2840
+ }
2841
+ }
2842
+ }
2843
+ },
2844
+ "/messages/report-spam": {
2845
+ "post": {
2846
+ "operationId": "MessageBulkOperations_reportSpam",
2847
+ "description": "Report messages as spam: block the sender (Address.flags.blocked) and move each message to the account's Junk folder. Skips the block (but still moves the message) when the sender is the account's own address. One call replaces composing the old separate `Block` and `Mark spam` actions — a single user intent, a single failure story. The sender block is written first and does not wait on the move; a failed move never rolls it back. Idempotent under a repeated call.",
2848
+ "parameters": [],
2412
2849
  "responses": {
2413
2850
  "200": {
2414
2851
  "description": "The request has succeeded.",
2415
2852
  "content": {
2416
2853
  "application/json": {
2417
2854
  "schema": {
2418
- "$ref": "#/components/schemas/RemitImap.OutboxMessageResponse"
2855
+ "$ref": "#/components/schemas/RemitImap.SpamReportBulkResult"
2419
2856
  }
2420
2857
  }
2421
2858
  }
@@ -2426,7 +2863,7 @@
2426
2863
  "content": {
2427
2864
  "application/json": {
2428
2865
  "schema": {
2429
- "$ref": "#/components/schemas/RemitImap.UpdateOutboxMessageInput"
2866
+ "$ref": "#/components/schemas/RemitImap.BulkMessageInput"
2430
2867
  }
2431
2868
  }
2432
2869
  }
@@ -2439,13 +2876,14 @@
2439
2876
  "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
2440
2877
  }
2441
2878
  }
2442
- },
2443
- "delete": {
2444
- "operationId": "OutboxDetailOperations_deleteOutboxMessage",
2445
- "description": "Delete a draft or failed outbox message",
2879
+ }
2880
+ },
2881
+ "/messages/{messageId}": {
2882
+ "get": {
2883
+ "operationId": "MessageOperations_describeMessage",
2446
2884
  "parameters": [
2447
2885
  {
2448
- "name": "outboxMessageId",
2886
+ "name": "messageId",
2449
2887
  "in": "path",
2450
2888
  "required": true,
2451
2889
  "schema": {
@@ -2454,8 +2892,15 @@
2454
2892
  }
2455
2893
  ],
2456
2894
  "responses": {
2457
- "204": {
2458
- "description": "There is no content to send for this request, but the headers may be useful. "
2895
+ "200": {
2896
+ "description": "The request has succeeded.",
2897
+ "content": {
2898
+ "application/json": {
2899
+ "schema": {
2900
+ "$ref": "#/components/schemas/RemitImap.DescribeMessageResponse"
2901
+ }
2902
+ }
2903
+ }
2459
2904
  }
2460
2905
  },
2461
2906
  "x-amazon-apigateway-integration": {
@@ -2468,13 +2913,12 @@
2468
2913
  }
2469
2914
  }
2470
2915
  },
2471
- "/outbox/{outboxMessageId}/attachments": {
2472
- "post": {
2473
- "operationId": "OutboxDetailOperations_mintOutboxAttachment",
2474
- "description": "Reserve room on a draft for a file, and get somewhere to put it",
2916
+ "/messages/{messageId}/flags": {
2917
+ "patch": {
2918
+ "operationId": "MessageOperations_updateMessageFlags",
2475
2919
  "parameters": [
2476
2920
  {
2477
- "name": "outboxMessageId",
2921
+ "name": "messageId",
2478
2922
  "in": "path",
2479
2923
  "required": true,
2480
2924
  "schema": {
@@ -2488,27 +2932,23 @@
2488
2932
  "content": {
2489
2933
  "application/json": {
2490
2934
  "schema": {
2491
- "$ref": "#/components/schemas/RemitImap.MintOutboxAttachmentResponse"
2492
- }
2493
- }
2494
- }
2495
- },
2496
- "400": {
2497
- "description": "Refusal response for an attachment",
2498
- "content": {
2499
- "application/json": {
2500
- "schema": {
2501
- "$ref": "#/components/schemas/RemitImap.OutboxAttachmentRejection"
2502
- }
2503
- }
2504
- }
2505
- },
2506
- "413": {
2507
- "description": "Refusal response for an attachment",
2508
- "content": {
2509
- "application/json": {
2510
- "schema": {
2511
- "$ref": "#/components/schemas/RemitImap.OutboxAttachmentRejection"
2935
+ "type": "object",
2936
+ "properties": {
2937
+ "messageId": {
2938
+ "$ref": "#/components/schemas/UUID"
2939
+ },
2940
+ "isRead": {
2941
+ "type": "boolean"
2942
+ },
2943
+ "isStarred": {
2944
+ "type": "boolean"
2945
+ }
2946
+ },
2947
+ "required": [
2948
+ "messageId",
2949
+ "isRead",
2950
+ "isStarred"
2951
+ ]
2512
2952
  }
2513
2953
  }
2514
2954
  }
@@ -2519,7 +2959,7 @@
2519
2959
  "content": {
2520
2960
  "application/json": {
2521
2961
  "schema": {
2522
- "$ref": "#/components/schemas/RemitImap.MintOutboxAttachmentInput"
2962
+ "$ref": "#/components/schemas/RemitImap.UpdateFlagsInput"
2523
2963
  }
2524
2964
  }
2525
2965
  }
@@ -2534,21 +2974,13 @@
2534
2974
  }
2535
2975
  }
2536
2976
  },
2537
- "/outbox/{outboxMessageId}/attachments/{outboxAttachmentId}/complete": {
2538
- "post": {
2539
- "operationId": "OutboxAttachmentOperations_completeOutboxAttachment",
2540
- "description": "Confirm an uploaded file against the object storage now holds",
2977
+ "/messages/{messageId}/raw": {
2978
+ "get": {
2979
+ "operationId": "MessageOperations_getRawMessage",
2980
+ "description": "Get the raw RFC822/MIME source of a message (headers and body) for inspection.",
2541
2981
  "parameters": [
2542
2982
  {
2543
- "name": "outboxMessageId",
2544
- "in": "path",
2545
- "required": true,
2546
- "schema": {
2547
- "$ref": "#/components/schemas/UUID"
2548
- }
2549
- },
2550
- {
2551
- "name": "outboxAttachmentId",
2983
+ "name": "messageId",
2552
2984
  "in": "path",
2553
2985
  "required": true,
2554
2986
  "schema": {
@@ -2562,27 +2994,97 @@
2562
2994
  "content": {
2563
2995
  "application/json": {
2564
2996
  "schema": {
2565
- "$ref": "#/components/schemas/RemitImap.OutboxAttachmentResponse"
2997
+ "$ref": "#/components/schemas/RemitImap.RawMessageResponse"
2566
2998
  }
2567
2999
  }
2568
3000
  }
2569
- },
2570
- "400": {
2571
- "description": "Refusal response for an attachment",
3001
+ }
3002
+ },
3003
+ "x-amazon-apigateway-integration": {
3004
+ "type": "aws_proxy",
3005
+ "httpMethod": "POST",
3006
+ "passthroughBehavior": "when_no_match",
3007
+ "uri": {
3008
+ "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
3009
+ }
3010
+ }
3011
+ }
3012
+ },
3013
+ "/outbox": {
3014
+ "post": {
3015
+ "operationId": "OutboxOperations_createOutboxMessage",
3016
+ "description": "Create a new outbox message (draft or send immediately)",
3017
+ "parameters": [],
3018
+ "responses": {
3019
+ "200": {
3020
+ "description": "The request has succeeded.",
2572
3021
  "content": {
2573
3022
  "application/json": {
2574
3023
  "schema": {
2575
- "$ref": "#/components/schemas/RemitImap.OutboxAttachmentRejection"
3024
+ "$ref": "#/components/schemas/RemitImap.OutboxMessageResponse"
2576
3025
  }
2577
3026
  }
2578
3027
  }
2579
- },
2580
- "413": {
2581
- "description": "Refusal response for an attachment",
3028
+ }
3029
+ },
3030
+ "requestBody": {
3031
+ "required": true,
3032
+ "content": {
3033
+ "application/json": {
3034
+ "schema": {
3035
+ "$ref": "#/components/schemas/RemitImap.CreateOutboxMessageInput"
3036
+ }
3037
+ }
3038
+ }
3039
+ },
3040
+ "x-amazon-apigateway-integration": {
3041
+ "type": "aws_proxy",
3042
+ "httpMethod": "POST",
3043
+ "passthroughBehavior": "when_no_match",
3044
+ "uri": {
3045
+ "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
3046
+ }
3047
+ }
3048
+ },
3049
+ "get": {
3050
+ "operationId": "OutboxOperations_listOutboxMessages",
3051
+ "description": "List outbox messages for the current account",
3052
+ "parameters": [
3053
+ {
3054
+ "name": "continuationToken",
3055
+ "in": "query",
3056
+ "required": false,
3057
+ "schema": {
3058
+ "type": "string"
3059
+ }
3060
+ }
3061
+ ],
3062
+ "responses": {
3063
+ "200": {
3064
+ "description": "The request has succeeded.",
2582
3065
  "content": {
2583
3066
  "application/json": {
2584
3067
  "schema": {
2585
- "$ref": "#/components/schemas/RemitImap.OutboxAttachmentRejection"
3068
+ "type": "object",
3069
+ "required": [
3070
+ "items"
3071
+ ],
3072
+ "properties": {
3073
+ "continuationToken": {
3074
+ "allOf": [
3075
+ {
3076
+ "$ref": "#/components/schemas/String800"
3077
+ }
3078
+ ],
3079
+ "description": "Opaque, server-minted cursor: absent means the first (or last) page, present means resume from here. Echo it back unchanged to fetch the next page. Every listing endpoint that accepts it rejects a token it cannot decode with 400 ValidationError rather than silently restarting from the first page (#136, #172)."
3080
+ },
3081
+ "items": {
3082
+ "type": "array",
3083
+ "items": {
3084
+ "$ref": "#/components/schemas/RemitImap.OutboxMessageResponse"
3085
+ }
3086
+ }
3087
+ }
2586
3088
  }
2587
3089
  }
2588
3090
  }
@@ -2598,10 +3100,10 @@
2598
3100
  }
2599
3101
  }
2600
3102
  },
2601
- "/outbox/{outboxMessageId}/send": {
2602
- "post": {
2603
- "operationId": "OutboxDetailOperations_sendOutboxMessage",
2604
- "description": "Queue a draft message for sending",
3103
+ "/outbox/{outboxMessageId}": {
3104
+ "get": {
3105
+ "operationId": "OutboxDetailOperations_getOutboxMessage",
3106
+ "description": "Get an outbox message by ID",
2605
3107
  "parameters": [
2606
3108
  {
2607
3109
  "name": "outboxMessageId",
@@ -2632,8 +3134,245 @@
2632
3134
  "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
2633
3135
  }
2634
3136
  }
2635
- }
2636
- },
3137
+ },
3138
+ "patch": {
3139
+ "operationId": "OutboxDetailOperations_updateOutboxMessage",
3140
+ "description": "Update a draft outbox message",
3141
+ "parameters": [
3142
+ {
3143
+ "name": "outboxMessageId",
3144
+ "in": "path",
3145
+ "required": true,
3146
+ "schema": {
3147
+ "$ref": "#/components/schemas/UUID"
3148
+ }
3149
+ }
3150
+ ],
3151
+ "responses": {
3152
+ "200": {
3153
+ "description": "The request has succeeded.",
3154
+ "content": {
3155
+ "application/json": {
3156
+ "schema": {
3157
+ "$ref": "#/components/schemas/RemitImap.OutboxMessageResponse"
3158
+ }
3159
+ }
3160
+ }
3161
+ }
3162
+ },
3163
+ "requestBody": {
3164
+ "required": true,
3165
+ "content": {
3166
+ "application/json": {
3167
+ "schema": {
3168
+ "$ref": "#/components/schemas/RemitImap.UpdateOutboxMessageInput"
3169
+ }
3170
+ }
3171
+ }
3172
+ },
3173
+ "x-amazon-apigateway-integration": {
3174
+ "type": "aws_proxy",
3175
+ "httpMethod": "POST",
3176
+ "passthroughBehavior": "when_no_match",
3177
+ "uri": {
3178
+ "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
3179
+ }
3180
+ }
3181
+ },
3182
+ "delete": {
3183
+ "operationId": "OutboxDetailOperations_deleteOutboxMessage",
3184
+ "description": "Delete a draft or failed outbox message",
3185
+ "parameters": [
3186
+ {
3187
+ "name": "outboxMessageId",
3188
+ "in": "path",
3189
+ "required": true,
3190
+ "schema": {
3191
+ "$ref": "#/components/schemas/UUID"
3192
+ }
3193
+ }
3194
+ ],
3195
+ "responses": {
3196
+ "204": {
3197
+ "description": "There is no content to send for this request, but the headers may be useful. "
3198
+ }
3199
+ },
3200
+ "x-amazon-apigateway-integration": {
3201
+ "type": "aws_proxy",
3202
+ "httpMethod": "POST",
3203
+ "passthroughBehavior": "when_no_match",
3204
+ "uri": {
3205
+ "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
3206
+ }
3207
+ }
3208
+ }
3209
+ },
3210
+ "/outbox/{outboxMessageId}/attachments": {
3211
+ "post": {
3212
+ "operationId": "OutboxDetailOperations_mintOutboxAttachment",
3213
+ "description": "Reserve room on a draft for a file, and get somewhere to put it",
3214
+ "parameters": [
3215
+ {
3216
+ "name": "outboxMessageId",
3217
+ "in": "path",
3218
+ "required": true,
3219
+ "schema": {
3220
+ "$ref": "#/components/schemas/UUID"
3221
+ }
3222
+ }
3223
+ ],
3224
+ "responses": {
3225
+ "200": {
3226
+ "description": "The request has succeeded.",
3227
+ "content": {
3228
+ "application/json": {
3229
+ "schema": {
3230
+ "$ref": "#/components/schemas/RemitImap.MintOutboxAttachmentResponse"
3231
+ }
3232
+ }
3233
+ }
3234
+ },
3235
+ "400": {
3236
+ "description": "Refusal response for an attachment",
3237
+ "content": {
3238
+ "application/json": {
3239
+ "schema": {
3240
+ "$ref": "#/components/schemas/RemitImap.OutboxAttachmentRejection"
3241
+ }
3242
+ }
3243
+ }
3244
+ },
3245
+ "413": {
3246
+ "description": "Refusal response for an attachment",
3247
+ "content": {
3248
+ "application/json": {
3249
+ "schema": {
3250
+ "$ref": "#/components/schemas/RemitImap.OutboxAttachmentRejection"
3251
+ }
3252
+ }
3253
+ }
3254
+ }
3255
+ },
3256
+ "requestBody": {
3257
+ "required": true,
3258
+ "content": {
3259
+ "application/json": {
3260
+ "schema": {
3261
+ "$ref": "#/components/schemas/RemitImap.MintOutboxAttachmentInput"
3262
+ }
3263
+ }
3264
+ }
3265
+ },
3266
+ "x-amazon-apigateway-integration": {
3267
+ "type": "aws_proxy",
3268
+ "httpMethod": "POST",
3269
+ "passthroughBehavior": "when_no_match",
3270
+ "uri": {
3271
+ "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
3272
+ }
3273
+ }
3274
+ }
3275
+ },
3276
+ "/outbox/{outboxMessageId}/attachments/{outboxAttachmentId}/complete": {
3277
+ "post": {
3278
+ "operationId": "OutboxAttachmentOperations_completeOutboxAttachment",
3279
+ "description": "Confirm an uploaded file against the object storage now holds",
3280
+ "parameters": [
3281
+ {
3282
+ "name": "outboxMessageId",
3283
+ "in": "path",
3284
+ "required": true,
3285
+ "schema": {
3286
+ "$ref": "#/components/schemas/UUID"
3287
+ }
3288
+ },
3289
+ {
3290
+ "name": "outboxAttachmentId",
3291
+ "in": "path",
3292
+ "required": true,
3293
+ "schema": {
3294
+ "$ref": "#/components/schemas/UUID"
3295
+ }
3296
+ }
3297
+ ],
3298
+ "responses": {
3299
+ "200": {
3300
+ "description": "The request has succeeded.",
3301
+ "content": {
3302
+ "application/json": {
3303
+ "schema": {
3304
+ "$ref": "#/components/schemas/RemitImap.OutboxAttachmentResponse"
3305
+ }
3306
+ }
3307
+ }
3308
+ },
3309
+ "400": {
3310
+ "description": "Refusal response for an attachment",
3311
+ "content": {
3312
+ "application/json": {
3313
+ "schema": {
3314
+ "$ref": "#/components/schemas/RemitImap.OutboxAttachmentRejection"
3315
+ }
3316
+ }
3317
+ }
3318
+ },
3319
+ "413": {
3320
+ "description": "Refusal response for an attachment",
3321
+ "content": {
3322
+ "application/json": {
3323
+ "schema": {
3324
+ "$ref": "#/components/schemas/RemitImap.OutboxAttachmentRejection"
3325
+ }
3326
+ }
3327
+ }
3328
+ }
3329
+ },
3330
+ "x-amazon-apigateway-integration": {
3331
+ "type": "aws_proxy",
3332
+ "httpMethod": "POST",
3333
+ "passthroughBehavior": "when_no_match",
3334
+ "uri": {
3335
+ "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
3336
+ }
3337
+ }
3338
+ }
3339
+ },
3340
+ "/outbox/{outboxMessageId}/send": {
3341
+ "post": {
3342
+ "operationId": "OutboxDetailOperations_sendOutboxMessage",
3343
+ "description": "Queue a draft message for sending",
3344
+ "parameters": [
3345
+ {
3346
+ "name": "outboxMessageId",
3347
+ "in": "path",
3348
+ "required": true,
3349
+ "schema": {
3350
+ "$ref": "#/components/schemas/UUID"
3351
+ }
3352
+ }
3353
+ ],
3354
+ "responses": {
3355
+ "200": {
3356
+ "description": "The request has succeeded.",
3357
+ "content": {
3358
+ "application/json": {
3359
+ "schema": {
3360
+ "$ref": "#/components/schemas/RemitImap.OutboxMessageResponse"
3361
+ }
3362
+ }
3363
+ }
3364
+ }
3365
+ },
3366
+ "x-amazon-apigateway-integration": {
3367
+ "type": "aws_proxy",
3368
+ "httpMethod": "POST",
3369
+ "passthroughBehavior": "when_no_match",
3370
+ "uri": {
3371
+ "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:{{HandlerFunctionName}}/invocations"
3372
+ }
3373
+ }
3374
+ }
3375
+ },
2637
3376
  "/search/semantic": {
2638
3377
  "get": {
2639
3378
  "operationId": "SemanticSearchOperations_semanticSearch",
@@ -3684,276 +4423,798 @@
3684
4423
  "readOnly": true
3685
4424
  }
3686
4425
  },
3687
- "description": "Address response for address book entries"
4426
+ "description": "Address response for address book entries"
4427
+ },
4428
+ "RemitImap.AddressRole": {
4429
+ "type": "string",
4430
+ "enum": [
4431
+ "from",
4432
+ "sender",
4433
+ "reply_to",
4434
+ "to",
4435
+ "cc",
4436
+ "bcc"
4437
+ ],
4438
+ "description": "Address roles in message headers per RFC 2822 Sections 3.6.2 and 3.6.3"
4439
+ },
4440
+ "RemitImap.ApplyUpdateInput": {
4441
+ "type": "object",
4442
+ "required": [
4443
+ "targetVersion"
4444
+ ],
4445
+ "properties": {
4446
+ "targetVersion": {
4447
+ "type": "string",
4448
+ "description": "The release version to install, `vX.Y.Z`"
4449
+ }
4450
+ },
4451
+ "description": "Request to install a specific release. `targetVersion` must equal the version the latest check reported; the backend passes exactly this one field across the control seam and resolves nothing else (RFC 037 R6)."
4452
+ },
4453
+ "RemitImap.AppointFolderRoleInput": {
4454
+ "type": "object",
4455
+ "required": [
4456
+ "mailboxId"
4457
+ ],
4458
+ "properties": {
4459
+ "mailboxId": {
4460
+ "type": "string",
4461
+ "nullable": true,
4462
+ "description": "Mailbox to appoint to the role, or `null` to clear the appointment. Typed as an inline nullable string rather than `UUID | null`: a named scalar union renders as allOf + nullable with no sibling type, which openapi-backend's AJV rejects for a `null` value — the same gotcha documented on RenameMailboxInput.displayNameOverride. An inline string keeps `null` valid at the wire; the handler validates the value as a UUID."
4463
+ }
4464
+ },
4465
+ "description": "Input for PUT /accounts/{accountId}/folder-roles/{role}. Appoints the mailbox to the role, replacing whichever mailbox previously held it (exclusive by construction — the map has one slot per role). Pass `mailboxId: null` to clear the role back to unfilled."
4466
+ },
4467
+ "RemitImap.AutoArchiveFlag": {
4468
+ "type": "object",
4469
+ "required": [
4470
+ "value"
4471
+ ],
4472
+ "properties": {
4473
+ "value": {
4474
+ "type": "boolean"
4475
+ }
4476
+ },
4477
+ "allOf": [
4478
+ {
4479
+ "$ref": "#/components/schemas/RemitImap.AddressFlagBase"
4480
+ }
4481
+ ],
4482
+ "description": "Auto-archive: skip inbox, file directly"
4483
+ },
4484
+ "RemitImap.AutoArchiveFlagUpdate": {
4485
+ "type": "object",
4486
+ "properties": {
4487
+ "value": {
4488
+ "type": "boolean"
4489
+ }
4490
+ },
4491
+ "allOf": [
4492
+ {
4493
+ "$ref": "#/components/schemas/RemitImap.AddressFlagBaseUpdate"
4494
+ }
4495
+ ],
4496
+ "description": "Auto-archive: skip inbox, file directly"
4497
+ },
4498
+ "RemitImap.AutoMovedInfo": {
4499
+ "type": "object",
4500
+ "properties": {
4501
+ "action": {
4502
+ "allOf": [
4503
+ {
4504
+ "$ref": "#/components/schemas/RemitImap.PlacementAction"
4505
+ }
4506
+ ],
4507
+ "description": "Classifier move only: direction Remit moved the message — MoveToInbox | MoveToJunk. Absent for a standing-filter move, which names its destination via `destinationMailboxId` instead."
4508
+ },
4509
+ "fromPlacement": {
4510
+ "type": "string",
4511
+ "description": "Classifier move only: folder role the message was moved from — inbox | junk | other. Used to render \"moved from Junk\" and to resolve the undo target via MailboxRole. Absent for a standing-filter move, which names its source via `fromMailboxId`."
4512
+ },
4513
+ "fromMailboxId": {
4514
+ "allOf": [
4515
+ {
4516
+ "$ref": "#/components/schemas/UUID"
4517
+ }
4518
+ ],
4519
+ "description": "Standing-filter move only: the mailbox the message was moved out of. The undo target — the client resolves its display name for the badge and moves the message back here."
4520
+ },
4521
+ "destinationMailboxId": {
4522
+ "allOf": [
4523
+ {
4524
+ "$ref": "#/components/schemas/UUID"
4525
+ }
4526
+ ],
4527
+ "description": "Standing-filter move only: the mailbox the filter moved the message into. The move is still in effect while the message sits here."
4528
+ },
4529
+ "filterId": {
4530
+ "allOf": [
4531
+ {
4532
+ "$ref": "#/components/schemas/UUID"
4533
+ }
4534
+ ],
4535
+ "description": "Standing-filter move only: the filter that moved the message, for the Settings › Filters link. Undo does not disable it."
4536
+ }
4537
+ },
4538
+ "description": "Present only when Remit auto-moved this message on body-sync. Two shapes share this model: a classifier placement move carries `action` + `fromPlacement` (Inbox/Junk direction); a standing-filter move carries `fromMailboxId` + `destinationMailboxId` + `filterId` (an arbitrary destination folder). Absent means the message was not auto-moved by Remit — clients treat absence as no indicator."
4539
+ },
4540
+ "RemitImap.BlockedFlag": {
4541
+ "type": "object",
4542
+ "required": [
4543
+ "value"
4544
+ ],
4545
+ "properties": {
4546
+ "value": {
4547
+ "type": "boolean"
4548
+ }
4549
+ },
4550
+ "allOf": [
4551
+ {
4552
+ "$ref": "#/components/schemas/RemitImap.AddressFlagBase"
4553
+ }
4554
+ ],
4555
+ "description": "Sender is blocked: mail is placed in Junk, and images never load, even on explicit click"
4556
+ },
4557
+ "RemitImap.BlockedFlagUpdate": {
4558
+ "type": "object",
4559
+ "properties": {
4560
+ "value": {
4561
+ "type": "boolean"
4562
+ }
4563
+ },
4564
+ "allOf": [
4565
+ {
4566
+ "$ref": "#/components/schemas/RemitImap.AddressFlagBaseUpdate"
4567
+ }
4568
+ ],
4569
+ "description": "Sender is blocked: mail is placed in Junk, and images never load, even on explicit click"
4570
+ },
4571
+ "RemitImap.BodyPartResponse": {
4572
+ "type": "object",
4573
+ "required": [
4574
+ "bodyPartId",
4575
+ "mediaType",
4576
+ "mediaSubtype",
4577
+ "sizeOctets",
4578
+ "isMultipart",
4579
+ "contentUrl"
4580
+ ],
4581
+ "properties": {
4582
+ "bodyPartId": {
4583
+ "allOf": [
4584
+ {
4585
+ "$ref": "#/components/schemas/UUID"
4586
+ }
4587
+ ],
4588
+ "description": "Primary identifier",
4589
+ "readOnly": true
4590
+ },
4591
+ "mediaType": {
4592
+ "allOf": [
4593
+ {
4594
+ "$ref": "#/components/schemas/RemitImap.MediaType"
4595
+ }
4596
+ ],
4597
+ "description": "MIME type (TEXT, IMAGE, APPLICATION, etc.)",
4598
+ "readOnly": true
4599
+ },
4600
+ "mediaSubtype": {
4601
+ "type": "string",
4602
+ "minLength": 1,
4603
+ "description": "MIME subtype (PLAIN, HTML, PNG, PDF, etc.)",
4604
+ "readOnly": true
4605
+ },
4606
+ "contentId": {
4607
+ "type": "string",
4608
+ "description": "Content-ID header value",
4609
+ "readOnly": true
4610
+ },
4611
+ "sizeOctets": {
4612
+ "type": "integer",
4613
+ "format": "int64",
4614
+ "minimum": 0,
4615
+ "description": "Encoded size in octets",
4616
+ "readOnly": true
4617
+ },
4618
+ "disposition": {
4619
+ "allOf": [
4620
+ {
4621
+ "$ref": "#/components/schemas/RemitImap.ContentDisposition"
4622
+ }
4623
+ ],
4624
+ "description": "Content disposition (inline or attachment)",
4625
+ "readOnly": true
4626
+ },
4627
+ "dispositionFilename": {
4628
+ "type": "string",
4629
+ "description": "Filename from Content-Disposition",
4630
+ "readOnly": true
4631
+ },
4632
+ "isMultipart": {
4633
+ "type": "boolean",
4634
+ "description": "True if this is a multipart container",
4635
+ "readOnly": true
4636
+ },
4637
+ "contentUrl": {
4638
+ "type": "string",
4639
+ "format": "uri",
4640
+ "description": "CloudFront-fronted URL for fetching this body part. Cookie-authenticated via Cognito id_token. Path: /content/accounts/{accountConfigId}/{accountId}/messages/{messageId}/parts/{partPath}. Required: handlers must throw rather than emit a placeholder when CONTENT_DELIVERY_DOMAIN is unset."
4641
+ }
4642
+ },
4643
+ "description": "Body part information"
4644
+ },
4645
+ "RemitImap.BulkMessageInput": {
4646
+ "type": "object",
4647
+ "required": [
4648
+ "messageIds"
4649
+ ],
4650
+ "properties": {
4651
+ "messageIds": {
4652
+ "type": "array",
4653
+ "items": {
4654
+ "$ref": "#/components/schemas/UUID"
4655
+ },
4656
+ "minItems": 1,
4657
+ "maxItems": 100,
4658
+ "description": "List of message IDs to operate on"
4659
+ }
4660
+ },
4661
+ "description": "Input for bulk message operations"
4662
+ },
4663
+ "RemitImap.BulkUpdateFlagsInput": {
4664
+ "type": "object",
4665
+ "required": [
4666
+ "messageIds"
4667
+ ],
4668
+ "properties": {
4669
+ "messageIds": {
4670
+ "type": "array",
4671
+ "items": {
4672
+ "$ref": "#/components/schemas/UUID"
4673
+ },
4674
+ "minItems": 1,
4675
+ "maxItems": 100,
4676
+ "description": "List of message IDs to update"
4677
+ },
4678
+ "isRead": {
4679
+ "type": "boolean",
4680
+ "description": "Set messages as read (maps to \\Seen flag)"
4681
+ },
4682
+ "isStarred": {
4683
+ "type": "boolean",
4684
+ "description": "Set messages as starred (maps to \\Flagged flag)"
4685
+ },
4686
+ "starColor": {
4687
+ "allOf": [
4688
+ {
4689
+ "$ref": "#/components/schemas/RemitImap.StarColor"
4690
+ }
4691
+ ],
4692
+ "description": "Star color for visual categorization"
4693
+ }
4694
+ },
4695
+ "description": "Input for bulk flag update operations"
4696
+ },
4697
+ "RemitImap.CalendarColor": {
4698
+ "type": "string",
4699
+ "enum": [
4700
+ "Cal1",
4701
+ "Cal2",
4702
+ "Cal3",
4703
+ "Cal4",
4704
+ "Cal5",
4705
+ "Cal6"
4706
+ ],
4707
+ "description": "Display color for a calendar collection. Positional rather than named: the palette is a client-side concern, so the store carries the slot and the client owns what it renders (issue #15)."
4708
+ },
4709
+ "RemitImap.CalendarComponentSet": {
4710
+ "type": "string",
4711
+ "enum": [
4712
+ "VeventOnly"
4713
+ ],
4714
+ "description": "Which iCalendar components a collection accepts (RFC 4791 supported-calendar-component-set). Only VEVENT is stored today; VTODO and VJOURNAL collections would be separate members, so the value is a property of the collection rather than of each object."
4715
+ },
4716
+ "RemitImap.CalendarEventInstance": {
4717
+ "type": "object",
4718
+ "required": [
4719
+ "calendarId",
4720
+ "calendarObjectId",
4721
+ "recurrenceId",
4722
+ "icalUid",
4723
+ "summary",
4724
+ "start",
4725
+ "end",
4726
+ "allDay",
4727
+ "status",
4728
+ "transparency",
4729
+ "zoneCertainty",
4730
+ "etag",
4731
+ "hasRecurrence"
4732
+ ],
4733
+ "properties": {
4734
+ "calendarId": {
4735
+ "allOf": [
4736
+ {
4737
+ "$ref": "#/components/schemas/UUID"
4738
+ }
4739
+ ],
4740
+ "description": "Collection this occurrence belongs to"
4741
+ },
4742
+ "calendarObjectId": {
4743
+ "allOf": [
4744
+ {
4745
+ "$ref": "#/components/schemas/UUID"
4746
+ }
4747
+ ],
4748
+ "description": "Resource this occurrence was expanded from"
4749
+ },
4750
+ "recurrenceId": {
4751
+ "allOf": [
4752
+ {
4753
+ "$ref": "#/components/schemas/String64"
4754
+ }
4755
+ ],
4756
+ "description": "RECURRENCE-ID of this occurrence as an ISO 8601 UTC instant, or `\"\"` for a resource that does not recur. This is the value a scoped write passes back to name the occurrence it means."
4757
+ },
4758
+ "icalUid": {
4759
+ "allOf": [
4760
+ {
4761
+ "$ref": "#/components/schemas/String512"
4762
+ }
4763
+ ],
4764
+ "description": "The event's iCalendar UID, shared by every occurrence of the series"
4765
+ },
4766
+ "summary": {
4767
+ "allOf": [
4768
+ {
4769
+ "$ref": "#/components/schemas/String512"
4770
+ }
4771
+ ],
4772
+ "description": "Projection of the VEVENT's SUMMARY. `\"\"` when the event carries none."
4773
+ },
4774
+ "start": {
4775
+ "allOf": [
4776
+ {
4777
+ "$ref": "#/components/schemas/String64"
4778
+ }
4779
+ ],
4780
+ "description": "When this occurrence starts, as an ISO 8601 date-time with zone offset, read in the collection's timezone. A business date, not an epoch instant: an all-day occurrence carries midnight of its civil date."
4781
+ },
4782
+ "end": {
4783
+ "allOf": [
4784
+ {
4785
+ "$ref": "#/components/schemas/String64"
4786
+ }
4787
+ ],
4788
+ "description": "When this occurrence ends, as an ISO 8601 date-time with zone offset. Exclusive for an all-day occurrence, per RFC 5545 3.6.1."
4789
+ },
4790
+ "allDay": {
4791
+ "type": "boolean",
4792
+ "description": "Whether this occurrence is all-day"
4793
+ },
4794
+ "status": {
4795
+ "allOf": [
4796
+ {
4797
+ "$ref": "#/components/schemas/RemitImap.CalendarEventStatus"
4798
+ }
4799
+ ],
4800
+ "description": "Projection of the occurrence's STATUS"
4801
+ },
4802
+ "transparency": {
4803
+ "allOf": [
4804
+ {
4805
+ "$ref": "#/components/schemas/RemitImap.CalendarTransparency"
4806
+ }
4807
+ ],
4808
+ "description": "Projection of the occurrence's TRANSP — whether it consumes free/busy time"
4809
+ },
4810
+ "zoneCertainty": {
4811
+ "allOf": [
4812
+ {
4813
+ "$ref": "#/components/schemas/RemitImap.ZoneCertainty"
4814
+ }
4815
+ ],
4816
+ "description": "How the resource's start was placed on the clock. `Ambiguous` means the resource named a zone nothing could resolve, so this occurrence may be hours out; a client says so rather than presenting the guess as a fact."
4817
+ },
4818
+ "etag": {
4819
+ "allOf": [
4820
+ {
4821
+ "$ref": "#/components/schemas/String64"
4822
+ }
4823
+ ],
4824
+ "description": "Entity tag of the resource this occurrence came from. Pass it as `If-Match` on a scoped write to refuse an edit built on a version somebody has since replaced."
4825
+ },
4826
+ "hasRecurrence": {
4827
+ "type": "boolean",
4828
+ "description": "Whether the resource this occurrence came from expands to more than one instance. A client showing the recurrence-scope prompt gates on this."
4829
+ }
4830
+ },
4831
+ "description": "One occurrence of an event, in the window the caller asked for (issue #15).\n\nInstances are expanded by the server. A client never reads an RRULE and never\nreconstructs a series: it renders what this returns, and addresses one\noccurrence by `calendarObjectId` plus `recurrenceId`."
4832
+ },
4833
+ "RemitImap.CalendarEventListResponse": {
4834
+ "type": "object",
4835
+ "required": [
4836
+ "items"
4837
+ ],
4838
+ "properties": {
4839
+ "items": {
4840
+ "type": "array",
4841
+ "items": {
4842
+ "$ref": "#/components/schemas/RemitImap.CalendarEventInstance"
4843
+ }
4844
+ }
4845
+ },
4846
+ "description": "Expanded occurrences in the requested window, in start order.\n\nIncludes an occurrence that began before `from` and runs into the window, up\nto a bounded lookback — a meeting that started last night is part of this\nmorning. Unpaginated: the window is the bound."
4847
+ },
4848
+ "RemitImap.CalendarEventResponse": {
4849
+ "type": "object",
4850
+ "required": [
4851
+ "calendarObjectId",
4852
+ "calendarId",
4853
+ "resourceName",
4854
+ "icalUid",
4855
+ "icalData",
4856
+ "etag",
4857
+ "sequence",
4858
+ "syncSequence",
4859
+ "summary",
4860
+ "dtStart",
4861
+ "dtEnd",
4862
+ "allDay",
4863
+ "zoneCertainty",
4864
+ "status",
4865
+ "transparency",
4866
+ "hasRecurrence",
4867
+ "expandedThrough",
4868
+ "createdAt",
4869
+ "updatedAt"
4870
+ ],
4871
+ "properties": {
4872
+ "calendarObjectId": {
4873
+ "allOf": [
4874
+ {
4875
+ "$ref": "#/components/schemas/UUID"
4876
+ }
4877
+ ],
4878
+ "description": "Primary identifier, derived from calendarId + resourceName",
4879
+ "readOnly": true
4880
+ },
4881
+ "calendarId": {
4882
+ "allOf": [
4883
+ {
4884
+ "$ref": "#/components/schemas/UUID"
4885
+ }
4886
+ ],
4887
+ "description": "Collection this resource belongs to",
4888
+ "readOnly": true
4889
+ },
4890
+ "resourceName": {
4891
+ "allOf": [
4892
+ {
4893
+ "$ref": "#/components/schemas/String256"
4894
+ }
4895
+ ],
4896
+ "description": "Last path segment of the resource's URL, e.g. \"a1b2c3.ics\". Unique within the collection and chosen by whoever wrote the resource, never derived from its contents — a client is free to rename a resource without changing the event it holds.",
4897
+ "readOnly": true
4898
+ },
4899
+ "icalUid": {
4900
+ "allOf": [
4901
+ {
4902
+ "$ref": "#/components/schemas/String512"
4903
+ }
4904
+ ],
4905
+ "description": "The VEVENT's UID property (RFC 5545 3.8.4.7). Identifies the event across collections and servers, which `resourceName` does not.",
4906
+ "readOnly": true
4907
+ },
4908
+ "icalData": {
4909
+ "type": "string",
4910
+ "description": "The complete VCALENDAR text, byte-for-byte as stored — CANONICAL. Line endings are CRLF and are never normalized; unknown properties and components are preserved verbatim.",
4911
+ "readOnly": true
4912
+ },
4913
+ "etag": {
4914
+ "allOf": [
4915
+ {
4916
+ "$ref": "#/components/schemas/String64"
4917
+ }
4918
+ ],
4919
+ "description": "Strong entity tag over the stored `icalData` bytes, used for If-Match on a conditional write and for change detection on read.",
4920
+ "readOnly": true
4921
+ },
4922
+ "sequence": {
4923
+ "type": "integer",
4924
+ "format": "int32",
4925
+ "description": "The VEVENT's SEQUENCE property (RFC 5545 3.8.7.4) — the organizer's own revision counter, carried for scheduling comparisons. `0` when the event declares none, which is the RFC default.",
4926
+ "default": 0,
4927
+ "readOnly": true
4928
+ },
4929
+ "syncSequence": {
4930
+ "type": "integer",
4931
+ "format": "int64",
4932
+ "description": "Value the owning collection's `syncSequence` held after the write that produced this row. Pages the `bySyncSequence` read.",
4933
+ "default": 0,
4934
+ "readOnly": true
4935
+ },
4936
+ "summary": {
4937
+ "allOf": [
4938
+ {
4939
+ "$ref": "#/components/schemas/String512"
4940
+ }
4941
+ ],
4942
+ "description": "Projection of the VEVENT's SUMMARY (RFC 5545 3.8.1.12). `\"\"` when the event carries none.",
4943
+ "readOnly": true
4944
+ },
4945
+ "dtStart": {
4946
+ "allOf": [
4947
+ {
4948
+ "$ref": "#/components/schemas/String64"
4949
+ }
4950
+ ],
4951
+ "description": "Projection of the master VEVENT's DTSTART as an ISO 8601 date-time with zone offset — a business date, not an epoch instant. An all-day event carries midnight in the event's own zone.",
4952
+ "readOnly": true
4953
+ },
4954
+ "dtEnd": {
4955
+ "allOf": [
4956
+ {
4957
+ "$ref": "#/components/schemas/String64"
4958
+ }
4959
+ ],
4960
+ "description": "Projection of the master VEVENT's effective end as an ISO 8601 date-time with zone offset, resolved from DTEND or from DTSTART + DURATION. An event with neither ends when it starts (RFC 5545 3.6.1).",
4961
+ "readOnly": true
4962
+ },
4963
+ "allDay": {
4964
+ "type": "boolean",
4965
+ "description": "Whether the master VEVENT's DTSTART is a DATE rather than a DATE-TIME (RFC 5545 3.3.4) — an all-day or multi-day event, whose end is exclusive.",
4966
+ "default": false,
4967
+ "readOnly": true
4968
+ },
4969
+ "zoneCertainty": {
4970
+ "allOf": [
4971
+ {
4972
+ "$ref": "#/components/schemas/RemitImap.ZoneCertainty"
4973
+ }
4974
+ ],
4975
+ "description": "How the master VEVENT's DTSTART was placed on the clock. `Ambiguous` means the resource named a zone nothing could resolve, so `dtStart` and every occurrence expanded from it may be off by hours — a client shows the event and says so rather than presenting a guess as a fact.",
4976
+ "default": "Explicit",
4977
+ "readOnly": true
4978
+ },
4979
+ "status": {
4980
+ "allOf": [
4981
+ {
4982
+ "$ref": "#/components/schemas/RemitImap.CalendarEventStatus"
4983
+ }
4984
+ ],
4985
+ "description": "Projection of the master VEVENT's STATUS",
4986
+ "default": "Confirmed",
4987
+ "readOnly": true
4988
+ },
4989
+ "transparency": {
4990
+ "allOf": [
4991
+ {
4992
+ "$ref": "#/components/schemas/RemitImap.CalendarTransparency"
4993
+ }
4994
+ ],
4995
+ "description": "Projection of the master VEVENT's TRANSP",
4996
+ "default": "Opaque",
4997
+ "readOnly": true
4998
+ },
4999
+ "hasRecurrence": {
5000
+ "type": "boolean",
5001
+ "description": "Whether the resource expands to more than its master instance — an RRULE, an RDATE, or an override VEVENT. Overrides count on their own: a client that edits one instance and then drops the rule leaves a resource with a master and its exceptions, and each of those is still an occurrence.",
5002
+ "default": false,
5003
+ "readOnly": true
5004
+ },
5005
+ "expandedThrough": {
5006
+ "allOf": [
5007
+ {
5008
+ "$ref": "#/components/schemas/String64"
5009
+ }
5010
+ ],
5011
+ "description": "ISO 8601 UTC instant through which `CalendarEventIndex` rows exist for this resource, when the series runs past the expansion horizon and the index is therefore incomplete. `\"\"` when the index is exhaustive — a single event, or a series that ends inside the horizon. A later live-expansion pass reads this to find the resources whose instances it must compute rather than query.",
5012
+ "default": "",
5013
+ "readOnly": true
5014
+ },
5015
+ "createdAt": {
5016
+ "type": "integer",
5017
+ "format": "int64",
5018
+ "readOnly": true
5019
+ },
5020
+ "updatedAt": {
5021
+ "type": "integer",
5022
+ "format": "int64",
5023
+ "readOnly": true
5024
+ }
5025
+ },
5026
+ "description": "A stored calendar resource — the bytes behind one event, plus the columns projected from them. `icalData` is canonical; every other field is derived from it on write."
3688
5027
  },
3689
- "RemitImap.AddressRole": {
5028
+ "RemitImap.CalendarEventStatus": {
3690
5029
  "type": "string",
3691
5030
  "enum": [
3692
- "from",
3693
- "sender",
3694
- "reply_to",
3695
- "to",
3696
- "cc",
3697
- "bcc"
5031
+ "Confirmed",
5032
+ "Tentative",
5033
+ "Cancelled"
3698
5034
  ],
3699
- "description": "Address roles in message headers per RFC 2822 Sections 3.6.2 and 3.6.3"
5035
+ "description": "Projection of a VEVENT's STATUS property (RFC 5545 3.8.1.11). A VEVENT with no STATUS projects as `Confirmed`, which is the value RFC 5545 gives an event whose status is unstated."
3700
5036
  },
3701
- "RemitImap.ApplyUpdateInput": {
5037
+ "RemitImap.CalendarFreeBusyResponse": {
3702
5038
  "type": "object",
3703
5039
  "required": [
3704
- "targetVersion"
5040
+ "items"
3705
5041
  ],
3706
5042
  "properties": {
3707
- "targetVersion": {
3708
- "type": "string",
3709
- "description": "The release version to install, `vX.Y.Z`"
5043
+ "items": {
5044
+ "type": "array",
5045
+ "items": {
5046
+ "$ref": "#/components/schemas/RemitImap.CalendarFreeBusySpan"
5047
+ }
3710
5048
  }
3711
5049
  },
3712
- "description": "Request to install a specific release. `targetVersion` must equal the version the latest check reported; the backend passes exactly this one field across the control seam and resolves nothing else (RFC 037 R6)."
5050
+ "description": "Merged busy spans over the requested window, in start order. An event marked TRANSP:TRANSPARENT or STATUS:CANCELLED is not busy time and never appears here."
3713
5051
  },
3714
- "RemitImap.AppointFolderRoleInput": {
5052
+ "RemitImap.CalendarFreeBusySpan": {
3715
5053
  "type": "object",
3716
5054
  "required": [
3717
- "mailboxId"
5055
+ "start",
5056
+ "end"
3718
5057
  ],
3719
5058
  "properties": {
3720
- "mailboxId": {
3721
- "type": "string",
3722
- "nullable": true,
3723
- "description": "Mailbox to appoint to the role, or `null` to clear the appointment. Typed as an inline nullable string rather than `UUID | null`: a named scalar union renders as allOf + nullable with no sibling type, which openapi-backend's AJV rejects for a `null` value — the same gotcha documented on RenameMailboxInput.displayNameOverride. An inline string keeps `null` valid at the wire; the handler validates the value as a UUID."
5059
+ "start": {
5060
+ "allOf": [
5061
+ {
5062
+ "$ref": "#/components/schemas/String64"
5063
+ }
5064
+ ],
5065
+ "description": "When the busy stretch starts, as an ISO 8601 date-time with a `+00:00` offset. A busy span is an interval on the clock rather than a civil date, so it carries no calendar's local zone — the caller renders it in whichever zone it is drawing."
5066
+ },
5067
+ "end": {
5068
+ "allOf": [
5069
+ {
5070
+ "$ref": "#/components/schemas/String64"
5071
+ }
5072
+ ],
5073
+ "description": "When the busy stretch ends, as an ISO 8601 date-time with a `+00:00` offset"
3724
5074
  }
3725
5075
  },
3726
- "description": "Input for PUT /accounts/{accountId}/folder-roles/{role}. Appoints the mailbox to the role, replacing whichever mailbox previously held it (exclusive by construction the map has one slot per role). Pass `mailboxId: null` to clear the role back to unfilled."
5076
+ "description": "One stretch of time the caller is busy in. Merged across every calendar they\nhold, so two overlapping meetings in two calendars are one span and a caller\nasking \"is this slot free\" never has to do the merging itself."
3727
5077
  },
3728
- "RemitImap.AutoArchiveFlag": {
5078
+ "RemitImap.CalendarListResponse": {
3729
5079
  "type": "object",
3730
5080
  "required": [
3731
- "value"
5081
+ "items"
3732
5082
  ],
3733
5083
  "properties": {
3734
- "value": {
3735
- "type": "boolean"
5084
+ "items": {
5085
+ "type": "array",
5086
+ "items": {
5087
+ "$ref": "#/components/schemas/RemitImap.CalendarResponse"
5088
+ }
3736
5089
  }
3737
5090
  },
3738
- "allOf": [
3739
- {
3740
- "$ref": "#/components/schemas/RemitImap.AddressFlagBase"
3741
- }
3742
- ],
3743
- "description": "Auto-archive: skip inbox, file directly"
5091
+ "description": "Every calendar collection of the caller's account config, url segment first. Unpaginated: an account holds a handful of calendars, and a page boundary in a sidebar is a bug waiting to happen."
3744
5092
  },
3745
- "RemitImap.AutoArchiveFlagUpdate": {
5093
+ "RemitImap.CalendarResponse": {
3746
5094
  "type": "object",
3747
- "properties": {
3748
- "value": {
3749
- "type": "boolean"
3750
- }
3751
- },
3752
- "allOf": [
3753
- {
3754
- "$ref": "#/components/schemas/RemitImap.AddressFlagBaseUpdate"
3755
- }
5095
+ "required": [
5096
+ "calendarId",
5097
+ "accountConfigId",
5098
+ "urlSegment",
5099
+ "displayName",
5100
+ "color",
5101
+ "componentSet",
5102
+ "source",
5103
+ "timezone",
5104
+ "syncSequence",
5105
+ "createdAt",
5106
+ "updatedAt"
3756
5107
  ],
3757
- "description": "Auto-archive: skip inbox, file directly"
3758
- },
3759
- "RemitImap.AutoMovedInfo": {
3760
- "type": "object",
3761
5108
  "properties": {
3762
- "action": {
5109
+ "calendarId": {
3763
5110
  "allOf": [
3764
5111
  {
3765
- "$ref": "#/components/schemas/RemitImap.PlacementAction"
5112
+ "$ref": "#/components/schemas/UUID"
3766
5113
  }
3767
5114
  ],
3768
- "description": "Classifier move only: direction Remit moved the message — MoveToInbox | MoveToJunk. Absent for a standing-filter move, which names its destination via `destinationMailboxId` instead."
3769
- },
3770
- "fromPlacement": {
3771
- "type": "string",
3772
- "description": "Classifier move only: folder role the message was moved from — inbox | junk | other. Used to render \"moved from Junk\" and to resolve the undo target via MailboxRole. Absent for a standing-filter move, which names its source via `fromMailboxId`."
5115
+ "description": "Primary identifier, derived from accountConfigId + urlSegment",
5116
+ "readOnly": true
3773
5117
  },
3774
- "fromMailboxId": {
5118
+ "accountConfigId": {
3775
5119
  "allOf": [
3776
5120
  {
3777
5121
  "$ref": "#/components/schemas/UUID"
3778
5122
  }
3779
5123
  ],
3780
- "description": "Standing-filter move only: the mailbox the message was moved out of. The undo target — the client resolves its display name for the badge and moves the message back here."
5124
+ "description": "Owning account configuration",
5125
+ "readOnly": true
3781
5126
  },
3782
- "destinationMailboxId": {
5127
+ "urlSegment": {
3783
5128
  "allOf": [
3784
5129
  {
3785
- "$ref": "#/components/schemas/UUID"
5130
+ "$ref": "#/components/schemas/String64"
3786
5131
  }
3787
5132
  ],
3788
- "description": "Standing-filter move only: the mailbox the filter moved the message into. The move is still in effect while the message sits here."
5133
+ "description": "Path segment this collection is addressed by, unique within the account config. Stored case-folded, the same folding `calendarId` is derived from a DAV path is matched case-insensitively, and storing the writer's casing would let a lookup by segment and a lookup by derived id disagree about one path. The default collection provisioned on first use holds \"default\".",
5134
+ "readOnly": true
3789
5135
  },
3790
- "filterId": {
5136
+ "displayName": {
3791
5137
  "allOf": [
3792
5138
  {
3793
- "$ref": "#/components/schemas/UUID"
5139
+ "$ref": "#/components/schemas/String140"
3794
5140
  }
3795
5141
  ],
3796
- "description": "Standing-filter move only: the filter that moved the message, for the Settings › Filters link. Undo does not disable it."
3797
- }
3798
- },
3799
- "description": "Present only when Remit auto-moved this message on body-sync. Two shapes share this model: a classifier placement move carries `action` + `fromPlacement` (Inbox/Junk direction); a standing-filter move carries `fromMailboxId` + `destinationMailboxId` + `filterId` (an arbitrary destination folder). Absent means the message was not auto-moved by Remit — clients treat absence as no indicator."
3800
- },
3801
- "RemitImap.BlockedFlag": {
3802
- "type": "object",
3803
- "required": [
3804
- "value"
3805
- ],
3806
- "properties": {
3807
- "value": {
3808
- "type": "boolean"
3809
- }
3810
- },
3811
- "allOf": [
3812
- {
3813
- "$ref": "#/components/schemas/RemitImap.AddressFlagBase"
3814
- }
3815
- ],
3816
- "description": "Sender is blocked: mail is placed in Junk, and images never load, even on explicit click"
3817
- },
3818
- "RemitImap.BlockedFlagUpdate": {
3819
- "type": "object",
3820
- "properties": {
3821
- "value": {
3822
- "type": "boolean"
3823
- }
3824
- },
3825
- "allOf": [
3826
- {
3827
- "$ref": "#/components/schemas/RemitImap.AddressFlagBaseUpdate"
3828
- }
3829
- ],
3830
- "description": "Sender is blocked: mail is placed in Junk, and images never load, even on explicit click"
3831
- },
3832
- "RemitImap.BodyPartResponse": {
3833
- "type": "object",
3834
- "required": [
3835
- "bodyPartId",
3836
- "mediaType",
3837
- "mediaSubtype",
3838
- "sizeOctets",
3839
- "isMultipart",
3840
- "contentUrl"
3841
- ],
3842
- "properties": {
3843
- "bodyPartId": {
5142
+ "description": "Human-readable collection name (display)"
5143
+ },
5144
+ "color": {
3844
5145
  "allOf": [
3845
5146
  {
3846
- "$ref": "#/components/schemas/UUID"
5147
+ "$ref": "#/components/schemas/RemitImap.CalendarColor"
3847
5148
  }
3848
5149
  ],
3849
- "description": "Primary identifier",
3850
- "readOnly": true
5150
+ "description": "Display color slot for this collection",
5151
+ "default": "Cal1"
3851
5152
  },
3852
- "mediaType": {
5153
+ "componentSet": {
3853
5154
  "allOf": [
3854
5155
  {
3855
- "$ref": "#/components/schemas/RemitImap.MediaType"
5156
+ "$ref": "#/components/schemas/RemitImap.CalendarComponentSet"
3856
5157
  }
3857
5158
  ],
3858
- "description": "MIME type (TEXT, IMAGE, APPLICATION, etc.)",
3859
- "readOnly": true
3860
- },
3861
- "mediaSubtype": {
3862
- "type": "string",
3863
- "minLength": 1,
3864
- "description": "MIME subtype (PLAIN, HTML, PNG, PDF, etc.)",
3865
- "readOnly": true
3866
- },
3867
- "contentId": {
3868
- "type": "string",
3869
- "description": "Content-ID header value",
5159
+ "description": "Which iCalendar components this collection accepts",
5160
+ "default": "VeventOnly",
3870
5161
  "readOnly": true
3871
5162
  },
3872
- "sizeOctets": {
3873
- "type": "integer",
3874
- "format": "int64",
3875
- "minimum": 0,
3876
- "description": "Encoded size in octets",
5163
+ "source": {
5164
+ "allOf": [
5165
+ {
5166
+ "$ref": "#/components/schemas/RemitImap.CalendarSource"
5167
+ }
5168
+ ],
5169
+ "description": "Where this collection came from",
5170
+ "default": "UserCreated",
3877
5171
  "readOnly": true
3878
5172
  },
3879
- "disposition": {
5173
+ "timezone": {
3880
5174
  "allOf": [
3881
5175
  {
3882
- "$ref": "#/components/schemas/RemitImap.ContentDisposition"
5176
+ "$ref": "#/components/schemas/String64"
3883
5177
  }
3884
5178
  ],
3885
- "description": "Content disposition (inline or attachment)",
3886
- "readOnly": true
5179
+ "description": "IANA time zone this collection's floating times are read in, e.g. \"Europe/Amsterdam\". `\"\"` when none is set, which reads as UTC.",
5180
+ "default": ""
3887
5181
  },
3888
- "dispositionFilename": {
3889
- "type": "string",
3890
- "description": "Filename from Content-Disposition",
5182
+ "syncSequence": {
5183
+ "type": "integer",
5184
+ "format": "int64",
5185
+ "description": "Monotonic change counter for this collection. Bumped by every write to one of its objects; the value stamped on `CalendarObject.syncSequence` is the value this counter held after that write.",
5186
+ "default": 0,
3891
5187
  "readOnly": true
3892
5188
  },
3893
- "isMultipart": {
3894
- "type": "boolean",
3895
- "description": "True if this is a multipart container",
5189
+ "createdAt": {
5190
+ "type": "integer",
5191
+ "format": "int64",
3896
5192
  "readOnly": true
3897
5193
  },
3898
- "contentUrl": {
3899
- "type": "string",
3900
- "format": "uri",
3901
- "description": "CloudFront-fronted URL for fetching this body part. Cookie-authenticated via Cognito id_token. Path: /content/accounts/{accountConfigId}/{accountId}/messages/{messageId}/parts/{partPath}. Required: handlers must throw rather than emit a placeholder when CONTENT_DELIVERY_DOMAIN is unset."
5194
+ "updatedAt": {
5195
+ "type": "integer",
5196
+ "format": "int64",
5197
+ "readOnly": true
3902
5198
  }
3903
5199
  },
3904
- "description": "Body part information"
5200
+ "description": "A calendar collection as the API returns it."
3905
5201
  },
3906
- "RemitImap.BulkMessageInput": {
3907
- "type": "object",
3908
- "required": [
3909
- "messageIds"
5202
+ "RemitImap.CalendarSource": {
5203
+ "type": "string",
5204
+ "enum": [
5205
+ "Default",
5206
+ "UserCreated",
5207
+ "MailDerived"
3910
5208
  ],
3911
- "properties": {
3912
- "messageIds": {
3913
- "type": "array",
3914
- "items": {
3915
- "$ref": "#/components/schemas/UUID"
3916
- },
3917
- "minItems": 1,
3918
- "maxItems": 100,
3919
- "description": "List of message IDs to operate on"
3920
- }
3921
- },
3922
- "description": "Input for bulk message operations"
5209
+ "description": "Where a calendar collection came from. `Default` is the one provisioned per account config on first use and is never deleted; `UserCreated` is one a person made; `MailDerived` is one populated from mail rather than by a person."
3923
5210
  },
3924
- "RemitImap.BulkUpdateFlagsInput": {
3925
- "type": "object",
3926
- "required": [
3927
- "messageIds"
5211
+ "RemitImap.CalendarTransparency": {
5212
+ "type": "string",
5213
+ "enum": [
5214
+ "Opaque",
5215
+ "Transparent"
3928
5216
  ],
3929
- "properties": {
3930
- "messageIds": {
3931
- "type": "array",
3932
- "items": {
3933
- "$ref": "#/components/schemas/UUID"
3934
- },
3935
- "minItems": 1,
3936
- "maxItems": 100,
3937
- "description": "List of message IDs to update"
3938
- },
3939
- "isRead": {
3940
- "type": "boolean",
3941
- "description": "Set messages as read (maps to \\Seen flag)"
3942
- },
3943
- "isStarred": {
3944
- "type": "boolean",
3945
- "description": "Set messages as starred (maps to \\Flagged flag)"
3946
- },
3947
- "starColor": {
3948
- "allOf": [
3949
- {
3950
- "$ref": "#/components/schemas/RemitImap.StarColor"
3951
- }
3952
- ],
3953
- "description": "Star color for visual categorization"
3954
- }
3955
- },
3956
- "description": "Input for bulk flag update operations"
5217
+ "description": "Projection of a VEVENT's TRANSP property (RFC 5545 3.8.2.7) — whether the event consumes free/busy time. A VEVENT with no TRANSP projects as `Opaque`, the RFC default."
3957
5218
  },
3958
5219
  "RemitImap.CanonicalMailboxRole": {
3959
5220
  "type": "string",
@@ -4021,6 +5282,14 @@
4021
5282
  "$ref": "#/components/schemas/RemitImap.AccountResponse"
4022
5283
  },
4023
5284
  "description": "Associated email accounts"
5285
+ },
5286
+ "pendingImport": {
5287
+ "allOf": [
5288
+ {
5289
+ "$ref": "#/components/schemas/RemitImap.PendingConfigImport"
5290
+ }
5291
+ ],
5292
+ "description": "Absent unless a configuration import is still waiting on folders IMAP has not produced yet (#1021). Nothing polls a separate route for it."
4024
5293
  }
4025
5294
  },
4026
5295
  "description": "Full configuration description including accounts"
@@ -4045,6 +5314,113 @@
4045
5314
  },
4046
5315
  "description": "A configuration export: one versioned document carrying every user decision this instance holds, and no credential."
4047
5316
  },
5317
+ "RemitImap.ConfigImportItemReport": {
5318
+ "type": "object",
5319
+ "required": [
5320
+ "section",
5321
+ "key",
5322
+ "verdict"
5323
+ ],
5324
+ "properties": {
5325
+ "section": {
5326
+ "type": "string",
5327
+ "description": "Which section: accounts | labels | filters | addressFlags | settings"
5328
+ },
5329
+ "key": {
5330
+ "type": "string",
5331
+ "description": "Human key of the item - an email, a label name, a filter name"
5332
+ },
5333
+ "verdict": {
5334
+ "type": "string",
5335
+ "description": "created | updated | unchanged | skipped | rejected"
5336
+ },
5337
+ "reason": {
5338
+ "type": "string",
5339
+ "description": "Why, when the verdict is skipped or rejected"
5340
+ }
5341
+ },
5342
+ "description": "What became of one item in the submitted document"
5343
+ },
5344
+ "RemitImap.ConfigImportMode": {
5345
+ "type": "string",
5346
+ "enum": [
5347
+ "validate",
5348
+ "apply"
5349
+ ],
5350
+ "description": "Whether an import writes. The default is the dry run, so the wizard can show what a file would do before anything lands."
5351
+ },
5352
+ "RemitImap.ConfigImportOnExisting": {
5353
+ "type": "string",
5354
+ "enum": [
5355
+ "abort",
5356
+ "merge"
5357
+ ],
5358
+ "description": "What an import does when the configuration it is importing into is not empty."
5359
+ },
5360
+ "RemitImap.ConfigImportReport": {
5361
+ "type": "object",
5362
+ "required": [
5363
+ "valid",
5364
+ "schemaVersion",
5365
+ "applied",
5366
+ "items",
5367
+ "errors",
5368
+ "warnings",
5369
+ "accountsNeedingCredentials"
5370
+ ],
5371
+ "properties": {
5372
+ "importId": {
5373
+ "allOf": [
5374
+ {
5375
+ "$ref": "#/components/schemas/UUID"
5376
+ }
5377
+ ],
5378
+ "description": "Import identity; present on apply, so the folder bindings this import is still waiting on can be named later"
5379
+ },
5380
+ "valid": {
5381
+ "type": "boolean",
5382
+ "description": "Whether the document is importable at all"
5383
+ },
5384
+ "schemaVersion": {
5385
+ "type": "integer",
5386
+ "format": "int32",
5387
+ "description": "Schema version of the submitted document"
5388
+ },
5389
+ "applied": {
5390
+ "type": "boolean",
5391
+ "description": "Whether anything was written"
5392
+ },
5393
+ "items": {
5394
+ "type": "array",
5395
+ "items": {
5396
+ "$ref": "#/components/schemas/RemitImap.ConfigImportItemReport"
5397
+ },
5398
+ "description": "Per-item verdicts"
5399
+ },
5400
+ "errors": {
5401
+ "type": "array",
5402
+ "items": {
5403
+ "$ref": "#/components/schemas/ApiError"
5404
+ },
5405
+ "description": "Blocking problems; non-empty means nothing was written"
5406
+ },
5407
+ "warnings": {
5408
+ "type": "array",
5409
+ "items": {
5410
+ "$ref": "#/components/schemas/ApiError"
5411
+ },
5412
+ "description": "Non-blocking notes - e.g. a folder path this account does not hold yet"
5413
+ },
5414
+ "accountsNeedingCredentials": {
5415
+ "type": "array",
5416
+ "items": {
5417
+ "$ref": "#/components/schemas/UUID"
5418
+ },
5419
+ "description": "Accounts that landed needing a credential before they can sync"
5420
+ }
5421
+ },
5422
+ "description": "The outcome of a validate or an apply. A rejected document is an answer, not a failure: `valid` is false and `errors` says why."
5423
+ },
4048
5424
  "RemitImap.ConnectionState": {
4049
5425
  "type": "string",
4050
5426
  "enum": [
@@ -4052,7 +5428,8 @@
4052
5428
  "authenticated",
4053
5429
  "selected",
4054
5430
  "logout",
4055
- "reauth_required"
5431
+ "reauth_required",
5432
+ "credentials_missing"
4056
5433
  ],
4057
5434
  "description": "IMAP connection states per RFC 9051 Section 3"
4058
5435
  },
@@ -4182,6 +5559,144 @@
4182
5559
  },
4183
5560
  "description": "Input for creating a new email account"
4184
5561
  },
5562
+ "RemitImap.CreateCalendarEventInput": {
5563
+ "type": "object",
5564
+ "required": [
5565
+ "calendarId",
5566
+ "summary",
5567
+ "start",
5568
+ "end"
5569
+ ],
5570
+ "properties": {
5571
+ "calendarId": {
5572
+ "allOf": [
5573
+ {
5574
+ "$ref": "#/components/schemas/UUID"
5575
+ }
5576
+ ],
5577
+ "description": "Collection to write the event into"
5578
+ },
5579
+ "summary": {
5580
+ "allOf": [
5581
+ {
5582
+ "$ref": "#/components/schemas/String512"
5583
+ }
5584
+ ],
5585
+ "description": "SUMMARY of the event"
5586
+ },
5587
+ "description": {
5588
+ "allOf": [
5589
+ {
5590
+ "$ref": "#/components/schemas/String2048"
5591
+ }
5592
+ ],
5593
+ "description": "DESCRIPTION of the event. Absent writes none."
5594
+ },
5595
+ "location": {
5596
+ "allOf": [
5597
+ {
5598
+ "$ref": "#/components/schemas/String512"
5599
+ }
5600
+ ],
5601
+ "description": "LOCATION of the event. Absent writes none."
5602
+ },
5603
+ "start": {
5604
+ "allOf": [
5605
+ {
5606
+ "$ref": "#/components/schemas/String64"
5607
+ }
5608
+ ],
5609
+ "description": "When the event starts: an ISO 8601 date-time with zone offset, or a `YYYY-MM-DD` date when `allDay` is set."
5610
+ },
5611
+ "end": {
5612
+ "allOf": [
5613
+ {
5614
+ "$ref": "#/components/schemas/String64"
5615
+ }
5616
+ ],
5617
+ "description": "When the event ends, in the same form as `start`. Exclusive for an all-day event, per RFC 5545 3.6.1."
5618
+ },
5619
+ "allDay": {
5620
+ "type": "boolean",
5621
+ "description": "Whether this is an all-day event, whose times are civil dates rather than instants"
5622
+ },
5623
+ "timeZone": {
5624
+ "allOf": [
5625
+ {
5626
+ "$ref": "#/components/schemas/String64"
5627
+ }
5628
+ ],
5629
+ "description": "IANA time zone the event is anchored in, e.g. \"Europe/Amsterdam\", written as the DTSTART TZID. Absent anchors the event in UTC. Refused with 400 when the zone is one this server cannot resolve — a calendar that accepted an unknown zone would draw the event hours away from where it belongs."
5630
+ },
5631
+ "status": {
5632
+ "allOf": [
5633
+ {
5634
+ "$ref": "#/components/schemas/RemitImap.CalendarEventStatus"
5635
+ }
5636
+ ],
5637
+ "description": "STATUS of the event"
5638
+ },
5639
+ "transparency": {
5640
+ "allOf": [
5641
+ {
5642
+ "$ref": "#/components/schemas/RemitImap.CalendarTransparency"
5643
+ }
5644
+ ],
5645
+ "description": "TRANSP of the event — whether it consumes free/busy time"
5646
+ },
5647
+ "recurrenceRule": {
5648
+ "allOf": [
5649
+ {
5650
+ "$ref": "#/components/schemas/String512"
5651
+ }
5652
+ ],
5653
+ "description": "RRULE value for a recurring event, e.g. \"FREQ=WEEKLY;BYDAY=MO\" (RFC 5545 3.8.5.3), without the property name. `\"\"` creates a single event. Refused with 400 when it is not a rule this server can read."
5654
+ }
5655
+ },
5656
+ "description": "Input for creating an event (issue #15). The server builds the VEVENT and\nwrites it through the single calendar write path, so an event created here,\none accepted from an invitation, and one PUT over CalDAV are the same bytes\nwritten the same way.\n\nTimes are ISO 8601 with zone offset — or a bare `YYYY-MM-DD` date when\n`allDay` is set. The offset pins the instant; `timeZone` names the zone the\nevent is anchored in, which is what a recurring event needs to survive a DST\ntransition. An offset alone cannot: a weekly 09:00 meeting written as\n`+02:00` becomes an 08:00 meeting in November."
5657
+ },
5658
+ "RemitImap.CreateCalendarInput": {
5659
+ "type": "object",
5660
+ "required": [
5661
+ "displayName",
5662
+ "urlSegment"
5663
+ ],
5664
+ "properties": {
5665
+ "displayName": {
5666
+ "allOf": [
5667
+ {
5668
+ "$ref": "#/components/schemas/String140"
5669
+ }
5670
+ ],
5671
+ "description": "Human-readable collection name (display)"
5672
+ },
5673
+ "urlSegment": {
5674
+ "allOf": [
5675
+ {
5676
+ "$ref": "#/components/schemas/String64"
5677
+ }
5678
+ ],
5679
+ "description": "Path segment addressing this collection, unique within the account config. Case-folded on write, so \"Work\" and \"work\" name the same collection and the second create is refused rather than silently merged into the first."
5680
+ },
5681
+ "color": {
5682
+ "allOf": [
5683
+ {
5684
+ "$ref": "#/components/schemas/RemitImap.CalendarColor"
5685
+ }
5686
+ ],
5687
+ "description": "Display colour slot. Absent takes the collection's own default."
5688
+ },
5689
+ "timezone": {
5690
+ "allOf": [
5691
+ {
5692
+ "$ref": "#/components/schemas/String64"
5693
+ }
5694
+ ],
5695
+ "description": "IANA time zone this collection's floating times are read in. Absent reads them as UTC."
5696
+ }
5697
+ },
5698
+ "description": "Input for creating a calendar collection (issue #15).\n\n`urlSegment` is supplied rather than derived from the display name: it is the\ncollection's identity — `calendarId` is a hash of it — and the path a CalDAV\nclient will later bookmark. Slugging a name would make two calendars called\n\"Work\" one row, and a rename would either move a bookmarked path or leave a\nsegment that no longer matches anything a person recognises. The name is the\nmutable overlay; this is the address."
5699
+ },
4185
5700
  "RemitImap.CreateExportResponse": {
4186
5701
  "type": "object",
4187
5702
  "required": [
@@ -4945,6 +6460,38 @@
4945
6460
  ],
4946
6461
  "description": "Where a resolved FolderAppointment came from. Total and required: every role carries one, so a surface can tell a folder somebody chose from a folder reader guessed without inferring it from which fields happen to be present."
4947
6462
  },
6463
+ "RemitImap.ImportConfigInput": {
6464
+ "type": "object",
6465
+ "required": [
6466
+ "document"
6467
+ ],
6468
+ "properties": {
6469
+ "mode": {
6470
+ "allOf": [
6471
+ {
6472
+ "$ref": "#/components/schemas/RemitImap.ConfigImportMode"
6473
+ }
6474
+ ],
6475
+ "description": "`validate` writes nothing; `apply` writes.",
6476
+ "default": "validate"
6477
+ },
6478
+ "onExisting": {
6479
+ "allOf": [
6480
+ {
6481
+ "$ref": "#/components/schemas/RemitImap.ConfigImportOnExisting"
6482
+ }
6483
+ ],
6484
+ "description": "What to do when this configuration is not empty.",
6485
+ "default": "abort"
6486
+ },
6487
+ "document": {
6488
+ "type": "object",
6489
+ "additionalProperties": {},
6490
+ "description": "The config document, as exported."
6491
+ }
6492
+ },
6493
+ "description": "Request body for POST /config/import"
6494
+ },
4948
6495
  "RemitImap.JunkOnlyFlag": {
4949
6496
  "type": "object",
4950
6497
  "required": [
@@ -6213,6 +7760,31 @@
6213
7760
  ],
6214
7761
  "description": "Status of an outbox message in the send queue"
6215
7762
  },
7763
+ "RemitImap.PendingConfigImport": {
7764
+ "type": "object",
7765
+ "required": [
7766
+ "importId",
7767
+ "folderPaths"
7768
+ ],
7769
+ "properties": {
7770
+ "importId": {
7771
+ "allOf": [
7772
+ {
7773
+ "$ref": "#/components/schemas/UUID"
7774
+ }
7775
+ ],
7776
+ "description": "The import still waiting"
7777
+ },
7778
+ "folderPaths": {
7779
+ "type": "array",
7780
+ "items": {
7781
+ "type": "string"
7782
+ },
7783
+ "description": "The IMAP paths no mailbox answers to yet, deduplicated"
7784
+ }
7785
+ },
7786
+ "description": "An import that is still waiting for IMAP discovery to produce the folders its\nfile named. The folder appointed to a role, a folder's own display name or\nmute, and a filter's move destination are all bound by path; a path this\naccount does not hold yet is kept, not guessed, and bound the moment discovery\nfinds it."
7787
+ },
6216
7788
  "RemitImap.PlacementAction": {
6217
7789
  "type": "string",
6218
7790
  "enum": [
@@ -6496,6 +8068,15 @@
6496
8068
  },
6497
8069
  "description": "Raw RFC822/MIME source of a message (headers and body), decoded as a string for inspection."
6498
8070
  },
8071
+ "RemitImap.RecurrenceScope": {
8072
+ "type": "string",
8073
+ "enum": [
8074
+ "This",
8075
+ "Following",
8076
+ "All"
8077
+ ],
8078
+ "description": "Which occurrences of a recurring event a write applies to (issue #15). A request parameter, never a stored column: the resource records what the series is, and this records what the caller meant by editing one drawing of it."
8079
+ },
6499
8080
  "RemitImap.ReferenceType": {
6500
8081
  "type": "string",
6501
8082
  "enum": [
@@ -7505,6 +9086,79 @@
7505
9086
  },
7506
9087
  "description": "Input for updating an Address (currently flag updates only)"
7507
9088
  },
9089
+ "RemitImap.UpdateCalendarEventInput": {
9090
+ "type": "object",
9091
+ "properties": {
9092
+ "summary": {
9093
+ "$ref": "#/components/schemas/String512"
9094
+ },
9095
+ "description": {
9096
+ "$ref": "#/components/schemas/String2048"
9097
+ },
9098
+ "location": {
9099
+ "$ref": "#/components/schemas/String512"
9100
+ },
9101
+ "start": {
9102
+ "$ref": "#/components/schemas/String64"
9103
+ },
9104
+ "end": {
9105
+ "$ref": "#/components/schemas/String64"
9106
+ },
9107
+ "allDay": {
9108
+ "type": "boolean"
9109
+ },
9110
+ "timeZone": {
9111
+ "$ref": "#/components/schemas/String64"
9112
+ },
9113
+ "status": {
9114
+ "$ref": "#/components/schemas/RemitImap.CalendarEventStatus"
9115
+ },
9116
+ "transparency": {
9117
+ "$ref": "#/components/schemas/RemitImap.CalendarTransparency"
9118
+ },
9119
+ "recurrenceRule": {
9120
+ "allOf": [
9121
+ {
9122
+ "$ref": "#/components/schemas/String512"
9123
+ }
9124
+ ],
9125
+ "description": "RRULE value, or `\"\"` to drop the rule. Only meaningful with `scope=All` or `scope=Following`: an override is one occurrence and carries no rule of its own."
9126
+ }
9127
+ },
9128
+ "description": "Input for updating an event. Every field is optional and absence means\nuntouched — a patch that carries only `summary` renames the event and moves\nnothing.\n\nWhat the patch applies to is the `scope` parameter's business: `All` rewrites\nthe master, `This` writes a RECURRENCE-ID override for one occurrence, and\n`Following` splits the series at one occurrence and applies the patch to the\nremainder."
9129
+ },
9130
+ "RemitImap.UpdateCalendarInput": {
9131
+ "type": "object",
9132
+ "properties": {
9133
+ "displayName": {
9134
+ "allOf": [
9135
+ {
9136
+ "$ref": "#/components/schemas/String140"
9137
+ }
9138
+ ],
9139
+ "description": "Human-readable collection name (display)"
9140
+ },
9141
+ "color": {
9142
+ "allOf": [
9143
+ {
9144
+ "$ref": "#/components/schemas/RemitImap.CalendarColor"
9145
+ }
9146
+ ],
9147
+ "description": "Display color slot for this collection",
9148
+ "default": "Cal1"
9149
+ },
9150
+ "timezone": {
9151
+ "allOf": [
9152
+ {
9153
+ "$ref": "#/components/schemas/String64"
9154
+ }
9155
+ ],
9156
+ "description": "IANA time zone this collection's floating times are read in, e.g. \"Europe/Amsterdam\". `\"\"` when none is set, which reads as UTC.",
9157
+ "default": ""
9158
+ }
9159
+ },
9160
+ "description": "Input for updating a calendar collection. The name, colour and timezone are the mutable overlay; `urlSegment` is not among them, because it is the collection's identity and the path a client has bookmarked."
9161
+ },
7508
9162
  "RemitImap.UpdateFilterInput": {
7509
9163
  "type": "object",
7510
9164
  "properties": {
@@ -7828,6 +9482,15 @@
7828
9482
  ],
7829
9483
  "description": "Sender is wellknown: engagement counters crossed the promotion threshold"
7830
9484
  },
9485
+ "RemitImap.ZoneCertainty": {
9486
+ "type": "string",
9487
+ "enum": [
9488
+ "Local",
9489
+ "Explicit",
9490
+ "Ambiguous"
9491
+ ],
9492
+ "description": "How confidently a stored time was placed on the clock. A calendar that cannot say this hides its worst failure: an event read in the wrong zone is off by hours and looks exactly like an event read correctly."
9493
+ },
7831
9494
  "String140": {
7832
9495
  "type": "string",
7833
9496
  "maxLength": 140