@whooshbang/contracts 3.0.0 → 3.2.0

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 (94) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/contract-manifest.json +32 -32
  3. package/dist/generated/artifact-index.d.ts +33 -33
  4. package/dist/generated/artifact-index.js +32 -32
  5. package/dist/generated/validators-standalone.d.ts +582 -491
  6. package/dist/generated/validators-standalone.js +10298 -5133
  7. package/dist/generated/validators-standalone.js.map +1 -1
  8. package/dist/generated/validators.d.ts +8 -1
  9. package/dist/generated/validators.js +8 -1
  10. package/dist/generated/validators.js.map +1 -1
  11. package/dist/generated/whooshbang-v1.d.ts +426 -3
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.js +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/input-defaults.d.ts +1 -1
  16. package/dist/input-defaults.js.map +1 -1
  17. package/fixtures/core-v1/fixture-index.json +1 -1
  18. package/fixtures/core-v1/scenarios/api-credential-invalid-indistinguishable.json +1 -1
  19. package/fixtures/core-v1/scenarios/api-credential-one-time-reveal.json +1 -1
  20. package/fixtures/core-v1/scenarios/api-credential-revocation-replay.json +1 -1
  21. package/fixtures/core-v1/scenarios/api-credential-rotation-overlap.json +1 -1
  22. package/fixtures/core-v1/scenarios/cancellation-too-late.json +1 -1
  23. package/fixtures/core-v1/scenarios/cross-organization-project.json +1 -1
  24. package/fixtures/core-v1/scenarios/cross-project-resource.json +1 -1
  25. package/fixtures/core-v1/scenarios/environment-mismatch.json +1 -1
  26. package/fixtures/core-v1/scenarios/idempotency-conflict.json +1 -1
  27. package/fixtures/core-v1/scenarios/idempotency-equivalent-replay.json +1 -1
  28. package/fixtures/core-v1/scenarios/malformed-bearer.json +1 -1
  29. package/fixtures/core-v1/scenarios/metadata-overrun.json +1 -1
  30. package/fixtures/core-v1/scenarios/missing-scope.json +1 -1
  31. package/fixtures/core-v1/scenarios/project-create-equivalent-replay.json +1 -1
  32. package/fixtures/core-v1/scenarios/project-slug-conflict.json +1 -1
  33. package/fixtures/core-v1/scenarios/provider-outcome-unknown.json +1 -1
  34. package/fixtures/core-v1/scenarios/subscription-activation-replay.json +1 -1
  35. package/fixtures/core-v1/scenarios/subscription-authorization-capability-opacity.json +1 -1
  36. package/fixtures/core-v1/scenarios/subscription-cross-connection-callback-rejected.json +1 -1
  37. package/fixtures/core-v1/scenarios/subscription-expired-replay.json +1 -1
  38. package/fixtures/core-v1/scenarios/subscription-selected-connection-replay.json +1 -1
  39. package/fixtures/interaction-machine-v1/scenarios/ack-conflict.json +1 -1
  40. package/fixtures/interaction-machine-v1/scenarios/ack-cross-machine.json +1 -1
  41. package/fixtures/interaction-machine-v1/scenarios/ack-out-of-order.json +1 -1
  42. package/fixtures/interaction-machine-v1/scenarios/ack-replay.json +1 -1
  43. package/fixtures/interaction-machine-v1/scenarios/ambiguous-free-text.json +1 -1
  44. package/fixtures/interaction-machine-v1/scenarios/callback-token-opacity.json +1 -1
  45. package/fixtures/interaction-machine-v1/scenarios/credential-registration-conflict.json +1 -1
  46. package/fixtures/interaction-machine-v1/scenarios/credential-registration-replay.json +1 -1
  47. package/fixtures/interaction-machine-v1/scenarios/credential-revocation-replay.json +1 -1
  48. package/fixtures/interaction-machine-v1/scenarios/credential-rotation.json +1 -1
  49. package/fixtures/interaction-machine-v1/scenarios/first-credential-activates.json +1 -1
  50. package/fixtures/interaction-machine-v1/scenarios/interaction-duplicate.json +1 -1
  51. package/fixtures/interaction-machine-v1/scenarios/interaction-expired.json +1 -1
  52. package/fixtures/interaction-machine-v1/scenarios/interaction-mismatch.json +1 -1
  53. package/fixtures/interaction-machine-v1/scenarios/interaction-retired.json +1 -1
  54. package/fixtures/interaction-machine-v1/scenarios/interaction-stale.json +1 -1
  55. package/fixtures/interaction-machine-v1/scenarios/machine-admin-denied.json +1 -1
  56. package/fixtures/interaction-machine-v1/scenarios/machine-bound-message-create.json +1 -1
  57. package/fixtures/interaction-machine-v1/scenarios/machine-cancel-denied.json +1 -1
  58. package/fixtures/interaction-machine-v1/scenarios/machine-crash-before-ack.json +1 -1
  59. package/fixtures/interaction-machine-v1/scenarios/machine-notifier-mismatch.json +1 -1
  60. package/fixtures/interaction-machine-v1/scenarios/machine-other-message-read.json +1 -1
  61. package/fixtures/interaction-machine-v1/scenarios/machine-owned-message-read.json +1 -1
  62. package/fixtures/interaction-machine-v1/scenarios/machine-subscriber-mismatch.json +1 -1
  63. package/fixtures/interaction-machine-v1/scenarios/poll-uncommitted-cursor-rejected.json +1 -1
  64. package/fixtures/interaction-machine-v1/scenarios/project-credential-machine-endpoint-denied.json +1 -1
  65. package/fixtures/interaction-machine-v1/scenarios/quarantine-integrity-rejected.json +1 -1
  66. package/fixtures/interaction-machine-v1/scenarios/quarantine-signature-rejected.json +1 -1
  67. package/fixtures/interaction-machine-v1/scenarios/quarantine-valid.json +1 -1
  68. package/fixtures/interaction-machine-v1/scenarios/revoked-machine-credential.json +1 -1
  69. package/fixtures/recipient-experience-v1/scenarios/membership-broadcast-snapshot.json +1 -1
  70. package/fixtures/recipient-experience-v1/scenarios/preferred-binding-resolution.json +1 -1
  71. package/fixtures/recipient-experience-v1/scenarios/recipient-session-security.json +1 -1
  72. package/fixtures/recipient-experience-v1/scenarios/subscriber-initial-groups-transaction.json +1 -1
  73. package/openapi/generated/whooshbang-v1.bundle.json +599 -16
  74. package/openapi/whooshbang-v1.yaml +286 -2
  75. package/package.json +1 -1
  76. package/schemas/core-scenario.schema.json +1 -1
  77. package/schemas/events/agent-binding-degraded-v1.schema.json +1 -1
  78. package/schemas/events/interaction-expired-v1.schema.json +1 -1
  79. package/schemas/events/interaction-received-v1.schema.json +1 -1
  80. package/schemas/events/message-delivered-v1.schema.json +1 -1
  81. package/schemas/events/message-expired-v1.schema.json +1 -1
  82. package/schemas/events/message-failed-v1.schema.json +1 -1
  83. package/schemas/events/message-provider-accepted-v1.schema.json +1 -1
  84. package/schemas/events/subscription-activated-v1.schema.json +1 -1
  85. package/schemas/events/subscription-revoked-v1.schema.json +1 -1
  86. package/schemas/events/whooshbang-event-v1.schema.json +1 -1
  87. package/schemas/fixture-index.schema.json +1 -1
  88. package/schemas/interaction-machine-scenario.schema.json +1 -1
  89. package/schemas/interaction-v1.schema.json +1 -1
  90. package/schemas/machine-v1.schema.json +1 -1
  91. package/schemas/provider-capabilities-v1.schema.json +1 -1
  92. package/schemas/webhooks/webhook-delivery-case-v1.schema.json +1 -1
  93. package/schemas/webhooks/webhook-signing-vector-v1.schema.json +1 -1
  94. package/schemas/whooshbang-v1.schema.json +294 -9
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.1",
3
3
  "info": {
4
4
  "title": "WhooshBang API",
5
- "version": "3.0.0",
5
+ "version": "3.2.0",
6
6
  "description": "The V1 contract for human organization/project administration, project-scoped\nsubscription links, and asynchronous text messages. A Clerk session selects\none organization for administration. A project credential selects one organization,\nproject, and `test` or `live` environment for product operations; request\nbodies cannot override either authenticated scope.\n\nA person's OAuth grant selects one organization and carries scopes, but no\nproject. It names the project and environment it acts in through the URL\npath, on the environment-scoped form of each operation; the service checks\nthat path against the granting person's membership on every call. The flat\nforms remain credential-only and are unchanged.\n\nResource identifiers are opaque and are not authorization. Reads outside the\ncredential scope return the same `404` shape as absent resources. Provider\ncredentials, raw Telegram identity, and protected provider responses are\nnever part of this API.\n",
7
7
  "termsOfService": "https://flowxo.com/terms",
8
8
  "contact": {
@@ -52,6 +52,10 @@
52
52
  "name": "Messages",
53
53
  "description": "Asynchronous logical messages and their highest proven outcomes."
54
54
  },
55
+ {
56
+ "name": "Message categories",
57
+ "description": "Project categories and subscriber choices for those categories."
58
+ },
55
59
  {
56
60
  "name": "Machine clients",
57
61
  "description": "Project-administered, destination-bound local-client identities and one-way credential metadata."
@@ -94,6 +98,209 @@
94
98
  }
95
99
  ],
