@wspc/cli 0.0.19 → 0.0.21

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.
package/spec/openapi.json CHANGED
@@ -46,20 +46,6 @@
46
46
  "updated_at"
47
47
  ]
48
48
  },
49
- "CreateApiKeyBody": {
50
- "type": "object",
51
- "properties": {
52
- "label": {
53
- "type": "string",
54
- "minLength": 1,
55
- "maxLength": 60,
56
- "description": "Human-readable label for the new key (1–60 chars after trimming). Pick something that identifies where the key will live — agent name, machine, or environment — so you can recognise it later in `wspc keys list`."
57
- }
58
- },
59
- "required": [
60
- "label"
61
- ]
62
- },
63
49
  "CreateApiKeyResponse": {
64
50
  "type": "object",
65
51
  "properties": {
@@ -92,17 +78,18 @@
92
78
  "created_at"
93
79
  ]
94
80
  },
95
- "CreateOrgInviteInput": {
81
+ "CreateApiKeyBody": {
96
82
  "type": "object",
97
83
  "properties": {
98
- "email": {
84
+ "label": {
99
85
  "type": "string",
100
- "format": "email",
101
- "description": "Email address to invite into the caller's organization."
86
+ "minLength": 1,
87
+ "maxLength": 60,
88
+ "description": "Human-readable label for the new key (1–60 chars after trimming). Pick something that identifies where the key will live — agent name, machine, or environment — so you can recognise it later in `wspc keys list`."
102
89
  }
103
90
  },
104
91
  "required": [
105
- "email"
92
+ "label"
106
93
  ]
107
94
  },
108
95
  "OrgInviteSummary": {
@@ -148,24 +135,17 @@
148
135
  "invite_url"
149
136
  ]
150
137
  },
151
- "OAuthDeviceBody": {
138
+ "CreateOrgInviteInput": {
152
139
  "type": "object",
153
140
  "properties": {
154
- "client_id": {
155
- "type": "string",
156
- "description": "Identifier of the OAuth client initiating the device flow. Must already be registered via `POST /auth/oauth/register`."
157
- },
158
- "scope": {
159
- "type": "string",
160
- "description": "Optional space-separated scope list. Defaults to `wspc:full` when omitted; WSPC issues only this scope today."
161
- },
162
- "resource": {
141
+ "email": {
163
142
  "type": "string",
164
- "description": "Optional RFC 8707 resource indicator the resulting access token will be bound to. When omitted, the server uses the default WSPC API audience."
143
+ "format": "email",
144
+ "description": "Email address to invite into the caller's organization."
165
145
  }
166
146
  },
167
147
  "required": [
168
- "client_id"
148
+ "email"
169
149
  ]
170
150
  },
171
151
  "OAuthDeviceResponse": {
@@ -207,6 +187,26 @@
207
187
  "interval"
208
188
  ]
209
189
  },
190
+ "OAuthDeviceBody": {
191
+ "type": "object",
192
+ "properties": {
193
+ "client_id": {
194
+ "type": "string",
195
+ "description": "Identifier of the OAuth client initiating the device flow. Must already be registered via `POST /auth/oauth/register`."
196
+ },
197
+ "scope": {
198
+ "type": "string",
199
+ "description": "Optional space-separated scope list. Defaults to `wspc:full` when omitted; WSPC issues only this scope today."
200
+ },
201
+ "resource": {
202
+ "type": "string",
203
+ "description": "Optional RFC 8707 resource indicator the resulting access token will be bound to. When omitted, the server uses the default WSPC API audience."
204
+ }
205
+ },
206
+ "required": [
207
+ "client_id"
208
+ ]
209
+ },
210
210
  "OrgInviteView": {
211
211
  "type": "object",
212
212
  "properties": {
@@ -315,19 +315,6 @@
315
315
  "invites"
316
316
  ]
317
317
  },
318
- "ListOrgMembersQuery": {
319
- "type": "object",
320
- "properties": {
321
- "cursor": {
322
- "type": "string",
323
- "description": "Opaque pagination cursor. Pass the `next_cursor` returned by the previous page to fetch the next slice. Omit on the first call."
324
- },
325
- "limit": {
326
- "type": "string",
327
- "description": "Maximum members to return. Clamped to [1, 100]. Defaults to 50."
328
- }
329
- }
330
- },
331
318
  "ListOrgMembersResponse": {
332
319
  "type": "object",
333
320
  "properties": {
@@ -500,48 +487,6 @@
500
487
  "token_endpoint_auth_methods_supported"
501
488
  ]
502
489
  },
503
- "OAuthRegisterBody": {
504
- "type": "object",
505
- "properties": {
506
- "client_name": {
507
- "type": "string",
508
- "description": "Human-readable name for the client application. Shown to users on the consent screen so they know which agent / integration is requesting access."
509
- },
510
- "redirect_uris": {
511
- "type": "array",
512
- "items": {
513
- "type": "string",
514
- "format": "uri"
515
- },
516
- "minItems": 1,
517
- "description": "Non-empty list of redirect URIs the client may use. Must exactly match the `redirect_uri` sent in subsequent authorization requests. Use `http://localhost:<port>/callback` for native / local development clients."
518
- },
519
- "token_endpoint_auth_method": {
520
- "type": "string",
521
- "enum": [
522
- "none",
523
- "client_secret_basic"
524
- ],
525
- "default": "none",
526
- "description": "Client authentication method to use at the token endpoint. `none` (the default and the only fully supported value today) is for public PKCE clients; `client_secret_basic` is reserved for future confidential clients."
527
- },
528
- "grant_types": {
529
- "type": "array",
530
- "items": {
531
- "type": "string"
532
- },
533
- "default": [
534
- "authorization_code",
535
- "refresh_token"
536
- ],
537
- "description": "List of grant types the client intends to use. Defaults to `[\"authorization_code\", \"refresh_token\"]`; add `urn:ietf:params:oauth:grant-type:device_code` for device flow."
538
- }
539
- },
540
- "required": [
541
- "client_name",
542
- "redirect_uris"
543
- ]
544
- },
545
490
  "OAuthRegisterResponse": {
546
491
  "type": "object",
547
492
  "properties": {
@@ -585,16 +530,46 @@
585
530
  "client_id_issued_at"
586
531
  ]
587
532
  },
588
- "RequestCodeBody": {
533
+ "OAuthRegisterBody": {
589
534
  "type": "object",
590
535
  "properties": {
591
- "email": {
536
+ "client_name": {
592
537
  "type": "string",
593
- "description": "Email address to send the magic code to. Lowercased and trimmed server-side. The same response is returned whether or not the address is registered, to avoid disclosing account existence."
538
+ "description": "Human-readable name for the client application. Shown to users on the consent screen so they know which agent / integration is requesting access."
539
+ },
540
+ "redirect_uris": {
541
+ "type": "array",
542
+ "items": {
543
+ "type": "string",
544
+ "format": "uri"
545
+ },
546
+ "minItems": 1,
547
+ "description": "Non-empty list of redirect URIs the client may use. Must exactly match the `redirect_uri` sent in subsequent authorization requests. Use `http://localhost:<port>/callback` for native / local development clients."
548
+ },
549
+ "token_endpoint_auth_method": {
550
+ "type": "string",
551
+ "enum": [
552
+ "none",
553
+ "client_secret_basic"
554
+ ],
555
+ "default": "none",
556
+ "description": "Client authentication method to use at the token endpoint. `none` (the default and the only fully supported value today) is for public PKCE clients; `client_secret_basic` is reserved for future confidential clients."
557
+ },
558
+ "grant_types": {
559
+ "type": "array",
560
+ "items": {
561
+ "type": "string"
562
+ },
563
+ "default": [
564
+ "authorization_code",
565
+ "refresh_token"
566
+ ],
567
+ "description": "List of grant types the client intends to use. Defaults to `[\"authorization_code\", \"refresh_token\"]`; add `urn:ietf:params:oauth:grant-type:device_code` for device flow."
594
568
  }
595
569
  },
596
570
  "required": [
597
- "email"
571
+ "client_name",
572
+ "redirect_uris"
598
573
  ]
599
574
  },
600
575
  "RequestCodeResponse": {
@@ -617,6 +592,18 @@
617
592
  "message"
618
593
  ]
619
594
  },
595
+ "RequestCodeBody": {
596
+ "type": "object",
597
+ "properties": {
598
+ "email": {
599
+ "type": "string",
600
+ "description": "Email address to send the magic code to. Lowercased and trimmed server-side. The same response is returned whether or not the address is registered, to avoid disclosing account existence."
601
+ }
602
+ },
603
+ "required": [
604
+ "email"
605
+ ]
606
+ },
620
607
  "RevokeApiKeyResponse": {
621
608
  "type": "object",
622
609
  "properties": {
@@ -652,6 +639,41 @@
652
639
  "token"
653
640
  ]
654
641
  },
642
+ "OAuthTokenResponse": {
643
+ "type": "object",
644
+ "properties": {
645
+ "access_token": {
646
+ "type": "string",
647
+ "description": "Opaque bearer access token. Send as `Authorization: Bearer <access_token>` to WSPC services. Treat as a short-lived secret."
648
+ },
649
+ "token_type": {
650
+ "type": "string",
651
+ "enum": [
652
+ "Bearer"
653
+ ],
654
+ "description": "Always the literal `Bearer`. WSPC issues OAuth bearer tokens only."
655
+ },
656
+ "expires_in": {
657
+ "type": "integer",
658
+ "description": "Lifetime of the access token in seconds. Clients should refresh before this deadline to avoid 401 responses."
659
+ },
660
+ "refresh_token": {
661
+ "type": "string",
662
+ "description": "Refresh token used to obtain a new access token without re-prompting the user. Rotated on every use; store securely."
663
+ },
664
+ "scope": {
665
+ "type": "string",
666
+ "description": "Space-separated list of OAuth scopes granted on this token. WSPC issues `wspc:full` today."
667
+ }
668
+ },
669
+ "required": [
670
+ "access_token",
671
+ "token_type",
672
+ "expires_in",
673
+ "refresh_token",
674
+ "scope"
675
+ ]
676
+ },
655
677
  "OAuthTokenBody": {
656
678
  "oneOf": [
657
679
  {
@@ -753,55 +775,6 @@
753
775
  }
754
776
  ]
755
777
  },
756
- "OAuthTokenResponse": {
757
- "type": "object",
758
- "properties": {
759
- "access_token": {
760
- "type": "string",
761
- "description": "Opaque bearer access token. Send as `Authorization: Bearer <access_token>` to WSPC services. Treat as a short-lived secret."
762
- },
763
- "token_type": {
764
- "type": "string",
765
- "enum": [
766
- "Bearer"
767
- ],
768
- "description": "Always the literal `Bearer`. WSPC issues OAuth bearer tokens only."
769
- },
770
- "expires_in": {
771
- "type": "integer",
772
- "description": "Lifetime of the access token in seconds. Clients should refresh before this deadline to avoid 401 responses."
773
- },
774
- "refresh_token": {
775
- "type": "string",
776
- "description": "Refresh token used to obtain a new access token without re-prompting the user. Rotated on every use; store securely."
777
- },
778
- "scope": {
779
- "type": "string",
780
- "description": "Space-separated list of OAuth scopes granted on this token. WSPC issues `wspc:full` today."
781
- }
782
- },
783
- "required": [
784
- "access_token",
785
- "token_type",
786
- "expires_in",
787
- "refresh_token",
788
- "scope"
789
- ]
790
- },
791
- "UpdateApiKeyBody": {
792
- "type": "object",
793
- "properties": {
794
- "label": {
795
- "type": "string",
796
- "minLength": 1,
797
- "maxLength": 60,
798
- "description": "Human-readable label for the key (1–60 chars after trimming)."
799
- }
800
- },
801
- "required": [
802
- "label"
803
- ]
804
- },
805
778
  "UpdateApiKeyResponse": {
806
779
  "type": "object",
807
780
  "properties": {
@@ -832,35 +805,32 @@
832
805
  "created_at"
833
806
  ]
834
807
  },
835
- "UpdateOrgInput": {
808
+ "UpdateApiKeyBody": {
836
809
  "type": "object",
837
810
  "properties": {
838
- "name": {
811
+ "label": {
839
812
  "type": "string",
840
813
  "minLength": 1,
841
- "maxLength": 100,
842
- "description": "The new name for the organization. Cannot be empty or purely whitespace."
814
+ "maxLength": 60,
815
+ "description": "Human-readable label for the key (1–60 chars after trimming)."
843
816
  }
844
817
  },
845
818
  "required": [
846
- "name"
819
+ "label"
847
820
  ]
848
821
  },
849
- "VerifyCodeBody": {
822
+ "UpdateOrgInput": {
850
823
  "type": "object",
851
824
  "properties": {
852
- "email": {
853
- "type": "string",
854
- "description": "Email address that received the magic code. Must exactly match (after lowercasing) the address used in the prior `POST /auth/request-code` call."
855
- },
856
- "code": {
825
+ "name": {
857
826
  "type": "string",
858
- "description": "Numeric magic code delivered by email. Single-use; expires a few minutes after issuance. Treat as a short-lived secret."
827
+ "minLength": 1,
828
+ "maxLength": 100,
829
+ "description": "The new name for the organization. Cannot be empty or purely whitespace."
859
830
  }
860
831
  },
861
832
  "required": [
862
- "email",
863
- "code"
833
+ "name"
864
834
  ]
865
835
  },
866
836
  "VerifyCodeResponse": {
@@ -885,75 +855,22 @@
885
855
  "created"
886
856
  ]
887
857
  },
888
- "CreateEventBody": {
858
+ "VerifyCodeBody": {
889
859
  "type": "object",
890
860
  "properties": {
891
- "title": {
892
- "type": "string",
893
- "minLength": 1,
894
- "maxLength": 500,
895
- "description": "Short human-readable summary of the event. Shown in list views and `.ics` SUMMARY."
896
- },
897
- "description": {
898
- "type": "string",
899
- "maxLength": 10000,
900
- "description": "Free-form notes about the event (agenda, dial-in instructions, etc.). Markdown formatted (CommonMark + GFM tables, strikethrough, task lists); stored verbatim. Invitation emails include the raw source — most email clients display it as plain text."
901
- },
902
- "start": {
903
- "type": "string",
904
- "description": "Accepts ISO 8601 datetime with offset (e.g. `2026-06-01T12:30:00+08:00`) for timed events, or ISO date-only (e.g. `2026-06-01`) for all-day. The `wspc` CLI additionally accepts natural-language phrases (`tomorrow 12:30pm`, `next Monday 9am`) and resolves them to ISO before sending; the server itself only accepts ISO. All-day uses RFC 5545 exclusive end: a one-day event on 6/1 is `start=2026-06-01, end=2026-06-02`; both endpoints must be the same type."
905
- },
906
- "end": {
907
- "type": "string",
908
- "description": "Accepts ISO 8601 datetime with offset (e.g. `2026-06-01T12:30:00+08:00`) for timed events, or ISO date-only (e.g. `2026-06-01`) for all-day. The `wspc` CLI additionally accepts natural-language phrases (`tomorrow 12:30pm`, `next Monday 9am`) and resolves them to ISO before sending; the server itself only accepts ISO. All-day uses RFC 5545 exclusive end: a one-day event on 6/1 is `start=2026-06-01, end=2026-06-02`; both endpoints must be the same type."
909
- },
910
- "location": {
911
- "type": "string",
912
- "maxLength": 10000,
913
- "description": "Free-text location — physical address, room, or short note. Separate from `url` (meeting link)."
914
- },
915
- "url": {
861
+ "email": {
916
862
  "type": "string",
917
- "format": "uri",
918
- "description": "Optional meeting link (Zoom / Meet / etc.). Kept separate from `location` so calendar clients can render it as a join action."
863
+ "description": "Email address that received the magic code. Must exactly match (after lowercasing) the address used in the prior `POST /auth/request-code` call."
919
864
  },
920
- "status": {
865
+ "code": {
921
866
  "type": "string",
922
- "enum": [
923
- "confirmed",
924
- "tentative",
925
- "cancelled"
926
- ],
927
- "description": "Lifecycle status. `confirmed`: the event will happen (default). `tentative`: organizer has not finalized; still visible in lists. `cancelled`: the event was called off but the record is kept so attendees can be notified and history audited; distinct from soft-delete (DELETE `/calendar/events/{id}`) which hides the event from default list responses."
928
- },
929
- "attendees": {
930
- "type": "array",
931
- "items": {
932
- "type": "object",
933
- "properties": {
934
- "email": {
935
- "type": "string",
936
- "description": "Attendee email address. Used as the identity key for dedupe and invite/update/cancel fan-out."
937
- },
938
- "display_name": {
939
- "type": "string",
940
- "description": "Optional human-readable name shown in invitation emails and `.ics` payloads. Omit to fall back to the email address."
941
- }
942
- },
943
- "required": [
944
- "email"
945
- ],
946
- "description": "A single attendee on a calendar event. Each event supports up to 50 unique attendees after case-insensitive email dedupe; new attendees receive an invitation email with an `.ics` REQUEST attachment, kept attendees receive an update email when the event is mutated, and removed attendees receive a cancellation."
947
- },
948
- "description": "Up to 50 unique attendees (deduped case-insensitively by email). If non-empty, each attendee receives an invitation email with an `.ics` REQUEST attachment as a side effect of creation."
867
+ "description": "Numeric magic code delivered by email. Single-use; expires a few minutes after issuance. Treat as a short-lived secret."
949
868
  }
950
869
  },
951
870
  "required": [
952
- "title",
953
- "start",
954
- "end"
955
- ],
956
- "description": "Request body for POST `/calendar/events`. `start` and `end` must be the same kind (both timed ISO datetimes with offset, or both ISO date-only for all-day) and `end` must be strictly after `start`."
871
+ "email",
872
+ "code"
873
+ ]
957
874
  },
958
875
  "Event": {
959
876
  "type": "object",
@@ -1058,17 +975,75 @@
1058
975
  ],
1059
976
  "description": "A calendar event row as returned by the API."
1060
977
  },
1061
- "VersionBody": {
978
+ "CreateEventBody": {
1062
979
  "type": "object",
1063
980
  "properties": {
1064
- "expected_version": {
1065
- "type": "integer",
1066
- "minimum": 0,
1067
- "description": "Optional optimistic lock. Omit to let the server use the current version; pass only to fail with 409 `VERSION_CONFLICT` if someone else has mutated the event since you last read."
981
+ "title": {
982
+ "type": "string",
983
+ "minLength": 1,
984
+ "maxLength": 500,
985
+ "description": "Short human-readable summary of the event. Shown in list views and `.ics` SUMMARY."
986
+ },
987
+ "description": {
988
+ "type": "string",
989
+ "maxLength": 10000,
990
+ "description": "Free-form notes about the event (agenda, dial-in instructions, etc.). Markdown formatted (CommonMark + GFM tables, strikethrough, task lists); stored verbatim. Invitation emails include the raw source — most email clients display it as plain text."
991
+ },
992
+ "start": {
993
+ "type": "string",
994
+ "description": "Accepts ISO 8601 datetime with offset (e.g. `2026-06-01T12:30:00+08:00`) for timed events, or ISO date-only (e.g. `2026-06-01`) for all-day. The `wspc` CLI additionally accepts natural-language phrases (`tomorrow 12:30pm`, `next Monday 9am`) and resolves them to ISO before sending; the server itself only accepts ISO. All-day uses RFC 5545 exclusive end: a one-day event on 6/1 is `start=2026-06-01, end=2026-06-02`; both endpoints must be the same type."
995
+ },
996
+ "end": {
997
+ "type": "string",
998
+ "description": "Accepts ISO 8601 datetime with offset (e.g. `2026-06-01T12:30:00+08:00`) for timed events, or ISO date-only (e.g. `2026-06-01`) for all-day. The `wspc` CLI additionally accepts natural-language phrases (`tomorrow 12:30pm`, `next Monday 9am`) and resolves them to ISO before sending; the server itself only accepts ISO. All-day uses RFC 5545 exclusive end: a one-day event on 6/1 is `start=2026-06-01, end=2026-06-02`; both endpoints must be the same type."
999
+ },
1000
+ "location": {
1001
+ "type": "string",
1002
+ "maxLength": 10000,
1003
+ "description": "Free-text location — physical address, room, or short note. Separate from `url` (meeting link)."
1004
+ },
1005
+ "url": {
1006
+ "type": "string",
1007
+ "format": "uri",
1008
+ "description": "Optional meeting link (Zoom / Meet / etc.). Kept separate from `location` so calendar clients can render it as a join action."
1009
+ },
1010
+ "status": {
1011
+ "type": "string",
1012
+ "enum": [
1013
+ "confirmed",
1014
+ "tentative",
1015
+ "cancelled"
1016
+ ],
1017
+ "description": "Lifecycle status. `confirmed`: the event will happen (default). `tentative`: organizer has not finalized; still visible in lists. `cancelled`: the event was called off but the record is kept so attendees can be notified and history audited; distinct from soft-delete (DELETE `/calendar/events/{id}`) which hides the event from default list responses."
1018
+ },
1019
+ "attendees": {
1020
+ "type": "array",
1021
+ "items": {
1022
+ "type": "object",
1023
+ "properties": {
1024
+ "email": {
1025
+ "type": "string",
1026
+ "description": "Attendee email address. Used as the identity key for dedupe and invite/update/cancel fan-out."
1027
+ },
1028
+ "display_name": {
1029
+ "type": "string",
1030
+ "description": "Optional human-readable name shown in invitation emails and `.ics` payloads. Omit to fall back to the email address."
1031
+ }
1032
+ },
1033
+ "required": [
1034
+ "email"
1035
+ ],
1036
+ "description": "A single attendee on a calendar event. Each event supports up to 50 unique attendees after case-insensitive email dedupe; new attendees receive an invitation email with an `.ics` REQUEST attachment, kept attendees receive an update email when the event is mutated, and removed attendees receive a cancellation."
1037
+ },
1038
+ "description": "Up to 50 unique attendees (deduped case-insensitively by email). If non-empty, each attendee receives an invitation email with an `.ics` REQUEST attachment as a side effect of creation."
1068
1039
  }
1069
1040
  },
1070
- "additionalProperties": false,
1071
- "description": "Request body for DELETE `/calendar/events/{id}` and POST `/calendar/events/{id}/restore`. Carries only the optional optimistic-lock version."
1041
+ "required": [
1042
+ "title",
1043
+ "start",
1044
+ "end"
1045
+ ],
1046
+ "description": "Request body for POST `/calendar/events`. `start` and `end` must be the same kind (both timed ISO datetimes with offset, or both ISO date-only for all-day) and `end` must be strictly after `start`."
1072
1047
  },
1073
1048
  "DeleteEventResponse": {
1074
1049
  "type": "object",
@@ -1094,47 +1069,17 @@
1094
1069
  ],
1095
1070
  "description": "Minimal response for DELETE `/calendar/events/{id}`. Use GET with `?include_deleted=true` to fetch the full soft-deleted row."
1096
1071
  },
1097
- "ListEventsQuery": {
1072
+ "VersionBody": {
1098
1073
  "type": "object",
1099
1074
  "properties": {
1100
- "q": {
1101
- "type": "string",
1102
- "description": "Optional full-text search across title, description, and location (case-insensitive substring)."
1103
- },
1104
- "start_from": {
1105
- "type": "string",
1106
- "description": "Inclusive lower bound on the event `start` (ISO datetime with offset, or ISO date-only). When ANY of `start_from`/`start_to`/`end_from`/`end_to` is provided, the implicit past filter is disabled."
1107
- },
1108
- "start_to": {
1109
- "type": "string",
1110
- "description": "Inclusive upper bound on the event `start`."
1111
- },
1112
- "end_from": {
1113
- "type": "string",
1114
- "description": "Inclusive lower bound on the event `end`."
1115
- },
1116
- "end_to": {
1117
- "type": "string",
1118
- "description": "Inclusive upper bound on the event `end`."
1119
- },
1120
- "cursor": {
1121
- "type": "string",
1122
- "description": "Opaque pagination cursor returned in `next_cursor` of a previous response."
1123
- },
1124
- "limit": {
1125
- "type": "string",
1126
- "description": "Maximum number of events to return. Clamped to `[1, 200]`. Default is server-defined."
1127
- },
1128
- "include_deleted": {
1129
- "type": "string",
1130
- "description": "When `true`, include soft-deleted events. Default `false`."
1131
- },
1132
- "include_past": {
1133
- "type": "string",
1134
- "description": "When omitted or `false`, events whose `end` is before now are hidden. Pass `true` to include them. Ignored when any of `start_from`/`start_to`/`end_from`/`end_to` is provided — explicit time bounds always win."
1075
+ "expected_version": {
1076
+ "type": "integer",
1077
+ "minimum": 0,
1078
+ "description": "Optional optimistic lock. Omit to let the server use the current version; pass only to fail with 409 `VERSION_CONFLICT` if someone else has mutated the event since you last read."
1135
1079
  }
1136
1080
  },
1137
- "description": "Query parameters for GET `/calendar/events`. Default behaviour hides soft-deleted events and events whose `end` is in the past; supply any explicit time bound or `include_past=true` to override."
1081
+ "additionalProperties": false,
1082
+ "description": "Request body for DELETE `/calendar/events/{id}` and POST `/calendar/events/{id}/restore`. Carries only the optional optimistic-lock version."
1138
1083
  },
1139
1084
  "ListEventsResponse": {
1140
1085
  "type": "object",
@@ -1237,19 +1182,6 @@
1237
1182
  "additionalProperties": false,
1238
1183
  "description": "Request body for PATCH `/calendar/events/{id}`. All fields are optional partial updates. To CANCEL a meeting (preserve the record and notify attendees) set `status: cancelled`; to remove the event from default listings, call DELETE instead."
1239
1184
  },
1240
- "CreateAliasBody": {
1241
- "type": "object",
1242
- "properties": {
1243
- "email": {
1244
- "type": "string",
1245
- "format": "email",
1246
- "description": "Full alias address under the platform email domain or a fully verified organization custom domain, for example alice-shop@wspc.app or alice-shop@example.com."
1247
- }
1248
- },
1249
- "required": [
1250
- "email"
1251
- ]
1252
- },
1253
1185
  "Alias": {
1254
1186
  "type": "object",
1255
1187
  "properties": {
@@ -1282,16 +1214,17 @@
1282
1214
  "created_at"
1283
1215
  ]
1284
1216
  },
1285
- "CreateDomainBody": {
1217
+ "CreateAliasBody": {
1286
1218
  "type": "object",
1287
1219
  "properties": {
1288
- "domain": {
1220
+ "email": {
1289
1221
  "type": "string",
1290
- "description": "Custom domain hostname to register with the provider, for example `mail.example.com`."
1222
+ "format": "email",
1223
+ "description": "Full alias address under the platform email domain or a fully verified organization custom domain, for example alice-shop@wspc.app or alice-shop@example.com."
1291
1224
  }
1292
1225
  },
1293
1226
  "required": [
1294
- "domain"
1227
+ "email"
1295
1228
  ]
1296
1229
  },
1297
1230
  "EmailDomainObjectResponse": {
@@ -1428,21 +1361,16 @@
1428
1361
  "domain"
1429
1362
  ]
1430
1363
  },
1431
- "BatchIdsBody": {
1364
+ "CreateDomainBody": {
1432
1365
  "type": "object",
1433
1366
  "properties": {
1434
- "ids": {
1435
- "type": "array",
1436
- "items": {
1437
- "type": "string"
1438
- },
1439
- "minItems": 1,
1440
- "maxItems": 100,
1441
- "description": "Email ids to act on. 1-100 ids per call. All bulk ops (read / unread / delete / restore) are idempotent: ids that are already in the target state are silently no-ops and ids that don't belong to the user (or don't exist) are reported in `not_found` rather than failing the call."
1367
+ "domain": {
1368
+ "type": "string",
1369
+ "description": "Custom domain hostname to register with the provider, for example `mail.example.com`."
1442
1370
  }
1443
1371
  },
1444
1372
  "required": [
1445
- "ids"
1373
+ "domain"
1446
1374
  ]
1447
1375
  },
1448
1376
  "DeleteBatchResponse": {
@@ -1465,49 +1393,23 @@
1465
1393
  "not_found"
1466
1394
  ]
1467
1395
  },
1468
- "EmailDomainParam": {
1396
+ "BatchIdsBody": {
1469
1397
  "type": "object",
1470
1398
  "properties": {
1471
- "domain": {
1472
- "type": "string",
1473
- "description": "Custom domain hostname path parameter. URL-encode if your client requires it."
1399
+ "ids": {
1400
+ "type": "array",
1401
+ "items": {
1402
+ "type": "string"
1403
+ },
1404
+ "minItems": 1,
1405
+ "maxItems": 100,
1406
+ "description": "Email ids to act on. 1-100 ids per call. All bulk ops (read / unread / delete / restore) are idempotent: ids that are already in the target state are silently no-ops and ids that don't belong to the user (or don't exist) are reported in `not_found` rather than failing the call."
1474
1407
  }
1475
1408
  },
1476
1409
  "required": [
1477
- "domain"
1410
+ "ids"
1478
1411
  ]
1479
1412
  },
1480
- "GetAttachmentQuery": {
1481
- "type": "object",
1482
- "properties": {
1483
- "include_deleted": {
1484
- "type": "string",
1485
- "description": "When `true`, allow downloading an attachment whose parent email is soft-deleted. Defaults to `false`."
1486
- }
1487
- }
1488
- },
1489
- "GetEmailQuery": {
1490
- "type": "object",
1491
- "properties": {
1492
- "include_html": {
1493
- "type": "string",
1494
- "description": "When `true`, fetch the HTML body from R2 and include it as `html_body` in the response. Costs an extra R2 read; omit if you only need text."
1495
- },
1496
- "include_deleted": {
1497
- "type": "string",
1498
- "description": "When `true`, allow fetching a soft-deleted email. Defaults to `false` (returns 404 for soft-deleted rows)."
1499
- }
1500
- }
1501
- },
1502
- "ListAliasesQuery": {
1503
- "type": "object",
1504
- "properties": {
1505
- "include_deleted": {
1506
- "type": "string",
1507
- "description": "When `true`, include soft-deleted aliases (with `deleted_at` set) alongside active ones. Defaults to `false`."
1508
- }
1509
- }
1510
- },
1511
1413
  "EmailDomainListResponse": {
1512
1414
  "type": "object",
1513
1415
  "properties": {
@@ -1645,42 +1547,12 @@
1645
1547
  "domains"
1646
1548
  ]
1647
1549
  },
1648
- "ListEmailsQuery": {
1550
+ "MarkBatchResponse": {
1649
1551
  "type": "object",
1650
1552
  "properties": {
1651
- "limit": {
1652
- "type": "string",
1653
- "description": "Max items to return (clamped to 1-100). Defaults to 20 server-side."
1654
- },
1655
- "alias_email": {
1656
- "type": "string",
1657
- "format": "email",
1658
- "description": "If set, only return emails received on this full alias email address."
1659
- },
1660
- "unread_only": {
1661
- "type": "string",
1662
- "description": "When `true`, only return emails with `is_read=false`."
1663
- },
1664
- "since": {
1665
- "type": "string",
1666
- "description": "Unix epoch milliseconds — only return emails with `received_at >= since`. Useful for incremental sync."
1667
- },
1668
- "cursor": {
1669
- "type": "string",
1670
- "description": "Opaque pagination cursor returned in `next_cursor` of a previous response."
1671
- },
1672
- "include_deleted": {
1673
- "type": "string",
1674
- "description": "When `true`, also return soft-deleted emails. Defaults to `false`."
1675
- }
1676
- }
1677
- },
1678
- "MarkBatchResponse": {
1679
- "type": "object",
1680
- "properties": {
1681
- "marked": {
1682
- "type": "integer",
1683
- "description": "Number of emails whose state actually changed. Already-in-target-state ids do not count."
1553
+ "marked": {
1554
+ "type": "integer",
1555
+ "description": "Number of emails whose state actually changed. Already-in-target-state ids do not count."
1684
1556
  },
1685
1557
  "not_found": {
1686
1558
  "type": "array",
@@ -1715,104 +1587,6 @@
1715
1587
  "not_found"
1716
1588
  ]
1717
1589
  },
1718
- "SendEmailBody": {
1719
- "type": "object",
1720
- "properties": {
1721
- "from_alias_email": {
1722
- "type": "string",
1723
- "format": "email",
1724
- "description": "Full active alias email address owned by the caller. Soft-deleted aliases are not allowed for sending."
1725
- },
1726
- "to": {
1727
- "type": "array",
1728
- "items": {
1729
- "type": "string"
1730
- },
1731
- "minItems": 1,
1732
- "maxItems": 10,
1733
- "description": "Recipient addresses. 1-10 entries. Required for a fresh send; may be omitted when `in_reply_to_email_id` is set (the server then reuses the original sender as the sole recipient)."
1734
- },
1735
- "subject": {
1736
- "type": "string",
1737
- "maxLength": 500,
1738
- "description": "Outbound subject. Required for a fresh send; may be omitted on reply (the server prefixes the inbound subject with `Re: `)."
1739
- },
1740
- "text": {
1741
- "type": "string",
1742
- "minLength": 1,
1743
- "description": "Plain-text body. 1 byte to 100 KiB. HTML bodies are not exposed in the v1 send API — agents compose plain text and clients render as desired."
1744
- },
1745
- "in_reply_to_email_id": {
1746
- "type": "string",
1747
- "description": "If set, sends as a threaded reply to the referenced inbound email. The outbound `In-Reply-To` / `References` headers and recipient default are derived from that message; the inbound email must belong to the caller."
1748
- },
1749
- "idempotency_key": {
1750
- "type": "string",
1751
- "minLength": 1,
1752
- "maxLength": 200,
1753
- "description": "Caller-chosen key (1-200 chars) that makes retries safe. The server hashes the full request and stores it under this key for the calling user: a second send with the same key and identical content returns the original result with `idempotent_replay: true`; the same key with different content returns 409 `IDEMPOTENCY_KEY_REUSED`."
1754
- },
1755
- "attachments": {
1756
- "type": "array",
1757
- "items": {
1758
- "anyOf": [
1759
- {
1760
- "type": "object",
1761
- "properties": {
1762
- "filename": {
1763
- "type": "string",
1764
- "minLength": 1,
1765
- "maxLength": 255,
1766
- "description": "Filename as presented to the recipient. Must not contain `/` or NUL."
1767
- },
1768
- "content_type": {
1769
- "type": "string",
1770
- "minLength": 1,
1771
- "maxLength": 255,
1772
- "description": "MIME type as advertised in the outbound message Content-Type header."
1773
- },
1774
- "content_base64": {
1775
- "type": "string",
1776
- "minLength": 1,
1777
- "description": "Base64-encoded attachment bytes. Per-attachment ≤ 5 MiB; per-send total ≤ 25 MiB."
1778
- }
1779
- },
1780
- "required": [
1781
- "filename",
1782
- "content_type",
1783
- "content_base64"
1784
- ]
1785
- },
1786
- {
1787
- "type": "object",
1788
- "properties": {
1789
- "from_inbound_email_id": {
1790
- "type": "string",
1791
- "description": "Id of an inbound email owned by the caller. Server reads its attachment at `idx` from R2 and re-attaches."
1792
- },
1793
- "idx": {
1794
- "type": "integer",
1795
- "minimum": 0,
1796
- "description": "0-based index into the inbound email's attachments array."
1797
- }
1798
- },
1799
- "required": [
1800
- "from_inbound_email_id",
1801
- "idx"
1802
- ]
1803
- }
1804
- ]
1805
- },
1806
- "maxItems": 10,
1807
- "description": "Optional outbound attachments. Each element is either an inline `{filename, content_type, content_base64}` blob OR a reference `{from_inbound_email_id, idx}` to an inbound email's existing attachment. Per-attachment ≤ 5 MiB; per-send total ≤ 25 MiB; max 10 attachments. Extensions in `[exe, bat, com, scr, cmd, jar, js]` are rejected."
1808
- }
1809
- },
1810
- "required": [
1811
- "from_alias_email",
1812
- "text",
1813
- "idempotency_key"
1814
- ]
1815
- },
1816
1590
  "SendEmailResponse": {
1817
1591
  "type": "object",
1818
1592
  "properties": {
@@ -1868,22 +1642,6 @@
1868
1642
  "type": "string",
1869
1643
  "description": "Materialized `References` header for threaded replies."
1870
1644
  },
1871
- "provider": {
1872
- "type": "string",
1873
- "enum": [
1874
- "cloudflare-email-service",
1875
- "pete-mail"
1876
- ],
1877
- "description": "Send provider used for this outbound email."
1878
- },
1879
- "provider_message_id": {
1880
- "type": "string",
1881
- "description": "Provider-side message id (after successful submission)."
1882
- },
1883
- "request_hash": {
1884
- "type": "string",
1885
- "description": "Hash of the canonicalized request body — used to enforce idempotency."
1886
- },
1887
1645
  "status": {
1888
1646
  "type": "string",
1889
1647
  "enum": [
@@ -1893,14 +1651,6 @@
1893
1651
  ],
1894
1652
  "description": "Lifecycle status. `submitted`: the row is persisted; provider call is in flight. `sent`: provider accepted the message. `failed`: provider rejected; see `error_code`/`error_message`."
1895
1653
  },
1896
- "error_code": {
1897
- "type": "string",
1898
- "description": "Provider error code on `status: failed`."
1899
- },
1900
- "error_message": {
1901
- "type": "string",
1902
- "description": "Provider error message on `status: failed`."
1903
- },
1904
1654
  "idempotency_key": {
1905
1655
  "type": "string",
1906
1656
  "description": "Echo of the idempotency key used for this send."
@@ -1932,8 +1682,6 @@
1932
1682
  "to",
1933
1683
  "text_body",
1934
1684
  "message_id",
1935
- "provider",
1936
- "request_hash",
1937
1685
  "status",
1938
1686
  "idempotency_key",
1939
1687
  "attachment_count",
@@ -1951,6 +1699,104 @@
1951
1699
  "idempotent_replay"
1952
1700
  ]
1953
1701
  },
1702
+ "SendEmailBody": {
1703
+ "type": "object",
1704
+ "properties": {
1705
+ "from_alias_email": {
1706
+ "type": "string",
1707
+ "format": "email",
1708
+ "description": "Full active alias email address owned by the caller. Soft-deleted aliases are not allowed for sending."
1709
+ },
1710
+ "to": {
1711
+ "type": "array",
1712
+ "items": {
1713
+ "type": "string"
1714
+ },
1715
+ "minItems": 1,
1716
+ "maxItems": 10,
1717
+ "description": "Recipient addresses. 1-10 entries. Required for a fresh send; may be omitted when `in_reply_to_email_id` is set (the server then reuses the original sender as the sole recipient)."
1718
+ },
1719
+ "subject": {
1720
+ "type": "string",
1721
+ "maxLength": 500,
1722
+ "description": "Outbound subject. Required for a fresh send; may be omitted on reply (the server prefixes the inbound subject with `Re: `)."
1723
+ },
1724
+ "text": {
1725
+ "type": "string",
1726
+ "minLength": 1,
1727
+ "description": "Plain-text body. 1 byte to 100 KiB. HTML bodies are not exposed in the v1 send API — agents compose plain text and clients render as desired."
1728
+ },
1729
+ "in_reply_to_email_id": {
1730
+ "type": "string",
1731
+ "description": "If set, sends as a threaded reply to the referenced inbound email. The outbound `In-Reply-To` / `References` headers and recipient default are derived from that message; the inbound email must belong to the caller."
1732
+ },
1733
+ "idempotency_key": {
1734
+ "type": "string",
1735
+ "minLength": 1,
1736
+ "maxLength": 200,
1737
+ "description": "Caller-chosen key (1-200 chars) that makes retries safe. The server hashes the full request and stores it under this key for the calling user: a second send with the same key and identical content returns the original result with `idempotent_replay: true`; the same key with different content returns 409 `IDEMPOTENCY_KEY_REUSED`."
1738
+ },
1739
+ "attachments": {
1740
+ "type": "array",
1741
+ "items": {
1742
+ "anyOf": [
1743
+ {
1744
+ "type": "object",
1745
+ "properties": {
1746
+ "filename": {
1747
+ "type": "string",
1748
+ "minLength": 1,
1749
+ "maxLength": 255,
1750
+ "description": "Filename as presented to the recipient. Must not contain `/` or NUL."
1751
+ },
1752
+ "content_type": {
1753
+ "type": "string",
1754
+ "minLength": 1,
1755
+ "maxLength": 255,
1756
+ "description": "MIME type as advertised in the outbound message Content-Type header."
1757
+ },
1758
+ "content_base64": {
1759
+ "type": "string",
1760
+ "minLength": 1,
1761
+ "description": "Base64-encoded attachment bytes. Per-attachment ≤ 5 MiB; per-send total ≤ 25 MiB."
1762
+ }
1763
+ },
1764
+ "required": [
1765
+ "filename",
1766
+ "content_type",
1767
+ "content_base64"
1768
+ ]
1769
+ },
1770
+ {
1771
+ "type": "object",
1772
+ "properties": {
1773
+ "from_inbound_email_id": {
1774
+ "type": "string",
1775
+ "description": "Id of an inbound email owned by the caller. Server reads its attachment at `idx` from R2 and re-attaches."
1776
+ },
1777
+ "idx": {
1778
+ "type": "integer",
1779
+ "minimum": 0,
1780
+ "description": "0-based index into the inbound email's attachments array."
1781
+ }
1782
+ },
1783
+ "required": [
1784
+ "from_inbound_email_id",
1785
+ "idx"
1786
+ ]
1787
+ }
1788
+ ]
1789
+ },
1790
+ "maxItems": 10,
1791
+ "description": "Optional outbound attachments. Each element is either an inline `{filename, content_type, content_base64}` blob OR a reference `{from_inbound_email_id, idx}` to an inbound email's existing attachment. Per-attachment ≤ 5 MiB; per-send total ≤ 25 MiB; max 10 attachments. Extensions in `[exe, bat, com, scr, cmd, jar, js]` are rejected."
1792
+ }
1793
+ },
1794
+ "required": [
1795
+ "from_alias_email",
1796
+ "text",
1797
+ "idempotency_key"
1798
+ ]
1799
+ },
1954
1800
  "SetPushConfigBody": {
1955
1801
  "type": "object",
1956
1802
  "properties": {
@@ -2053,21 +1899,6 @@
2053
1899
  "configs"
2054
1900
  ]
2055
1901
  },
2056
- "TestPushBody": {
2057
- "type": "object",
2058
- "properties": {
2059
- "transport": {
2060
- "type": "string",
2061
- "enum": [
2062
- "telegram"
2063
- ],
2064
- "description": "Which transport to send the test message through. Must match a transport the caller has already registered via `POST /push/config`; today only `telegram` is supported."
2065
- }
2066
- },
2067
- "required": [
2068
- "transport"
2069
- ]
2070
- },
2071
1902
  "TestPushResponse": {
2072
1903
  "oneOf": [
2073
1904
  {
@@ -2122,17 +1953,19 @@
2122
1953
  }
2123
1954
  ]
2124
1955
  },
2125
- "CreateCommentBody": {
1956
+ "TestPushBody": {
2126
1957
  "type": "object",
2127
1958
  "properties": {
2128
- "content": {
1959
+ "transport": {
2129
1960
  "type": "string",
2130
- "minLength": 1,
2131
- "maxLength": 10000
1961
+ "enum": [
1962
+ "telegram"
1963
+ ],
1964
+ "description": "Which transport to send the test message through. Must match a transport the caller has already registered via `POST /push/config`; today only `telegram` is supported."
2132
1965
  }
2133
1966
  },
2134
1967
  "required": [
2135
- "content"
1968
+ "transport"
2136
1969
  ]
2137
1970
  },
2138
1971
  "Comment": {
@@ -2173,20 +2006,17 @@
2173
2006
  "updated_at"
2174
2007
  ]
2175
2008
  },
2176
- "CreateProjectBody": {
2009
+ "CreateCommentBody": {
2177
2010
  "type": "object",
2178
2011
  "properties": {
2179
- "name": {
2012
+ "content": {
2180
2013
  "type": "string",
2181
2014
  "minLength": 1,
2182
- "maxLength": 500
2183
- },
2184
- "default_todo_type_id": {
2185
- "type": "string"
2015
+ "maxLength": 10000
2186
2016
  }
2187
2017
  },
2188
2018
  "required": [
2189
- "name"
2019
+ "content"
2190
2020
  ]
2191
2021
  },
2192
2022
  "Project": {
@@ -2230,51 +2060,20 @@
2230
2060
  "updated_at"
2231
2061
  ]
2232
2062
  },
2233
- "CreateRecurrenceRuleBody": {
2063
+ "CreateProjectBody": {
2234
2064
  "type": "object",
2235
2065
  "properties": {
2236
- "rrule": {
2237
- "type": "string",
2238
- "minLength": 1
2239
- },
2240
- "dtstart": {
2241
- "type": "string",
2242
- "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
2243
- },
2244
- "title": {
2066
+ "name": {
2245
2067
  "type": "string",
2246
2068
  "minLength": 1,
2247
2069
  "maxLength": 500
2248
2070
  },
2249
- "description": {
2250
- "type": "string",
2251
- "maxLength": 10000
2252
- },
2253
- "parent_id": {
2254
- "anyOf": [
2255
- {
2256
- "type": "string",
2257
- "minLength": 1
2258
- },
2259
- {
2260
- "type": "null"
2261
- }
2262
- ]
2263
- },
2264
- "project_id": {
2265
- "type": "string",
2266
- "description": "Project for the recurrence rule, its template todo, and all materialized instances. Must be an active project in the caller's organization."
2267
- },
2268
- "type_id": {
2269
- "type": "string",
2270
- "minLength": 1
2071
+ "default_todo_type_id": {
2072
+ "type": "string"
2271
2073
  }
2272
2074
  },
2273
2075
  "required": [
2274
- "rrule",
2275
- "dtstart",
2276
- "title",
2277
- "project_id"
2076
+ "name"
2278
2077
  ]
2279
2078
  },
2280
2079
  "CreateRecurrenceRuleResponse": {
@@ -2344,24 +2143,25 @@
2344
2143
  "updated_at"
2345
2144
  ]
2346
2145
  },
2347
- "CreateTodoBody": {
2146
+ "CreateRecurrenceRuleBody": {
2348
2147
  "type": "object",
2349
2148
  "properties": {
2350
- "title": {
2149
+ "rrule": {
2351
2150
  "type": "string",
2352
- "minLength": 1,
2353
- "maxLength": 500,
2354
- "description": "Short human-readable summary of the work item. Required, non-empty. Shown as the primary label in CLI list output."
2151
+ "minLength": 1
2355
2152
  },
2356
- "project_id": {
2153
+ "dtstart": {
2154
+ "type": "string",
2155
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
2156
+ },
2157
+ "title": {
2357
2158
  "type": "string",
2358
2159
  "minLength": 1,
2359
- "description": "Project id to assign this todo to. It must be an active project in the caller's organization."
2160
+ "maxLength": 500
2360
2161
  },
2361
2162
  "description": {
2362
2163
  "type": "string",
2363
- "maxLength": 10000,
2364
- "description": "Free-form details about the todo. Fully supports GFM Markdown (tables, strikethrough, task lists). Stored verbatim; client applications are responsible for rendering. Optional. Passing `null` is strictly rejected."
2164
+ "maxLength": 10000
2365
2165
  },
2366
2166
  "parent_id": {
2367
2167
  "anyOf": [
@@ -2372,8 +2172,42 @@
2372
2172
  {
2373
2173
  "type": "null"
2374
2174
  }
2375
- ],
2376
- "description": "Parent todo ID (`tod_<ULID>`) to attach this todo as a child under another todo. Omit or pass `null` to create a root-level todo. Nesting is limited to one level; attempting to set a child todo as a parent will trigger `PARENT_IS_CHILD`. To make a subtask appear on every occurrence of a recurring rule, set this to that rule's template todo id (the template id returned when the rule is created); the server re-materializes future occurrences so each carries the subtask."
2175
+ ]
2176
+ },
2177
+ "project_id": {
2178
+ "type": "string",
2179
+ "description": "Project for the recurrence rule, its template todo, and all materialized instances. Must be an active project in the caller's organization."
2180
+ },
2181
+ "type_id": {
2182
+ "type": "string",
2183
+ "minLength": 1
2184
+ }
2185
+ },
2186
+ "required": [
2187
+ "rrule",
2188
+ "dtstart",
2189
+ "title",
2190
+ "project_id"
2191
+ ]
2192
+ },
2193
+ "Todo": {
2194
+ "type": "object",
2195
+ "properties": {
2196
+ "id": {
2197
+ "type": "string"
2198
+ },
2199
+ "user_id": {
2200
+ "type": "string"
2201
+ },
2202
+ "project_id": {
2203
+ "type": "string",
2204
+ "description": "Project id this todo belongs to. Use /todo/projects/{id} to inspect the project."
2205
+ },
2206
+ "title": {
2207
+ "type": "string"
2208
+ },
2209
+ "description": {
2210
+ "type": "string"
2377
2211
  },
2378
2212
  "status": {
2379
2213
  "type": "string",
@@ -2382,17 +2216,36 @@
2382
2216
  "in_progress",
2383
2217
  "done",
2384
2218
  "cancelled"
2385
- ],
2386
- "description": "Initial status of the todo. Omit to default to `open`. Allowed values: `open`, `in_progress`, `done`, `cancelled`."
2219
+ ]
2220
+ },
2221
+ "parent_id": {
2222
+ "type": [
2223
+ "string",
2224
+ "null"
2225
+ ]
2226
+ },
2227
+ "child_count": {
2228
+ "type": "integer",
2229
+ "minimum": 0
2230
+ },
2231
+ "version": {
2232
+ "type": "integer",
2233
+ "minimum": 0
2234
+ },
2235
+ "created_at": {
2236
+ "type": "number"
2237
+ },
2238
+ "updated_at": {
2239
+ "type": "number"
2240
+ },
2241
+ "deleted_at": {
2242
+ "type": "number"
2387
2243
  },
2388
2244
  "due_at": {
2389
- "type": "string",
2390
- "description": "Optional calendar due date in ISO date-only format (`YYYY-MM-DD`). Stored without timezone offsets to represent the same local calendar day globally. Pass `\"\"` or omit the field to skip setting a due date. Passing `null` is strictly rejected."
2245
+ "type": "string"
2391
2246
  },
2392
2247
  "type_id": {
2393
- "type": "string",
2394
- "minLength": 1,
2395
- "description": "Type id this todo belongs to. Omit to use the project's default type. When project_id is also supplied, the type must belong to the same project. New server-generated type ids use typ_<ULID>; legacy ids remain accepted."
2248
+ "type": "string"
2396
2249
  },
2397
2250
  "custom_fields": {
2398
2251
  "type": "object",
@@ -2408,33 +2261,53 @@
2408
2261
  }
2409
2262
  }
2410
2263
  ]
2411
- },
2412
- "description": "Custom field values keyed by the field's immutable `key` (not the human `label`). Each value must match the declared field type: string fields require string values, and string_array fields require string arrays. Providing a key that is not declared on the resolved todo type is strictly rejected with `UNDECLARED_FIELD`. Missing required fields that lack a default value are rejected with `FIELD_REQUIRED`. Defaults declared on the type are auto-applied at create time."
2264
+ }
2413
2265
  }
2414
2266
  },
2415
2267
  "required": [
2268
+ "id",
2269
+ "user_id",
2270
+ "project_id",
2416
2271
  "title",
2417
- "project_id"
2272
+ "status",
2273
+ "parent_id",
2274
+ "child_count",
2275
+ "version",
2276
+ "created_at",
2277
+ "updated_at",
2278
+ "type_id"
2418
2279
  ]
2419
2280
  },
2420
- "Todo": {
2281
+ "CreateTodoBody": {
2421
2282
  "type": "object",
2422
2283
  "properties": {
2423
- "id": {
2424
- "type": "string"
2425
- },
2426
- "user_id": {
2427
- "type": "string"
2284
+ "title": {
2285
+ "type": "string",
2286
+ "minLength": 1,
2287
+ "maxLength": 500,
2288
+ "description": "Short human-readable summary of the work item. Required, non-empty. Shown as the primary label in CLI list output."
2428
2289
  },
2429
2290
  "project_id": {
2430
2291
  "type": "string",
2431
- "description": "Project id this todo belongs to. Use /todo/projects/{id} to inspect the project."
2432
- },
2433
- "title": {
2434
- "type": "string"
2292
+ "minLength": 1,
2293
+ "description": "Project id to assign this todo to. It must be an active project in the caller's organization."
2435
2294
  },
2436
2295
  "description": {
2437
- "type": "string"
2296
+ "type": "string",
2297
+ "maxLength": 10000,
2298
+ "description": "Free-form details about the todo. Fully supports GFM Markdown (tables, strikethrough, task lists). Stored verbatim; client applications are responsible for rendering. Optional. Passing `null` is strictly rejected."
2299
+ },
2300
+ "parent_id": {
2301
+ "anyOf": [
2302
+ {
2303
+ "type": "string",
2304
+ "minLength": 1
2305
+ },
2306
+ {
2307
+ "type": "null"
2308
+ }
2309
+ ],
2310
+ "description": "Parent todo ID (`tod_<ULID>`) to attach this todo as a child under another todo. Omit or pass `null` to create a root-level todo. Nesting is limited to one level; attempting to set a child todo as a parent will trigger `PARENT_IS_CHILD`. To make a subtask appear on every occurrence of a recurring rule, set this to that rule's template todo id (the template id returned when the rule is created); the server re-materializes future occurrences so each carries the subtask."
2438
2311
  },
2439
2312
  "status": {
2440
2313
  "type": "string",
@@ -2443,36 +2316,17 @@
2443
2316
  "in_progress",
2444
2317
  "done",
2445
2318
  "cancelled"
2446
- ]
2447
- },
2448
- "parent_id": {
2449
- "type": [
2450
- "string",
2451
- "null"
2452
- ]
2453
- },
2454
- "child_count": {
2455
- "type": "integer",
2456
- "minimum": 0
2457
- },
2458
- "version": {
2459
- "type": "integer",
2460
- "minimum": 0
2461
- },
2462
- "created_at": {
2463
- "type": "number"
2464
- },
2465
- "updated_at": {
2466
- "type": "number"
2467
- },
2468
- "deleted_at": {
2469
- "type": "number"
2319
+ ],
2320
+ "description": "Initial status of the todo. Omit to default to `open`. Allowed values: `open`, `in_progress`, `done`, `cancelled`."
2470
2321
  },
2471
2322
  "due_at": {
2472
- "type": "string"
2323
+ "type": "string",
2324
+ "description": "Optional calendar due date in ISO date-only format (`YYYY-MM-DD`). Stored without timezone offsets to represent the same local calendar day globally. Pass `\"\"` or omit the field to skip setting a due date. Passing `null` is strictly rejected."
2473
2325
  },
2474
2326
  "type_id": {
2475
- "type": "string"
2327
+ "type": "string",
2328
+ "minLength": 1,
2329
+ "description": "Type id this todo belongs to. Omit to use the project's default type. When project_id is also supplied, the type must belong to the same project. New server-generated type ids use typ_<ULID>; legacy ids remain accepted."
2476
2330
  },
2477
2331
  "custom_fields": {
2478
2332
  "type": "object",
@@ -2488,47 +2342,32 @@
2488
2342
  }
2489
2343
  }
2490
2344
  ]
2491
- }
2345
+ },
2346
+ "description": "Custom field values keyed by the field's immutable `key` (not the human `label`). Each value must match the declared field type: string fields require string values, and string_array fields require string arrays. Providing a key that is not declared on the resolved todo type is strictly rejected with `UNDECLARED_FIELD`. Missing required fields that lack a default value are rejected with `FIELD_REQUIRED`. Defaults declared on the type are auto-applied at create time."
2492
2347
  }
2493
2348
  },
2494
2349
  "required": [
2495
- "id",
2496
- "user_id",
2497
- "project_id",
2498
2350
  "title",
2499
- "status",
2500
- "parent_id",
2501
- "child_count",
2502
- "version",
2503
- "created_at",
2504
- "updated_at",
2505
- "type_id"
2351
+ "project_id"
2506
2352
  ]
2507
2353
  },
2508
- "CreateTodoTypeBody": {
2354
+ "TodoType": {
2509
2355
  "type": "object",
2510
2356
  "properties": {
2511
- "label": {
2512
- "type": "string",
2513
- "minLength": 1,
2514
- "maxLength": 500
2357
+ "id": {
2358
+ "type": "string"
2515
2359
  },
2516
2360
  "project_id": {
2517
2361
  "type": "string",
2518
- "minLength": 1,
2519
- "description": "Project this type belongs to. Required. It must be an active project in the caller's organization."
2362
+ "description": "Project scope for this todo type. Todos can use this type only when they belong to the same project."
2363
+ },
2364
+ "label": {
2365
+ "type": "string"
2520
2366
  },
2521
2367
  "hide_core_fields": {
2522
2368
  "type": "array",
2523
2369
  "items": {
2524
- "type": "string",
2525
- "enum": [
2526
- "description",
2527
- "status",
2528
- "due_at",
2529
- "parent_id",
2530
- "recurrence"
2531
- ]
2370
+ "type": "string"
2532
2371
  }
2533
2372
  },
2534
2373
  "custom_fields": {
@@ -2598,30 +2437,55 @@
2598
2437
  }
2599
2438
  ]
2600
2439
  }
2440
+ },
2441
+ "version": {
2442
+ "type": "integer"
2443
+ },
2444
+ "created_at": {
2445
+ "type": "integer"
2446
+ },
2447
+ "updated_at": {
2448
+ "type": "integer"
2449
+ },
2450
+ "deleted_at": {
2451
+ "type": "integer"
2601
2452
  }
2602
2453
  },
2603
2454
  "required": [
2455
+ "id",
2456
+ "project_id",
2604
2457
  "label",
2605
- "project_id"
2458
+ "hide_core_fields",
2459
+ "custom_fields",
2460
+ "version",
2461
+ "created_at",
2462
+ "updated_at"
2606
2463
  ]
2607
2464
  },
2608
- "TodoType": {
2465
+ "CreateTodoTypeBody": {
2609
2466
  "type": "object",
2610
2467
  "properties": {
2611
- "id": {
2612
- "type": "string"
2468
+ "label": {
2469
+ "type": "string",
2470
+ "minLength": 1,
2471
+ "maxLength": 500
2613
2472
  },
2614
2473
  "project_id": {
2615
2474
  "type": "string",
2616
- "description": "Project scope for this todo type. Todos can use this type only when they belong to the same project."
2617
- },
2618
- "label": {
2619
- "type": "string"
2475
+ "minLength": 1,
2476
+ "description": "Project this type belongs to. Required. It must be an active project in the caller's organization."
2620
2477
  },
2621
2478
  "hide_core_fields": {
2622
2479
  "type": "array",
2623
2480
  "items": {
2624
- "type": "string"
2481
+ "type": "string",
2482
+ "enum": [
2483
+ "description",
2484
+ "status",
2485
+ "due_at",
2486
+ "parent_id",
2487
+ "recurrence"
2488
+ ]
2625
2489
  }
2626
2490
  },
2627
2491
  "custom_fields": {
@@ -2691,40 +2555,13 @@
2691
2555
  }
2692
2556
  ]
2693
2557
  }
2694
- },
2695
- "version": {
2696
- "type": "integer"
2697
- },
2698
- "created_at": {
2699
- "type": "integer"
2700
- },
2701
- "updated_at": {
2702
- "type": "integer"
2703
- },
2704
- "deleted_at": {
2705
- "type": "integer"
2706
2558
  }
2707
2559
  },
2708
2560
  "required": [
2709
- "id",
2710
- "project_id",
2711
2561
  "label",
2712
- "hide_core_fields",
2713
- "custom_fields",
2714
- "version",
2715
- "created_at",
2716
- "updated_at"
2562
+ "project_id"
2717
2563
  ]
2718
2564
  },
2719
- "DeleteRecurrenceRuleBody": {
2720
- "type": "object",
2721
- "properties": {
2722
- "expected_version": {
2723
- "type": "integer",
2724
- "minimum": 0
2725
- }
2726
- }
2727
- },
2728
2565
  "DeleteRecurrenceRuleResponse": {
2729
2566
  "type": "object",
2730
2567
  "properties": {
@@ -2736,16 +2573,12 @@
2736
2573
  "ok"
2737
2574
  ]
2738
2575
  },
2739
- "DeleteTodoBody": {
2576
+ "DeleteRecurrenceRuleBody": {
2740
2577
  "type": "object",
2741
2578
  "properties": {
2742
2579
  "expected_version": {
2743
2580
  "type": "integer",
2744
2581
  "minimum": 0
2745
- },
2746
- "cascade": {
2747
- "type": "boolean",
2748
- "default": false
2749
2582
  }
2750
2583
  }
2751
2584
  },
@@ -2765,6 +2598,19 @@
2765
2598
  "deleted_at"
2766
2599
  ]
2767
2600
  },
2601
+ "DeleteTodoBody": {
2602
+ "type": "object",
2603
+ "properties": {
2604
+ "expected_version": {
2605
+ "type": "integer",
2606
+ "minimum": 0
2607
+ },
2608
+ "cascade": {
2609
+ "type": "boolean",
2610
+ "default": false
2611
+ }
2612
+ }
2613
+ },
2768
2614
  "RecurrenceRuleDetail": {
2769
2615
  "type": "object",
2770
2616
  "properties": {
@@ -2870,40 +2716,6 @@
2870
2716
  "materialized_instance_count"
2871
2717
  ]
2872
2718
  },
2873
- "GetTodoQuery": {
2874
- "type": "object",
2875
- "properties": {
2876
- "include_deleted": {
2877
- "anyOf": [
2878
- {
2879
- "type": "string"
2880
- },
2881
- {
2882
- "type": "array",
2883
- "items": {
2884
- "type": "string"
2885
- }
2886
- }
2887
- ]
2888
- },
2889
- "include_orphan_fields": {
2890
- "anyOf": [
2891
- {
2892
- "type": "string"
2893
- },
2894
- {
2895
- "type": "array",
2896
- "items": {
2897
- "type": "string"
2898
- }
2899
- }
2900
- ]
2901
- },
2902
- "include": {
2903
- "type": "string"
2904
- }
2905
- }
2906
- },
2907
2719
  "TodoWithRelations": {
2908
2720
  "type": "object",
2909
2721
  "properties": {
@@ -3010,22 +2822,6 @@
3010
2822
  "type_id"
3011
2823
  ]
3012
2824
  },
3013
- "ListRecurrenceRulesQuery": {
3014
- "type": "object",
3015
- "properties": {
3016
- "project_id": {
3017
- "type": "string",
3018
- "minLength": 1,
3019
- "description": "Project id filter. Required. Unknown, cross-organization, or soft-deleted project ids return NOT_FOUND."
3020
- },
3021
- "user_id": {
3022
- "type": "string"
3023
- }
3024
- },
3025
- "required": [
3026
- "project_id"
3027
- ]
3028
- },
3029
2825
  "ListRecurrenceRulesResponse": {
3030
2826
  "type": "object",
3031
2827
  "properties": {
@@ -3058,19 +2854,6 @@
3058
2854
  "todos"
3059
2855
  ]
3060
2856
  },
3061
- "RestoreTodoBody": {
3062
- "type": "object",
3063
- "properties": {
3064
- "expected_version": {
3065
- "type": "integer",
3066
- "minimum": 0
3067
- },
3068
- "cascade": {
3069
- "type": "boolean",
3070
- "default": false
3071
- }
3072
- }
3073
- },
3074
2857
  "RestoreTodoResponse": {
3075
2858
  "type": "object",
3076
2859
  "properties": {
@@ -3092,6 +2875,19 @@
3092
2875
  "descendants_in_trash_count"
3093
2876
  ]
3094
2877
  },
2878
+ "RestoreTodoBody": {
2879
+ "type": "object",
2880
+ "properties": {
2881
+ "expected_version": {
2882
+ "type": "integer",
2883
+ "minimum": 0
2884
+ },
2885
+ "cascade": {
2886
+ "type": "boolean",
2887
+ "default": false
2888
+ }
2889
+ }
2890
+ },
3095
2891
  "UpdateCommentBody": {
3096
2892
  "type": "object",
3097
2893
  "properties": {
@@ -20305,8 +20101,6 @@
20305
20101
  "subject": "Welcome to WSPC",
20306
20102
  "text_body": "Hi Alice — welcome aboard.",
20307
20103
  "message_id": "<01HW3K4N9V5G6Z8C2Q7B1Y0M3G@wspc.app>",
20308
- "provider": "cloudflare-email-service",
20309
- "request_hash": "sha256:abc...",
20310
20104
  "status": "sent",
20311
20105
  "idempotency_key": "retry-20260601-001",
20312
20106
  "submitted_at": 1748736000500,
@@ -20331,8 +20125,6 @@
20331
20125
  "subject": "Welcome to WSPC",
20332
20126
  "text_body": "Hi Alice — welcome aboard.",
20333
20127
  "message_id": "<01HW3K4N9V5G6Z8C2Q7B1Y0M3G@wspc.app>",
20334
- "provider": "cloudflare-email-service",
20335
- "request_hash": "sha256:abc...",
20336
20128
  "status": "sent",
20337
20129
  "idempotency_key": "retry-20260601-001",
20338
20130
  "submitted_at": 1748736000500,
@@ -21010,10 +20802,10 @@
21010
20802
  "name": "x-cb-push",
21011
20803
  "in": "header",
21012
20804
  "required": false,
20805
+ "description": "Optional opaque consistency bookmark returned by a previous push response. Send it back unchanged to continue read-after-write consistency.",
21013
20806
  "schema": {
21014
20807
  "type": "string"
21015
20808
  },
21016
- "description": "Optional opaque consistency bookmark returned by a previous push response. Send it back unchanged to continue read-after-write consistency.",
21017
20809
  "example": "opaque-consistency-bookmark"
21018
20810
  },
21019
20811
  {
@@ -21296,10 +21088,10 @@
21296
21088
  "name": "x-cb-push",
21297
21089
  "in": "header",
21298
21090
  "required": false,
21091
+ "description": "Optional opaque consistency bookmark returned by a previous push response. Send it back unchanged to continue read-after-write consistency.",
21299
21092
  "schema": {
21300
21093
  "type": "string"
21301
21094
  },
21302
- "description": "Optional opaque consistency bookmark returned by a previous push response. Send it back unchanged to continue read-after-write consistency.",
21303
21095
  "example": "opaque-consistency-bookmark"
21304
21096
  }
21305
21097
  ],
@@ -21627,10 +21419,10 @@
21627
21419
  "name": "x-cb-push",
21628
21420
  "in": "header",
21629
21421
  "required": false,
21422
+ "description": "Optional opaque consistency bookmark returned by a previous push response. Send it back unchanged to continue read-after-write consistency.",
21630
21423
  "schema": {
21631
21424
  "type": "string"
21632
21425
  },
21633
- "description": "Optional opaque consistency bookmark returned by a previous push response. Send it back unchanged to continue read-after-write consistency.",
21634
21426
  "example": "opaque-consistency-bookmark"
21635
21427
  }
21636
21428
  ],
@@ -21932,10 +21724,10 @@
21932
21724
  "name": "x-cb-push",
21933
21725
  "in": "header",
21934
21726
  "required": false,
21727
+ "description": "Optional opaque consistency bookmark returned by a previous push response. Send it back unchanged to continue read-after-write consistency.",
21935
21728
  "schema": {
21936
21729
  "type": "string"
21937
21730
  },
21938
- "description": "Optional opaque consistency bookmark returned by a previous push response. Send it back unchanged to continue read-after-write consistency.",
21939
21731
  "example": "opaque-consistency-bookmark"
21940
21732
  }
21941
21733
  ],