@whooshbang/contracts 2.3.0 → 2.5.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 (98) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/contract-manifest.json +35 -32
  3. package/dist/generated/artifact-index.d.ts +34 -34
  4. package/dist/generated/artifact-index.js +35 -32
  5. package/dist/generated/artifact-index.js.map +1 -1
  6. package/dist/generated/validators-standalone.d.ts +439 -400
  7. package/dist/generated/validators-standalone.js +6840 -1621
  8. package/dist/generated/validators-standalone.js.map +1 -1
  9. package/dist/generated/validators.d.ts +4 -1
  10. package/dist/generated/validators.js +4 -1
  11. package/dist/generated/validators.js.map +1 -1
  12. package/dist/generated/whooshbang-v1.d.ts +112 -1
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.js +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/fixtures/core-v1/fixture-index.json +24 -1
  17. package/fixtures/core-v1/requests/redeem-billing-promotion.json +3 -0
  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/core-v1/success/billing-promotion.json +14 -0
  40. package/fixtures/core-v1/success/billing-promotions.json +64 -0
  41. package/fixtures/core-v1/success/credit-purchase.json +2 -0
  42. package/fixtures/core-v1/success/subscription-change.json +7 -0
  43. package/fixtures/interaction-machine-v1/scenarios/ack-conflict.json +1 -1
  44. package/fixtures/interaction-machine-v1/scenarios/ack-cross-machine.json +1 -1
  45. package/fixtures/interaction-machine-v1/scenarios/ack-out-of-order.json +1 -1
  46. package/fixtures/interaction-machine-v1/scenarios/ack-replay.json +1 -1
  47. package/fixtures/interaction-machine-v1/scenarios/ambiguous-free-text.json +1 -1
  48. package/fixtures/interaction-machine-v1/scenarios/callback-token-opacity.json +1 -1
  49. package/fixtures/interaction-machine-v1/scenarios/credential-registration-conflict.json +1 -1
  50. package/fixtures/interaction-machine-v1/scenarios/credential-registration-replay.json +1 -1
  51. package/fixtures/interaction-machine-v1/scenarios/credential-revocation-replay.json +1 -1
  52. package/fixtures/interaction-machine-v1/scenarios/credential-rotation.json +1 -1
  53. package/fixtures/interaction-machine-v1/scenarios/first-credential-activates.json +1 -1
  54. package/fixtures/interaction-machine-v1/scenarios/interaction-duplicate.json +1 -1
  55. package/fixtures/interaction-machine-v1/scenarios/interaction-expired.json +1 -1
  56. package/fixtures/interaction-machine-v1/scenarios/interaction-mismatch.json +1 -1
  57. package/fixtures/interaction-machine-v1/scenarios/interaction-retired.json +1 -1
  58. package/fixtures/interaction-machine-v1/scenarios/interaction-stale.json +1 -1
  59. package/fixtures/interaction-machine-v1/scenarios/machine-admin-denied.json +1 -1
  60. package/fixtures/interaction-machine-v1/scenarios/machine-bound-message-create.json +1 -1
  61. package/fixtures/interaction-machine-v1/scenarios/machine-cancel-denied.json +1 -1
  62. package/fixtures/interaction-machine-v1/scenarios/machine-crash-before-ack.json +1 -1
  63. package/fixtures/interaction-machine-v1/scenarios/machine-notifier-mismatch.json +1 -1
  64. package/fixtures/interaction-machine-v1/scenarios/machine-other-message-read.json +1 -1
  65. package/fixtures/interaction-machine-v1/scenarios/machine-owned-message-read.json +1 -1
  66. package/fixtures/interaction-machine-v1/scenarios/machine-subscriber-mismatch.json +1 -1
  67. package/fixtures/interaction-machine-v1/scenarios/poll-uncommitted-cursor-rejected.json +1 -1
  68. package/fixtures/interaction-machine-v1/scenarios/project-credential-machine-endpoint-denied.json +1 -1
  69. package/fixtures/interaction-machine-v1/scenarios/quarantine-integrity-rejected.json +1 -1
  70. package/fixtures/interaction-machine-v1/scenarios/quarantine-signature-rejected.json +1 -1
  71. package/fixtures/interaction-machine-v1/scenarios/quarantine-valid.json +1 -1
  72. package/fixtures/interaction-machine-v1/scenarios/revoked-machine-credential.json +1 -1
  73. package/fixtures/recipient-experience-v1/scenarios/membership-broadcast-snapshot.json +1 -1
  74. package/fixtures/recipient-experience-v1/scenarios/preferred-binding-resolution.json +1 -1
  75. package/fixtures/recipient-experience-v1/scenarios/recipient-session-security.json +1 -1
  76. package/fixtures/recipient-experience-v1/scenarios/subscriber-initial-groups-transaction.json +1 -1
  77. package/openapi/generated/whooshbang-v1.bundle.json +423 -11
  78. package/openapi/whooshbang-v1.yaml +124 -2
  79. package/package.json +1 -1
  80. package/schemas/core-scenario.schema.json +1 -1
  81. package/schemas/events/agent-binding-degraded-v1.schema.json +1 -1
  82. package/schemas/events/interaction-expired-v1.schema.json +1 -1
  83. package/schemas/events/interaction-received-v1.schema.json +1 -1
  84. package/schemas/events/message-delivered-v1.schema.json +1 -1
  85. package/schemas/events/message-expired-v1.schema.json +1 -1
  86. package/schemas/events/message-failed-v1.schema.json +1 -1
  87. package/schemas/events/message-provider-accepted-v1.schema.json +1 -1
  88. package/schemas/events/subscription-activated-v1.schema.json +1 -1
  89. package/schemas/events/subscription-revoked-v1.schema.json +1 -1
  90. package/schemas/events/whooshbang-event-v1.schema.json +1 -1
  91. package/schemas/fixture-index.schema.json +1 -1
  92. package/schemas/interaction-machine-scenario.schema.json +1 -1
  93. package/schemas/interaction-v1.schema.json +1 -1
  94. package/schemas/machine-v1.schema.json +1 -1
  95. package/schemas/provider-capabilities-v1.schema.json +1 -1
  96. package/schemas/webhooks/webhook-delivery-case-v1.schema.json +1 -1
  97. package/schemas/webhooks/webhook-signing-vector-v1.schema.json +1 -1
  98. package/schemas/whooshbang-v1.schema.json +374 -3
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.1",
3
3
  "info": {
4
4
  "title": "WhooshBang API",
5
- "version": "2.3.0",
5
+ "version": "2.5.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": {
@@ -97,6 +97,7 @@
97
97
  "/v1/projects/{project_id}/environments/{environment_id}/whoami": {
98
98
  "get": {
99
99
  "operationId": "whoAmI",
100
+ "x-notification-scopes": ["notifications:self", "notifications:team"],
100
101
  "summary": "Identify the authenticated caller",
101
102
  "description": "Personal login identifies me without a recipient question. Shared or service credentials report their actual principal and require an explicit recipient. No account owner or credential creator is inferred.",
102
103
  "tags": ["Operators"],
@@ -105,6 +106,8 @@
105
106
  { "bearerCredential": [] },
106
107
  { "bearerOrganizationCredential": [] },
107
108
  { "bearerGrant": ["messages:read"] },
109
+ { "bearerGrant": ["notifications:self"] },
110
+ { "bearerGrant": ["notifications:team"] },
108
111
  { "machineCredential": [] }
109
112
  ],
110
113
  "x-required-scopes": ["messages:read"],
@@ -129,13 +132,15 @@
129
132
  "/v1/projects/{project_id}/environments/{environment_id}/operators": {
130
133
  "get": {
131
134
  "operationId": "listOperators",
135
+ "x-notification-scopes": ["notifications:team"],
132
136
  "summary": "listOperators",
133
137
  "description": "Membership-derived operator messaging. Only the authenticated human can manage personal preferences.",
134
138
  "tags": ["Operators"],
135
139
  "security": [
136
140
  { "clerkSession": [] },
137
141
  { "bearerCredential": [] },
138
- { "bearerGrant": ["messages:read"] }
142
+ { "bearerGrant": ["messages:read"] },
143
+ { "bearerGrant": ["notifications:team"] }
139
144
  ],
140
145
  "x-required-scopes": ["messages:read"],
141
146
  "parameters": [
@@ -162,13 +167,15 @@
162
167
  "/v1/projects/{project_id}/environments/{environment_id}/operators/resolve": {
163
168
  "get": {
164
169
  "operationId": "resolveOperator",
170
+ "x-notification-scopes": ["notifications:team"],
165
171
  "summary": "resolveOperator",
166
172
  "description": "Membership-derived operator messaging. Only the authenticated human can manage personal preferences.",
167
173
  "tags": ["Operators"],
168
174
  "security": [
169
175
  { "clerkSession": [] },
170
176
  { "bearerCredential": [] },
171
- { "bearerGrant": ["messages:read"] }
177
+ { "bearerGrant": ["messages:read"] },
178
+ { "bearerGrant": ["notifications:team"] }
172
179
  ],
173
180
  "x-required-scopes": ["messages:read"],
174
181
  "parameters": [
@@ -198,6 +205,7 @@
198
205
  "/v1/projects/{project_id}/environments/{environment_id}/operator-messages": {
199
206
  "post": {
200
207
  "operationId": "sendOperatorMessage",
208
+ "x-notification-scopes": ["notifications:self", "notifications:team"],
201
209
  "summary": "sendOperatorMessage",
202
210
  "description": "Message a current teammate by name or operator ID. Personal callers may omit recipient or use me. Shared credentials must name a recipient. Runtime machine credentials report recipient_required for omitted or me; named operator sends are forbidden because runtime authority remains subscriber-bound.",
203
211
  "tags": ["Operators"],
@@ -206,7 +214,9 @@
206
214
  { "bearerCredential": [] },
207
215
  { "bearerOrganizationCredential": [] },
208
216
  { "machineCredential": [] },
209
- { "bearerGrant": ["messages:write"] }
217
+ { "bearerGrant": ["messages:write"] },
218
+ { "bearerGrant": ["notifications:self"] },
219
+ { "bearerGrant": ["notifications:team"] }
210
220
  ],
211
221
  "x-required-scopes": ["messages:write"],
212
222
  "parameters": [
@@ -243,13 +253,15 @@
243
253
  "/v1/projects/{project_id}/environments/{environment_id}/operators/broadcasts": {
244
254
  "post": {
245
255
  "operationId": "broadcastOperators",
256
+ "x-notification-scopes": ["notifications:team"],
246
257
  "summary": "broadcastOperators",
247
258
  "description": "Membership-derived operator messaging. Only the authenticated human can manage personal preferences.",
248
259
  "tags": ["Operators"],
249
260
  "security": [
250
261
  { "clerkSession": [] },
251
262
  { "bearerCredential": [] },
252
- { "bearerGrant": ["messages:write"] }
263
+ { "bearerGrant": ["messages:write"] },
264
+ { "bearerGrant": ["notifications:team"] }
253
265
  ],
254
266
  "x-required-scopes": ["messages:write"],
255
267
  "parameters": [
@@ -284,13 +296,15 @@
284
296
  "/v1/projects/{project_id}/environments/{environment_id}/operators/broadcasts/{broadcast_id}": {
285
297
  "get": {
286
298
  "operationId": "getOperatorBroadcast",
299
+ "x-notification-scopes": ["notifications:team"],
287
300
  "summary": "getOperatorBroadcast",
288
301
  "description": "Membership-derived operator messaging. Only the authenticated human can manage personal preferences.",
289
302
  "tags": ["Operators"],
290
303
  "security": [
291
304
  { "clerkSession": [] },
292
305
  { "bearerCredential": [] },
293
- { "bearerGrant": ["messages:read"] }
306
+ { "bearerGrant": ["messages:read"] },
307
+ { "bearerGrant": ["notifications:team"] }
294
308
  ],
295
309
  "x-required-scopes": ["messages:read"],
296
310
  "parameters": [
@@ -320,13 +334,16 @@
320
334
  "/v1/projects/{project_id}/environments/{environment_id}/notification-preferences": {
321
335
  "get": {
322
336
  "operationId": "getOperatorPreferences",
337
+ "x-notification-scopes": ["notifications:self", "notifications:team"],
323
338
  "summary": "getOperatorPreferences",
324
339
  "description": "Membership-derived operator messaging. Only the authenticated human can manage personal preferences.",
325
340
  "tags": ["Operators"],
326
341
  "security": [
327
342
  { "clerkSession": [] },
328
343
  { "bearerCredential": [] },
329
- { "bearerGrant": ["messages:read"] }
344
+ { "bearerGrant": ["messages:read"] },
345
+ { "bearerGrant": ["notifications:self"] },
346
+ { "bearerGrant": ["notifications:team"] }
330
347
  ],
331
348
  "x-required-scopes": ["messages:read"],
332
349
  "parameters": [
@@ -350,13 +367,16 @@
350
367
  "/v1/projects/{project_id}/environments/{environment_id}/notification-preferences/preferred-binding": {
351
368
  "put": {
352
369
  "operationId": "setOperatorPreferredBinding",
370
+ "x-notification-scopes": ["notifications:self", "notifications:team"],
353
371
  "summary": "setOperatorPreferredBinding",
354
372
  "description": "Membership-derived operator messaging. Only the authenticated human can manage personal preferences.",
355
373
  "tags": ["Operators"],
356
374
  "security": [
357
375
  { "clerkSession": [] },
358
376
  { "bearerCredential": [] },
359
- { "bearerGrant": ["messages:write"] }
377
+ { "bearerGrant": ["messages:write"] },
378
+ { "bearerGrant": ["notifications:self"] },
379
+ { "bearerGrant": ["notifications:team"] }
360
380
  ],
361
381
  "x-required-scopes": ["messages:write"],
362
382
  "parameters": [
@@ -390,13 +410,16 @@
390
410
  "/v1/projects/{project_id}/environments/{environment_id}/notification-preferences/connect": {
391
411
  "post": {
392
412
  "operationId": "connectOperatorChannel",
413
+ "x-notification-scopes": ["notifications:self", "notifications:team"],
393
414
  "summary": "connectOperatorChannel",
394
415
  "description": "Membership-derived operator messaging. Only the authenticated human can manage personal preferences.",
395
416
  "tags": ["Operators"],
396
417
  "security": [
397
418
  { "clerkSession": [] },
398
419
  { "bearerCredential": [] },
399
- { "bearerGrant": ["messages:write"] }
420
+ { "bearerGrant": ["messages:write"] },
421
+ { "bearerGrant": ["notifications:self"] },
422
+ { "bearerGrant": ["notifications:team"] }
400
423
  ],
401
424
  "x-required-scopes": ["messages:write"],
402
425
  "parameters": [
@@ -501,6 +524,92 @@
501
524
  }
502
525
  }
503
526
  },
527
+ "/v1/billing/promotions": {
528
+ "get": {
529
+ "operationId": "getBillingPromotions",
530
+ "summary": "Read the organization's promotions",
531
+ "description": "Organization administrators can read every promotion the organization holds: rewards WhooshBang gave it\nand promotion codes it redeemed, with what remains and when each ends. Promotional value has no cash\nvalue, is never refunded and cannot move to another organization.\n",
532
+ "tags": ["Organization"],
533
+ "security": [{ "clerkSession": [] }, { "bearerGrant": [] }],
534
+ "x-required-scopes": ["billing:read"],
535
+ "x-organization-membership": "required",
536
+ "responses": {
537
+ "200": {
538
+ "description": "The organization's promotions, newest first.",
539
+ "headers": {
540
+ "WhooshBang-Diagnostic-Id": {
541
+ "$ref": "#/components/headers/DiagnosticId"
542
+ }
543
+ },
544
+ "content": {
545
+ "application/json": {
546
+ "schema": { "$ref": "#/components/schemas/BillingPromotions" }
547
+ }
548
+ }
549
+ },
550
+ "401": { "$ref": "#/components/responses/Unauthorized" },
551
+ "403": { "$ref": "#/components/responses/Forbidden" },
552
+ "404": { "$ref": "#/components/responses/NotFound" },
553
+ "429": { "$ref": "#/components/responses/RateLimited" }
554
+ }
555
+ }
556
+ },
557
+ "/v1/billing/promotions/redeem": {
558
+ "post": {
559
+ "operationId": "redeemBillingPromotion",
560
+ "summary": "Redeem a promotion code",
561
+ "description": "Organization administrators can redeem a promotion code for their own organization. Nothing is charged.\nEvery code that cannot be redeemed (mistyped, ended, fully used, not offered to this organization, or not\ncombinable with a promotion it already holds) receives the same `resource_not_found` answer. Repeated\nrefusals are rate limited per organization; wait for `Retry-After`. Reuse the same idempotency key after\nan uncertain response: it can never redeem twice.\n",
562
+ "tags": ["Organization"],
563
+ "security": [{ "clerkSession": [] }, { "bearerGrant": [] }],
564
+ "x-required-scopes": ["billing:write"],
565
+ "x-organization-membership": "required",
566
+ "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
567
+ "requestBody": {
568
+ "required": true,
569
+ "content": {
570
+ "application/json": {
571
+ "schema": {
572
+ "$ref": "#/components/schemas/RedeemBillingPromotionRequest"
573
+ }
574
+ }
575
+ }
576
+ },
577
+ "responses": {
578
+ "200": {
579
+ "description": "The same promotion, replayed for a repeated idempotency key.",
580
+ "headers": {
581
+ "WhooshBang-Diagnostic-Id": {
582
+ "$ref": "#/components/headers/DiagnosticId"
583
+ }
584
+ },
585
+ "content": {
586
+ "application/json": {
587
+ "schema": { "$ref": "#/components/schemas/BillingPromotion" }
588
+ }
589
+ }
590
+ },
591
+ "201": {
592
+ "description": "The promotion the code gave the organization.",
593
+ "headers": {
594
+ "WhooshBang-Diagnostic-Id": {
595
+ "$ref": "#/components/headers/DiagnosticId"
596
+ }
597
+ },
598
+ "content": {
599
+ "application/json": {
600
+ "schema": { "$ref": "#/components/schemas/BillingPromotion" }
601
+ }
602
+ }
603
+ },
604
+ "400": { "$ref": "#/components/responses/BadRequest" },
605
+ "401": { "$ref": "#/components/responses/Unauthorized" },
606
+ "403": { "$ref": "#/components/responses/Forbidden" },
607
+ "404": { "$ref": "#/components/responses/NotFound" },
608
+ "409": { "$ref": "#/components/responses/Conflict" },
609
+ "429": { "$ref": "#/components/responses/RateLimited" }
610
+ }
611
+ }
612
+ },
504
613
  "/v1/billing/auto-top-up": {
505
614
  "get": {
506
615
  "operationId": "getAutoTopUpSettings",
@@ -2190,12 +2299,15 @@
2190
2299
  "/v1/projects": {
2191
2300
  "get": {
2192
2301
  "operationId": "listProjects",
2302
+ "x-notification-scopes": ["notifications:self", "notifications:team"],
2193
2303
  "summary": "List projects in the current organization",
2194
2304
  "description": "Returns only projects owned by the organization selected by the verified\nClerk organization session. Every project includes its complete test/live\nenvironment and default-notifier bootstrap.\n",
2195
2305
  "tags": ["Projects"],
2196
2306
  "security": [
2197
2307
  { "clerkSession": [] },
2198
2308
  { "bearerGrant": [] },
2309
+ { "bearerGrant": ["notifications:self"] },
2310
+ { "bearerGrant": ["notifications:team"] },
2199
2311
  { "bearerOrganizationCredential": [] }
2200
2312
  ],
2201
2313
  "x-required-scopes": ["projects:read"],
@@ -2379,12 +2491,15 @@
2379
2491
  "parameters": [{ "$ref": "#/components/parameters/ProjectId" }],
2380
2492
  "get": {
2381
2493
  "operationId": "getProject",
2494
+ "x-notification-scopes": ["notifications:self", "notifications:team"],
2382
2495
  "summary": "Read one project in the current organization",
2383
2496
  "description": "Returns the complete project bootstrap. An absent project and a project\noutside the authenticated organization use the same non-enumerating `404`.\n",
2384
2497
  "tags": ["Projects"],
2385
2498
  "security": [
2386
2499
  { "clerkSession": [] },
2387
2500
  { "bearerGrant": [] },
2501
+ { "bearerGrant": ["notifications:self"] },
2502
+ { "bearerGrant": ["notifications:team"] },
2388
2503
  { "bearerOrganizationCredential": [] }
2389
2504
  ],
2390
2505
  "x-required-scopes": ["projects:read"],
@@ -12836,6 +12951,202 @@
12836
12951
  "additionalProperties": false,
12837
12952
  "description": "Replace the requested active project selection with owned, active projects within current paid capacity. An empty selection releases all slots."
12838
12953
  },
12954
+ "BillingPromotions": {
12955
+ "type": "object",
12956
+ "required": [
12957
+ "promotional_credit_usd",
12958
+ "free_shared_emails",
12959
+ "branding_removed_until",
12960
+ "promotions"
12961
+ ],
12962
+ "properties": {
12963
+ "promotional_credit_usd": {
12964
+ "type": "string",
12965
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
12966
+ "maxLength": 40,
12967
+ "description": "Unexpired promotional message credit available now."
12968
+ },
12969
+ "free_shared_emails": {
12970
+ "type": "integer",
12971
+ "minimum": 0,
12972
+ "maximum": 2147483647,
12973
+ "description": "Unexpired free shared emails available now."
12974
+ },
12975
+ "branding_removed_until": {
12976
+ "anyOf": [
12977
+ { "type": "string", "format": "date-time" },
12978
+ { "type": "null" }
12979
+ ],
12980
+ "description": "When the latest active branding promotion ends, if one is active."
12981
+ },
12982
+ "promotions": {
12983
+ "type": "array",
12984
+ "items": { "$ref": "#/components/schemas/BillingPromotion" },
12985
+ "maxItems": 200
12986
+ }
12987
+ },
12988
+ "additionalProperties": false,
12989
+ "description": "An organization's promotions: rewards WhooshBang gave it and promotion codes it redeemed, newest first."
12990
+ },
12991
+ "BillingPromotion": {
12992
+ "type": "object",
12993
+ "required": [
12994
+ "id",
12995
+ "source",
12996
+ "name",
12997
+ "status",
12998
+ "starts_at",
12999
+ "ends_at",
13000
+ "ended_at",
13001
+ "benefit"
13002
+ ],
13003
+ "properties": {
13004
+ "id": { "type": "string", "pattern": "^grn_[0-9a-z]{26}$" },
13005
+ "source": {
13006
+ "type": "string",
13007
+ "enum": ["whooshbang", "code"],
13008
+ "description": "`whooshbang` for a reward WhooshBang gave the organization directly; `code` for a promotion code the organization redeemed."
13009
+ },
13010
+ "name": {
13011
+ "anyOf": [
13012
+ { "type": "string", "minLength": 1, "maxLength": 120 },
13013
+ { "type": "null" }
13014
+ ],
13015
+ "description": "The promotion's name for a redeemed code. Null for a direct reward."
13016
+ },
13017
+ "status": {
13018
+ "type": "string",
13019
+ "enum": ["active", "pending", "expired", "used", "revoked"],
13020
+ "description": "`pending` is a credit pack discount carried by a payment still waiting for the customer's bank; it is not available to another purchase. `used` applies only to a discount that has been applied to a purchase or put on a plan. `revoked` means WhooshBang withdrew what remained."
13021
+ },
13022
+ "starts_at": { "type": "string", "format": "date-time" },
13023
+ "ends_at": {
13024
+ "anyOf": [
13025
+ { "type": "string", "format": "date-time" },
13026
+ { "type": "null" }
13027
+ ],
13028
+ "description": "When the benefit stops. Null for a discount, which ends when it is used or, for a plan discount, after its months."
13029
+ },
13030
+ "ended_at": {
13031
+ "anyOf": [
13032
+ { "type": "string", "format": "date-time" },
13033
+ { "type": "null" }
13034
+ ],
13035
+ "description": "When the promotion was used or withdrawn, if it was."
13036
+ },
13037
+ "benefit": {
13038
+ "oneOf": [
13039
+ {
13040
+ "type": "object",
13041
+ "required": ["kind", "credit_usd", "remaining_usd"],
13042
+ "properties": {
13043
+ "kind": { "const": "message_credit" },
13044
+ "credit_usd": {
13045
+ "type": "string",
13046
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13047
+ "maxLength": 40
13048
+ },
13049
+ "remaining_usd": {
13050
+ "type": "string",
13051
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13052
+ "maxLength": 40
13053
+ }
13054
+ },
13055
+ "additionalProperties": false,
13056
+ "description": "Promotional credit for messages. Spent before purchased, bonus and welcome credit; it never pays the shared-email charge."
13057
+ },
13058
+ {
13059
+ "type": "object",
13060
+ "required": ["kind", "shared_emails", "remaining"],
13061
+ "properties": {
13062
+ "kind": { "const": "shared_emails" },
13063
+ "shared_emails": {
13064
+ "type": "integer",
13065
+ "minimum": 1,
13066
+ "maximum": 1000000
13067
+ },
13068
+ "remaining": {
13069
+ "type": "integer",
13070
+ "minimum": 0,
13071
+ "maximum": 1000000
13072
+ }
13073
+ },
13074
+ "additionalProperties": false,
13075
+ "description": "Emails through WhooshBang's shared email service with no shared-email charge. The message itself is still paid for."
13076
+ },
13077
+ {
13078
+ "type": "object",
13079
+ "required": ["kind"],
13080
+ "properties": { "kind": { "const": "branding" } },
13081
+ "additionalProperties": false,
13082
+ "description": "WhooshBang branding can be removed from recipient messages until ends_at."
13083
+ },
13084
+ {
13085
+ "type": "object",
13086
+ "required": [
13087
+ "kind",
13088
+ "applies_to",
13089
+ "percent_off",
13090
+ "amount_off_usd",
13091
+ "duration",
13092
+ "months"
13093
+ ],
13094
+ "properties": {
13095
+ "kind": { "const": "discount" },
13096
+ "applies_to": {
13097
+ "type": "string",
13098
+ "enum": ["credit_packs", "subscriptions"]
13099
+ },
13100
+ "percent_off": {
13101
+ "anyOf": [
13102
+ { "type": "integer", "minimum": 1, "maximum": 90 },
13103
+ { "type": "null" }
13104
+ ]
13105
+ },
13106
+ "amount_off_usd": {
13107
+ "anyOf": [
13108
+ {
13109
+ "type": "string",
13110
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13111
+ "maxLength": 40
13112
+ },
13113
+ { "type": "null" }
13114
+ ]
13115
+ },
13116
+ "duration": {
13117
+ "type": "string",
13118
+ "enum": ["once", "repeating"]
13119
+ },
13120
+ "months": {
13121
+ "anyOf": [
13122
+ { "type": "integer", "minimum": 1, "maximum": 12 },
13123
+ { "type": "null" }
13124
+ ]
13125
+ }
13126
+ },
13127
+ "additionalProperties": false,
13128
+ "description": "Money off the next credit pack, or off plan and add-on charges for one charge or a number of months."
13129
+ }
13130
+ ]
13131
+ }
13132
+ },
13133
+ "additionalProperties": false,
13134
+ "description": "One promotion an organization holds. Promotional value has no cash value, is never refunded and cannot move to another organization."
13135
+ },
13136
+ "RedeemBillingPromotionRequest": {
13137
+ "type": "object",
13138
+ "required": ["code"],
13139
+ "properties": {
13140
+ "code": {
13141
+ "type": "string",
13142
+ "minLength": 4,
13143
+ "maxLength": 64,
13144
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9 _-]*$",
13145
+ "description": "The promotion code. Case, spaces, hyphens and underscores are ignored."
13146
+ }
13147
+ },
13148
+ "additionalProperties": false
13149
+ },
12839
13150
  "BillingEntitlements": {
12840
13151
  "type": "object",
12841
13152
  "required": [
@@ -13000,6 +13311,8 @@
13000
13311
  "monthly_price_usd",
13001
13312
  "charge_now_usd",
13002
13313
  "unused_time_credit_usd",
13314
+ "promotion",
13315
+ "renewal_promotion",
13003
13316
  "effective_at",
13004
13317
  "expires_at",
13005
13318
  "client_secret",
@@ -13055,6 +13368,85 @@
13055
13368
  ],
13056
13369
  "description": "Compatibility field: new upgrades return 0.000000; starts and boundary changes return null. Historical quotes can retain their recorded unused-time credit. No new automatic unused-time refunds or offsets."
13057
13370
  },
13371
+ "promotion": {
13372
+ "anyOf": [
13373
+ {
13374
+ "type": "object",
13375
+ "required": [
13376
+ "percent_off",
13377
+ "duration",
13378
+ "months",
13379
+ "discount_usd"
13380
+ ],
13381
+ "properties": {
13382
+ "percent_off": {
13383
+ "type": "integer",
13384
+ "minimum": 1,
13385
+ "maximum": 90
13386
+ },
13387
+ "duration": {
13388
+ "type": "string",
13389
+ "enum": ["once", "repeating"]
13390
+ },
13391
+ "months": {
13392
+ "anyOf": [
13393
+ { "type": "integer", "minimum": 1, "maximum": 12 },
13394
+ { "type": "null" }
13395
+ ]
13396
+ },
13397
+ "discount_usd": {
13398
+ "type": "string",
13399
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13400
+ "maxLength": 40,
13401
+ "description": "What the promotion takes off charge_now_usd."
13402
+ }
13403
+ },
13404
+ "additionalProperties": false
13405
+ },
13406
+ { "type": "null" }
13407
+ ],
13408
+ "description": "A promotion code's plan discount applied to this first plan or add-on purchase: charge_now_usd is already reduced by it, and the monthly charges keep it for its months. Null when no plan discount applies."
13409
+ },
13410
+ "renewal_promotion": {
13411
+ "anyOf": [
13412
+ {
13413
+ "type": "object",
13414
+ "required": [
13415
+ "percent_off",
13416
+ "duration",
13417
+ "renewal_price_usd",
13418
+ "ends_at"
13419
+ ],
13420
+ "properties": {
13421
+ "percent_off": {
13422
+ "type": "integer",
13423
+ "minimum": 1,
13424
+ "maximum": 90
13425
+ },
13426
+ "duration": {
13427
+ "type": "string",
13428
+ "enum": ["once", "repeating"]
13429
+ },
13430
+ "renewal_price_usd": {
13431
+ "type": "string",
13432
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13433
+ "maxLength": 40,
13434
+ "description": "What the next monthly charge for this selection comes to with the promotion: monthly_price_usd less its percentage."
13435
+ },
13436
+ "ends_at": {
13437
+ "anyOf": [
13438
+ { "type": "string", "format": "date-time" },
13439
+ { "type": "null" }
13440
+ ],
13441
+ "description": "When a repeating promotion stops applying. Null for a once promotion, which applies to the next monthly charge only."
13442
+ }
13443
+ },
13444
+ "additionalProperties": false
13445
+ },
13446
+ { "type": "null" }
13447
+ ],
13448
+ "description": "A plan discount already running on the subscription, which keeps lowering the monthly charges after this change. It never reduces charge_now_usd. Null when none applies to the next monthly charge, and once a change is no longer a quote."
13449
+ },
13058
13450
  "effective_at": {
13059
13451
  "anyOf": [
13060
13452
  { "type": "string", "format": "date-time" },
@@ -13387,6 +13779,12 @@
13387
13779
  "purchase_cents": {
13388
13780
  "type": "integer",
13389
13781
  "enum": [500, 1000, 2500, 5000, 10000]
13782
+ },
13783
+ "approved_charge_usd": {
13784
+ "type": "string",
13785
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13786
+ "maxLength": 40,
13787
+ "description": "Optional. The card charge the customer approved: the pack price less any promotion shown to them. When it differs from what would be charged, the purchase is refused with credit_purchase_price_changed and nothing is charged."
13390
13788
  }
13391
13789
  },
13392
13790
  "description": "Buy one credit pack now with the default card. The amount is the explicit approval of the charge."
@@ -13398,6 +13796,8 @@
13398
13796
  "id",
13399
13797
  "purchase_cents",
13400
13798
  "credit_usd",
13799
+ "charged_usd",
13800
+ "discount_usd",
13401
13801
  "status",
13402
13802
  "failure",
13403
13803
  "client_secret",
@@ -13418,6 +13818,18 @@
13418
13818
  "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13419
13819
  "maxLength": 40
13420
13820
  },
13821
+ "charged_usd": {
13822
+ "type": "string",
13823
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13824
+ "maxLength": 40,
13825
+ "description": "What the card is charged: the pack price less any promotional discount. The pack's credit is unchanged."
13826
+ },
13827
+ "discount_usd": {
13828
+ "type": "string",
13829
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13830
+ "maxLength": 40,
13831
+ "description": "The promotional discount taken off the pack price, or 0.000000."
13832
+ },
13421
13833
  "status": {
13422
13834
  "type": "string",
13423
13835
  "enum": ["processing", "requires_action", "paid", "failed"]
@@ -20387,7 +20799,7 @@
20387
20799
  "provider-capabilities-v1.schema": {
20388
20800
  "$schema": "https://json-schema.org/draft/2020-12/schema",
20389
20801
  "$id": "https://whooshbang.flowxo.com/schemas/provider-capabilities-v1.schema.json",
20390
- "x-contract-version": "2.3.0",
20802
+ "x-contract-version": "2.5.0",
20391
20803
  "title": "ProviderCapabilitiesV1",
20392
20804
  "description": "Provider-neutral, renderer-facing delivery and interaction capabilities with explicit degradation.",
20393
20805
  "type": "object",
@@ -21168,5 +21580,5 @@
21168
21580
  }
21169
21581
  }
21170
21582
  },
21171
- "x-contract-version": "2.3.0"
21583
+ "x-contract-version": "2.5.0"
21172
21584
  }