96
100
  "paths": {
101
+ "/v1/projects/{project_id}/message-categories": {
102
+ "parameters": [{ "$ref": "#/components/parameters/ProjectId" }],
103
+ "get": {
104
+ "operationId": "listMessageCategories",
105
+ "summary": "List project message categories",
106
+ "tags": ["Message categories"],
107
+ "security": [
108
+ { "clerkSession": [] },
109
+ { "bearerGrant": [] },
110
+ { "bearerCredential": [] }
111
+ ],
112
+ "x-required-scopes": ["projects:read"],
113
+ "responses": {
114
+ "200": {
115
+ "description": "Categories in this project.",
116
+ "content": {
117
+ "application/json": {
118
+ "schema": {
119
+ "$ref": "#/components/schemas/MessageCategoryCollection"
120
+ }
121
+ }
122
+ }
123
+ },
124
+ "401": { "$ref": "#/components/responses/Unauthorized" },
125
+ "403": { "$ref": "#/components/responses/Forbidden" },
126
+ "404": { "$ref": "#/components/responses/NotFound" }
127
+ }
128
+ },
129
+ "post": {
130
+ "operationId": "createMessageCategory",
131
+ "summary": "Add a project message category",
132
+ "tags": ["Message categories"],
133
+ "security": [
134
+ { "clerkSession": [] },
135
+ { "bearerGrant": [] },
136
+ { "bearerCredential": [] }
137
+ ],
138
+ "x-required-scopes": ["projects:write"],
139
+ "requestBody": {
140
+ "required": true,
141
+ "content": {
142
+ "application/json": {
143
+ "schema": {
144
+ "$ref": "#/components/schemas/CreateMessageCategoryRequest"
145
+ }
146
+ }
147
+ }
148
+ },
149
+ "responses": {
150
+ "201": {
151
+ "description": "Category added.",
152
+ "content": {
153
+ "application/json": {
154
+ "schema": { "$ref": "#/components/schemas/MessageCategory" }
155
+ }
156
+ }
157
+ },
158
+ "400": { "$ref": "#/components/responses/BadRequest" },
159
+ "401": { "$ref": "#/components/responses/Unauthorized" },
160
+ "403": { "$ref": "#/components/responses/Forbidden" },
161
+ "409": { "$ref": "#/components/responses/Conflict" }
162
+ }
163
+ }
164
+ },
165
+ "/v1/projects/{project_id}/message-categories/{category_key}": {
166
+ "parameters": [
167
+ { "$ref": "#/components/parameters/ProjectId" },
168
+ {
169
+ "name": "category_key",
170
+ "in": "path",
171
+ "required": true,
172
+ "schema": { "$ref": "#/components/schemas/MessageCategoryKey" }
173
+ }
174
+ ],
175
+ "put": {
176
+ "operationId": "updateMessageCategory",
177
+ "summary": "Change a project's message category",
178
+ "tags": ["Message categories"],
179
+ "security": [
180
+ { "clerkSession": [] },
181
+ { "bearerGrant": [] },
182
+ { "bearerCredential": [] }
183
+ ],
184
+ "x-required-scopes": ["projects:write"],
185
+ "requestBody": {
186
+ "required": true,
187
+ "content": {
188
+ "application/json": {
189
+ "schema": {
190
+ "$ref": "#/components/schemas/UpdateMessageCategoryRequest"
191
+ }
192
+ }
193
+ }
194
+ },
195
+ "responses": {
196
+ "200": {
197
+ "description": "Category updated.",
198
+ "content": {
199
+ "application/json": {
200
+ "schema": { "$ref": "#/components/schemas/MessageCategory" }
201
+ }
202
+ }
203
+ },
204
+ "400": { "$ref": "#/components/responses/BadRequest" },
205
+ "401": { "$ref": "#/components/responses/Unauthorized" },
206
+ "403": { "$ref": "#/components/responses/Forbidden" },
207
+ "404": { "$ref": "#/components/responses/NotFound" },
208
+ "409": { "$ref": "#/components/responses/Conflict" }
209
+ }
210
+ },
211
+ "delete": {
212
+ "operationId": "deleteMessageCategory",
213
+ "summary": "Remove an unused message category",
214
+ "tags": ["Message categories"],
215
+ "description": "The default category and a category still used by queued messages cannot be removed.",
216
+ "security": [
217
+ { "clerkSession": [] },
218
+ { "bearerGrant": [] },
219
+ { "bearerCredential": [] }
220
+ ],
221
+ "x-required-scopes": ["projects:write"],
222
+ "responses": {
223
+ "204": { "description": "Category removed." },
224
+ "401": { "$ref": "#/components/responses/Unauthorized" },
225
+ "403": { "$ref": "#/components/responses/Forbidden" },
226
+ "404": { "$ref": "#/components/responses/NotFound" },
227
+ "409": { "$ref": "#/components/responses/Conflict" }
228
+ }
229
+ }
230
+ },
231
+ "/v1/projects/{project_id}/environments/{environment_id}/notification-preferences/categories": {
232
+ "parameters": [
233
+ { "$ref": "#/components/parameters/ProjectId" },
234
+ { "$ref": "#/components/parameters/EnvironmentId" }
235
+ ],
236
+ "get": {
237
+ "operationId": "getOwnCategoryPreferences",
238
+ "summary": "Read my message category switches",
239
+ "tags": ["Message categories"],
240
+ "security": [{ "clerkSession": [] }],
241
+ "x-required-scopes": ["messages:read"],
242
+ "responses": {
243
+ "200": {
244
+ "description": "Category choices for the signed-in person.",
245
+ "content": {
246
+ "application/json": {
247
+ "schema": {
248
+ "$ref": "#/components/schemas/SubscriberCategoryPreferenceCollection"
249
+ }
250
+ }
251
+ }
252
+ },
253
+ "401": { "$ref": "#/components/responses/Unauthorized" },
254
+ "403": { "$ref": "#/components/responses/Forbidden" }
255
+ }
256
+ }
257
+ },
258
+ "/v1/projects/{project_id}/environments/{environment_id}/notification-preferences/categories/{category_key}": {
259
+ "parameters": [
260
+ { "$ref": "#/components/parameters/ProjectId" },
261
+ { "$ref": "#/components/parameters/EnvironmentId" },
262
+ {
263
+ "name": "category_key",
264
+ "in": "path",
265
+ "required": true,
266
+ "schema": { "$ref": "#/components/schemas/MessageCategoryKey" }
267
+ }
268
+ ],
269
+ "put": {
270
+ "operationId": "setOwnCategoryPreference",
271
+ "summary": "Switch one of my message categories on or off",
272
+ "tags": ["Message categories"],
273
+ "security": [{ "clerkSession": [] }],
274
+ "x-required-scopes": ["messages:write"],
275
+ "requestBody": {
276
+ "required": true,
277
+ "content": {
278
+ "application/json": {
279
+ "schema": {
280
+ "$ref": "#/components/schemas/SetSubscriberCategoryPreferenceRequest"
281
+ }
282
+ }
283
+ }
284
+ },
285
+ "responses": {
286
+ "200": {
287
+ "description": "Updated category choice.",
288
+ "content": {
289
+ "application/json": {
290
+ "schema": {
291
+ "$ref": "#/components/schemas/SubscriberCategoryPreference"
292
+ }
293
+ }
294
+ }
295
+ },
296
+ "400": { "$ref": "#/components/responses/BadRequest" },
297
+ "401": { "$ref": "#/components/responses/Unauthorized" },
298
+ "403": { "$ref": "#/components/responses/Forbidden" },
299
+ "404": { "$ref": "#/components/responses/NotFound" },
300
+ "409": { "$ref": "#/components/responses/Conflict" }
301
+ }
302
+ }
303
+ },
97
304
  "/v1/projects/{project_id}/environments/{environment_id}/whoami": {
98
305
  "get": {
99
306
  "operationId": "whoAmI",
@@ -407,6 +614,46 @@
407
614
  }
408
615
  }
409
616
  },
617
+ "/v1/projects/{project_id}/environments/{environment_id}/notification-preferences/bindings/{binding_id}/resume": {
618
+ "post": {
619
+ "operationId": "resumeOperatorBinding",
620
+ "x-notification-scopes": ["notifications:self", "notifications:team"],
621
+ "summary": "resumeOperatorBinding",
622
+ "description": "Resumes one of the authenticated person's own paused channels. Only the authenticated human can manage personal preferences; this never establishes consent or revives a revoked binding.",
623
+ "tags": ["Operators"],
624
+ "security": [
625
+ { "clerkSession": [] },
626
+ { "bearerCredential": [] },
627
+ { "bearerGrant": ["messages:write"] },
628
+ { "bearerGrant": ["notifications:self"] },
629
+ { "bearerGrant": ["notifications:team"] }
630
+ ],
631
+ "x-required-scopes": ["messages:write"],
632
+ "parameters": [
633
+ { "$ref": "#/components/parameters/ProjectId" },
634
+ { "$ref": "#/components/parameters/EnvironmentId" },
635
+ {
636
+ "in": "path",
637
+ "name": "binding_id",
638
+ "required": true,
639
+ "schema": { "$ref": "#/components/schemas/OpaqueIdentifier" }
640
+ }
641
+ ],
642
+ "responses": {
643
+ "200": {
644
+ "description": "Authorized result.",
645
+ "content": {
646
+ "application/json": {
647
+ "schema": { "$ref": "#/components/schemas/OperatorPreferences" }
648
+ }
649
+ }
650
+ },
651
+ "401": { "$ref": "#/components/responses/Unauthorized" },
652
+ "403": { "$ref": "#/components/responses/Forbidden" },
653
+ "404": { "$ref": "#/components/responses/NotFound" }
654
+ }
655
+ }
656
+ },
410
657
  "/v1/projects/{project_id}/environments/{environment_id}/notification-preferences/connect": {
411
658
  "post": {
412
659
  "operationId": "connectOperatorChannel",
@@ -7236,6 +7483,75 @@
7236
7483
  }
7237
7484
  }
7238
7485
  },
7486
+ "/v1/recipient-session/category-preferences": {
7487
+ "get": {
7488
+ "operationId": "getCategoryPreferences",
7489
+ "summary": "Read this subscriber's message categories and switches",
7490
+ "tags": ["Message categories"],
7491
+ "security": [{ "recipientSessionCapability": [] }],
7492
+ "responses": {
7493
+ "200": {
7494
+ "description": "Category choices for this exact subscriber.",
7495
+ "content": {
7496
+ "application/json": {
7497
+ "schema": {
7498
+ "$ref": "#/components/schemas/SubscriberCategoryPreferenceCollection"
7499
+ }
7500
+ }
7501
+ }
7502
+ },
7503
+ "401": {
7504
+ "$ref": "#/components/responses/RecipientCapabilityInvalid"
7505
+ },
7506
+ "403": { "$ref": "#/components/responses/RecipientOriginForbidden" }
7507
+ }
7508
+ }
7509
+ },
7510
+ "/v1/recipient-session/category-preferences/{category_key}": {
7511
+ "parameters": [
7512
+ {
7513
+ "name": "category_key",
7514
+ "in": "path",
7515
+ "required": true,
7516
+ "schema": { "$ref": "#/components/schemas/MessageCategoryKey" }
7517
+ }
7518
+ ],
7519
+ "put": {
7520
+ "operationId": "setCategoryPreference",
7521
+ "summary": "Switch one of this subscriber's message categories on or off",
7522
+ "tags": ["Message categories"],
7523
+ "security": [{ "recipientSessionCapability": [] }],
7524
+ "requestBody": {
7525
+ "required": true,
7526
+ "content": {
7527
+ "application/json": {
7528
+ "schema": {
7529
+ "$ref": "#/components/schemas/SetSubscriberCategoryPreferenceRequest"
7530
+ }
7531
+ }
7532
+ }
7533
+ },
7534
+ "responses": {
7535
+ "200": {
7536
+ "description": "Updated category choice.",
7537
+ "content": {
7538
+ "application/json": {
7539
+ "schema": {
7540
+ "$ref": "#/components/schemas/SubscriberCategoryPreference"
7541
+ }
7542
+ }
7543
+ }
7544
+ },
7545
+ "400": { "$ref": "#/components/responses/BadRequest" },
7546
+ "401": {
7547
+ "$ref": "#/components/responses/RecipientCapabilityInvalid"
7548
+ },
7549
+ "403": { "$ref": "#/components/responses/RecipientOriginForbidden" },
7550
+ "404": { "$ref": "#/components/responses/NotFound" },
7551
+ "409": { "$ref": "#/components/responses/Conflict" }
7552
+ }
7553
+ }
7554
+ },
7239
7555
  "/v1/recipient-session/logo/{asset_id}": {
7240
7556
  "parameters": [
7241
7557
  {
@@ -11652,6 +11968,125 @@
11652
11968
  }
11653
11969
  },
11654
11970
  "schemas": {
11971
+ "MessageCategoryKey": {
11972
+ "type": "string",
11973
+ "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
11974
+ "minLength": 1,
11975
+ "maxLength": 63
11976
+ },
11977
+ "MessageCategory": {
11978
+ "type": "object",
11979
+ "description": "One project-owned category. Its key stays stable when settings change.",
11980
+ "required": [
11981
+ "key",
11982
+ "name",
11983
+ "description",
11984
+ "purpose",
11985
+ "level",
11986
+ "can_turn_off",
11987
+ "is_default"
11988
+ ],
11989
+ "properties": {
11990
+ "key": { "$ref": "#/components/schemas/MessageCategoryKey" },
11991
+ "name": { "type": "string", "minLength": 1, "maxLength": 100 },
11992
+ "description": { "type": "string", "minLength": 1, "maxLength": 160 },
11993
+ "purpose": { "enum": ["service", "marketing"] },
11994
+ "level": { "enum": ["low", "normal", "high", "critical"] },
11995
+ "can_turn_off": { "type": "boolean" },
11996
+ "is_default": { "type": "boolean" }
11997
+ },
11998
+ "additionalProperties": false
11999
+ },
12000
+ "MessageCategoryCollection": {
12001
+ "type": "object",
12002
+ "required": ["items", "total"],
12003
+ "properties": {
12004
+ "items": {
12005
+ "type": "array",
12006
+ "maxItems": 100,
12007
+ "items": { "$ref": "#/components/schemas/MessageCategory" }
12008
+ },
12009
+ "total": { "$ref": "#/components/schemas/ListTotal" }
12010
+ },
12011
+ "additionalProperties": false
12012
+ },
12013
+ "CreateMessageCategoryRequest": {
12014
+ "type": "object",
12015
+ "required": [
12016
+ "key",
12017
+ "name",
12018
+ "description",
12019
+ "purpose",
12020
+ "level",
12021
+ "can_turn_off"
12022
+ ],
12023
+ "properties": {
12024
+ "key": { "$ref": "#/components/schemas/MessageCategoryKey" },
12025
+ "name": { "type": "string", "minLength": 1, "maxLength": 100 },
12026
+ "description": { "type": "string", "minLength": 1, "maxLength": 160 },
12027
+ "purpose": { "enum": ["service", "marketing"] },
12028
+ "level": { "enum": ["low", "normal", "high", "critical"] },
12029
+ "can_turn_off": { "type": "boolean" }
12030
+ },
12031
+ "additionalProperties": false
12032
+ },
12033
+ "UpdateMessageCategoryRequest": {
12034
+ "type": "object",
12035
+ "minProperties": 1,
12036
+ "properties": {
12037
+ "name": { "type": "string", "minLength": 1, "maxLength": 100 },
12038
+ "description": { "type": "string", "minLength": 1, "maxLength": 160 },
12039
+ "purpose": { "enum": ["service", "marketing"] },
12040
+ "level": { "enum": ["low", "normal", "high", "critical"] },
12041
+ "can_turn_off": { "type": "boolean" }
12042
+ },
12043
+ "additionalProperties": false
12044
+ },
12045
+ "SubscriberCategoryPreference": {
12046
+ "type": "object",
12047
+ "required": [
12048
+ "key",
12049
+ "name",
12050
+ "description",
12051
+ "purpose",
12052
+ "level",
12053
+ "can_turn_off",
12054
+ "is_default",
12055
+ "enabled"
12056
+ ],
12057
+ "properties": {
12058
+ "key": { "$ref": "#/components/schemas/MessageCategoryKey" },
12059
+ "name": { "type": "string", "minLength": 1, "maxLength": 100 },
12060
+ "description": { "type": "string", "minLength": 1, "maxLength": 160 },
12061
+ "purpose": { "enum": ["service", "marketing"] },
12062
+ "level": { "enum": ["low", "normal", "high", "critical"] },
12063
+ "can_turn_off": { "type": "boolean" },
12064
+ "is_default": { "type": "boolean" },
12065
+ "enabled": { "type": "boolean" }
12066
+ },
12067
+ "additionalProperties": false
12068
+ },
12069
+ "SubscriberCategoryPreferenceCollection": {
12070
+ "type": "object",
12071
+ "required": ["items", "total"],
12072
+ "properties": {
12073
+ "items": {
12074
+ "type": "array",
12075
+ "maxItems": 100,
12076
+ "items": {
12077
+ "$ref": "#/components/schemas/SubscriberCategoryPreference"
12078
+ }
12079
+ },
12080
+ "total": { "$ref": "#/components/schemas/ListTotal" }
12081
+ },
12082
+ "additionalProperties": false
12083
+ },
12084
+ "SetSubscriberCategoryPreferenceRequest": {
12085
+ "type": "object",
12086
+ "required": ["enabled"],
12087
+ "properties": { "enabled": { "type": "boolean" } },
12088
+ "additionalProperties": false
12089
+ },
11655
12090
  "WhoAmI": {
11656
12091
  "type": "object",
11657
12092
  "description": "Current principal, authorized context, safe channel readiness and deterministic personal me identity. Contains no email or provider destination.",
@@ -11808,6 +12243,10 @@
11808
12243
  "enum": ["service", "marketing"],
11809
12244
  "default": "service",
11810
12245
  "description": "Omitted means service. Marketing refuses service-only bindings before charging and at every delayed dispatch."
12246
+ },
12247
+ "category_key": {
12248
+ "$ref": "#/components/schemas/MessageCategoryKey",
12249
+ "description": "Optional project category. Omitted service sends use Updates; omitted marketing sends use Marketing."
11811
12250
  }
11812
12251
  },
11813
12252
  "additionalProperties": false,
@@ -11897,6 +12336,29 @@
11897
12336
  "settings": {
11898
12337
  "$ref": "#/components/schemas/SubscriberChannelSettings"
11899
12338
  },
12339
+ "accounts": {
12340
+ "type": "array",
12341
+ "maxItems": 10,
12342
+ "description": "Who each of your connected channels reaches, as the channel named you when you connected: a Telegram or Slack name and username, by binding. Kept only on the Standard privacy setting, so it is empty otherwise.",
12343
+ "items": {
12344
+ "type": "object",
12345
+ "required": ["binding_id"],
12346
+ "properties": {
12347
+ "binding_id": {
12348
+ "type": "string",
12349
+ "minLength": 1,
12350
+ "maxLength": 128
12351
+ },
12352
+ "name": { "type": "string", "minLength": 1, "maxLength": 254 },
12353
+ "username": {
12354
+ "type": "string",
12355
+ "minLength": 1,
12356
+ "maxLength": 80
12357
+ }
12358
+ },
12359
+ "additionalProperties": false
12360
+ }
12361
+ },
11900
12362
  "connections": {
11901
12363
  "type": "array",
11902
12364
  "items": {
@@ -11957,7 +12419,7 @@
11957
12419
  },
11958
12420
  "MessageListEntry": {
11959
12421
  "type": "object",
11960
- "description": "Content-free accepted-message summary. Includes neither the notification nor an interaction answer.",
12422
+ "description": "Accepted-message summary: a one-line snippet of what the message said, and no interaction answer.",
11961
12423
  "required": [
11962
12424
  "accepted_at",
11963
12425
  "diagnostic_id",
@@ -11989,6 +12451,10 @@
11989
12451
  "environment": { "$ref": "#/components/schemas/Environment" },
11990
12452
  "id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
11991
12453
  "notifier_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
12454
+ "category": {
12455
+ "$ref": "#/components/schemas/MessageCategorySnapshot",
12456
+ "description": "Category as accepted. Absent on messages accepted before categories existed."
12457
+ },
11992
12458
  "state": { "$ref": "#/components/schemas/MessageState" },
11993
12459
  "subscriber_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
11994
12460
  "updated_at": { "$ref": "#/components/schemas/Timestamp" },
@@ -12005,6 +12471,16 @@
12005
12471
  "classified_error": {
12006
12472
  "$ref": "#/components/schemas/ClassifiedError"
12007
12473
  },
12474
+ "content_snippet": {
12475
+ "type": "string",
12476
+ "minLength": 1,
12477
+ "maxLength": 121,
12478
+ "description": "The start of what the message said, on one line: its text, or the title of block content, cut to 120 characters with an ellipsis. Absent when `content_retained` is false."
12479
+ },
12480
+ "content_retained": {
12481
+ "type": "boolean",
12482
+ "description": "False once the message's content has been deleted at the end of its retention period (7 days, or 90 on paid plans, after it finished). The rest of the entry is kept."
12483
+ },
12008
12484
  "channel": { "$ref": "#/components/schemas/Channel" }
12009
12485
  },
12010
12486
  "additionalProperties": false
@@ -16434,6 +16910,10 @@
16434
16910
  "enum": ["service", "marketing"],
16435
16911
  "default": "service",
16436
16912
  "description": "Omitted means service. Marketing refuses service-only bindings before charging and at every delayed dispatch."
16913
+ },
16914
+ "category_key": {
16915
+ "$ref": "#/components/schemas/MessageCategoryKey",
16916
+ "description": "Optional project category. Omitted service sends use Updates; omitted marketing sends use Marketing."
16437
16917
  }
16438
16918
  },
16439
16919
  "allOf": [
@@ -16547,6 +17027,10 @@
16547
17027
  "description": "What to call the subscriber, resolved as on the subscriber's `name`: your display name, otherwise the channel's own name where the privacy setting keeps it. Absent when there is neither."
16548
17028
  },
16549
17029
  "notifier_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
17030
+ "category": {
17031
+ "$ref": "#/components/schemas/MessageCategorySnapshot",
17032
+ "description": "Category as accepted. Absent on messages accepted before categories existed."
17033
+ },
16550
17034
  "binding": { "$ref": "#/components/schemas/BindingSummary" },
16551
17035
  "binding_resolution": {
16552
17036
  "type": "string",
@@ -16789,7 +17273,6 @@
16789
17273
  "type": "object",
16790
17274
  "required": ["classified_error"],
16791
17275
  "properties": {
16792
- "attempt_count": { "type": "integer", "minimum": 1 },
16793
17276
  "classified_error": {
16794
17277
  "allOf": [
16795
17278
  { "$ref": "#/components/schemas/ClassifiedError" },
@@ -16799,7 +17282,8 @@
16799
17282
  "code": {
16800
17283
  "enum": [
16801
17284
  "provider_terminal",
16802
- "not_consented_for_marketing"
17285
+ "not_consented_for_marketing",
17286
+ "category_turned_off"
16803
17287
  ]
16804
17288
  },
16805
17289
  "retryable": { "const": false }
@@ -16807,7 +17291,32 @@
16807
17291
  }
16808
17292
  ]
16809
17293
  }
16810
- }
17294
+ },
17295
+ "allOf": [
17296
+ {
17297
+ "if": {
17298
+ "type": "object",
17299
+ "properties": {
17300
+ "classified_error": {
17301
+ "type": "object",
17302
+ "properties": {
17303
+ "code": { "const": "provider_terminal" }
17304
+ },
17305
+ "required": ["code"]
17306
+ }
17307
+ },
17308
+ "required": ["classified_error"]
17309
+ },
17310
+ "then": {
17311
+ "properties": {
17312
+ "attempt_count": {
17313
+ "type": "integer",
17314
+ "minimum": 1
17315
+ }
17316
+ }
17317
+ }
17318
+ }
17319
+ ]
16811
17320
  }
16812
17321
  ]
16813
17322
  }
@@ -17310,6 +17819,54 @@
17310
17819
  "code": { "const": "not_consented_for_marketing" },
17311
17820
  "retryable": { "const": false }
17312
17821
  }
17822
+ },
17823
+ {
17824
+ "type": "object",
17825
+ "properties": {
17826
+ "type": {
17827
+ "const": "https://whooshbang.flowxo.com/problems/category_not_found"
17828
+ },
17829
+ "title": { "const": "Message category not found" },
17830
+ "status": { "const": 404 },
17831
+ "code": { "const": "category_not_found" },
17832
+ "retryable": { "const": false }
17833
+ }
17834
+ },
17835
+ {
17836
+ "type": "object",
17837
+ "properties": {
17838
+ "type": {
17839
+ "const": "https://whooshbang.flowxo.com/problems/category_purpose_mismatch"
17840
+ },
17841
+ "title": { "const": "Purpose and category do not match" },
17842
+ "status": { "const": 422 },
17843
+ "code": { "const": "category_purpose_mismatch" },
17844
+ "retryable": { "const": false }
17845
+ }
17846
+ },
17847
+ {
17848
+ "type": "object",
17849
+ "properties": {
17850
+ "type": {
17851
+ "const": "https://whooshbang.flowxo.com/problems/category_turned_off"
17852
+ },
17853
+ "title": { "const": "Message category turned off" },
17854
+ "status": { "const": 422 },
17855
+ "code": { "const": "category_turned_off" },
17856
+ "retryable": { "const": false }
17857
+ }
17858
+ },
17859
+ {
17860
+ "type": "object",
17861
+ "properties": {
17862
+ "type": {
17863
+ "const": "https://whooshbang.flowxo.com/problems/category_conflict"
17864
+ },
17865
+ "title": { "const": "Message category conflict" },
17866
+ "status": { "const": 409 },
17867
+ "code": { "const": "category_conflict" },
17868
+ "retryable": { "const": false }
17869
+ }
17313
17870
  }
17314
17871
  ],
17315
17872
  "additionalProperties": false
@@ -18716,6 +19273,11 @@
18716
19273
  "maxLength": 254,
18717
19274
  "description": "What to call this subscriber: `display_name` when your application sent one, otherwise the name their channel gave when they connected (a Telegram username, a Slack display name, an email address), which is kept only on the standard privacy setting. Absent when there is neither; show `subscriber_id` then."
18718
19275
  },
19276
+ "team_member": {
19277
+ "type": "boolean",
19278
+ "const": true,
19279
+ "description": "Present when this subscriber is someone on your WhooshBang team, messaged through the operators group. `name` is then their WhooshBang account name. They manage their own channels in notification preferences, so the subscriber can be read but not renamed, paused or erased."
19280
+ },
18719
19281
  "locale": { "$ref": "#/components/schemas/RecipientLanguage" },
18720
19282
  "time_zone": { "$ref": "#/components/schemas/TimeZone" },
18721
19283
  "channels": {
@@ -19158,6 +19720,10 @@
19158
19720
  "enum": ["service", "marketing"],
19159
19721
  "default": "service",
19160
19722
  "description": "Omitted means service. Marketing refuses service-only bindings before charging and at every delayed dispatch."
19723
+ },
19724
+ "category_key": {
19725
+ "$ref": "#/components/schemas/MessageCategoryKey",
19726
+ "description": "Optional project category. Omitted service sends use Updates; omitted marketing sends use Marketing."
19161
19727
  }
19162
19728
  },
19163
19729
  "additionalProperties": false,
@@ -19230,10 +19796,10 @@
19230
19796
  },
19231
19797
  "additionalProperties": false
19232
19798
  },
19233
- "Channel": {
19234
- "type": "string",
19235
- "enum": ["telegram", "slack", "email"],
19236
- "description": "A provider-neutral delivery channel. Adding a member makes it representable; it does not claim that every deployment has registered an executable adapter."
19799
+ "ListTotal": {
19800
+ "type": "integer",
19801
+ "minimum": 0,
19802
+ "description": "How many records the list holds in total, not only on this page. Counted while the page was read, so a write landing between the count and the page can leave it a record or two out of step with `items`. That is noise on an inspection surface; it never changes which records a page addresses."
19237
19803
  },
19238
19804
  "ProblemCode": {
19239
19805
  "type": "string",
@@ -19270,7 +19836,11 @@
19270
19836
  "provider_terminal",
19271
19837
  "provider_outcome_unknown",
19272
19838
  "internal_error",
19273
- "not_consented_for_marketing"
19839
+ "not_consented_for_marketing",
19840
+ "category_purpose_mismatch",
19841
+ "category_turned_off",
19842
+ "category_conflict",
19843
+ "category_not_found"
19274
19844
  ]
19275
19845
  },
19276
19846
  "ProblemFieldError": {
@@ -19442,10 +20012,10 @@
19442
20012
  }
19443
20013
  ]
19444
20014
  },
19445
- "ListTotal": {
19446
- "type": "integer",
19447
- "minimum": 0,
19448
- "description": "How many records the list holds in total, not only on this page. Counted while the page was read, so a write landing between the count and the page can leave it a record or two out of step with `items`. That is noise on an inspection surface; it never changes which records a page addresses."
20015
+ "Channel": {
20016
+ "type": "string",
20017
+ "enum": ["telegram", "slack", "email"],
20018
+ "description": "A provider-neutral delivery channel. Adding a member makes it representable; it does not claim that every deployment has registered an executable adapter."
19449
20019
  },
19450
20020
  "StructuredTextSection": {
19451
20021
  "type": "object",
@@ -19883,6 +20453,19 @@
19883
20453
  "enum": ["test", "live"],
19884
20454
  "description": "The single environment selected by the authenticated credential."
19885
20455
  },
20456
+ "MessageCategorySnapshot": {
20457
+ "type": "object",
20458
+ "description": "The category settings recorded when the message was accepted.",
20459
+ "required": ["key", "name", "purpose", "level", "can_turn_off"],
20460
+ "properties": {
20461
+ "key": { "$ref": "#/components/schemas/MessageCategoryKey" },
20462
+ "name": { "type": "string", "minLength": 1, "maxLength": 100 },
20463
+ "purpose": { "enum": ["service", "marketing"] },
20464
+ "level": { "enum": ["low", "normal", "high", "critical"] },
20465
+ "can_turn_off": { "type": "boolean" }
20466
+ },
20467
+ "additionalProperties": false
20468
+ },
19886
20469
  "EmailAddress": {
19887
20470
  "type": "string",
19888
20471
  "minLength": 3,
@@ -21485,7 +22068,7 @@
21485
22068
  "provider-capabilities-v1.schema": {
21486
22069
  "$schema": "https://json-schema.org/draft/2020-12/schema",
21487
22070
  "$id": "https://whooshbang.flowxo.com/schemas/provider-capabilities-v1.schema.json",
21488
- "x-contract-version": "3.0.0",
22071
+ "x-contract-version": "3.2.0",
21489
22072
  "title": "ProviderCapabilitiesV1",
21490
22073
  "description": "Provider-neutral, renderer-facing delivery and interaction capabilities with explicit degradation.",
21491
22074
  "type": "object",
@@ -22287,5 +22870,5 @@
22287
22870
  }
22288
22871
  }
22289
22872
  },
22290
- "x-contract-version": "3.0.0"
22873
+ "x-contract-version": "3.2.0"
22291
22874
  }