@whooshbang/contracts 2.4.0 → 2.6.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 (106) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +21 -21
  3. package/contract-manifest.json +35 -32
  4. package/dist/generated/artifact-index.d.ts +34 -34
  5. package/dist/generated/artifact-index.js +35 -32
  6. package/dist/generated/artifact-index.js.map +1 -1
  7. package/dist/generated/interaction-v1.d.ts +77 -4
  8. package/dist/generated/validators-standalone.d.ts +522 -483
  9. package/dist/generated/validators-standalone.js +12972 -6118
  10. package/dist/generated/validators-standalone.js.map +1 -1
  11. package/dist/generated/validators.d.ts +5 -2
  12. package/dist/generated/validators.js +4 -1
  13. package/dist/generated/validators.js.map +1 -1
  14. package/dist/generated/whooshbang-v1.d.ts +634 -116
  15. package/dist/index.d.ts +5 -2
  16. package/dist/index.js +2 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/input-defaults.d.ts +7 -0
  19. package/dist/input-defaults.js +59 -0
  20. package/dist/input-defaults.js.map +1 -0
  21. package/dist/interaction-expiry.js +2 -1
  22. package/dist/interaction-expiry.js.map +1 -1
  23. package/fixtures/core-v1/fixture-index.json +24 -1
  24. package/fixtures/core-v1/problems/idempotency-key-required.json +2 -2
  25. package/fixtures/core-v1/requests/redeem-billing-promotion.json +3 -0
  26. package/fixtures/core-v1/scenarios/api-credential-invalid-indistinguishable.json +1 -1
  27. package/fixtures/core-v1/scenarios/api-credential-one-time-reveal.json +1 -1
  28. package/fixtures/core-v1/scenarios/api-credential-revocation-replay.json +1 -1
  29. package/fixtures/core-v1/scenarios/api-credential-rotation-overlap.json +1 -1
  30. package/fixtures/core-v1/scenarios/cancellation-too-late.json +1 -1
  31. package/fixtures/core-v1/scenarios/cross-organization-project.json +1 -1
  32. package/fixtures/core-v1/scenarios/cross-project-resource.json +1 -1
  33. package/fixtures/core-v1/scenarios/environment-mismatch.json +1 -1
  34. package/fixtures/core-v1/scenarios/idempotency-conflict.json +1 -1
  35. package/fixtures/core-v1/scenarios/idempotency-equivalent-replay.json +1 -1
  36. package/fixtures/core-v1/scenarios/malformed-bearer.json +1 -1
  37. package/fixtures/core-v1/scenarios/metadata-overrun.json +1 -1
  38. package/fixtures/core-v1/scenarios/missing-scope.json +1 -1
  39. package/fixtures/core-v1/scenarios/project-create-equivalent-replay.json +1 -1
  40. package/fixtures/core-v1/scenarios/project-slug-conflict.json +1 -1
  41. package/fixtures/core-v1/scenarios/provider-outcome-unknown.json +1 -1
  42. package/fixtures/core-v1/scenarios/subscription-activation-replay.json +1 -1
  43. package/fixtures/core-v1/scenarios/subscription-authorization-capability-opacity.json +1 -1
  44. package/fixtures/core-v1/scenarios/subscription-cross-connection-callback-rejected.json +1 -1
  45. package/fixtures/core-v1/scenarios/subscription-expired-replay.json +1 -1
  46. package/fixtures/core-v1/scenarios/subscription-selected-connection-replay.json +1 -1
  47. package/fixtures/core-v1/success/billing-promotion.json +14 -0
  48. package/fixtures/core-v1/success/billing-promotions.json +64 -0
  49. package/fixtures/core-v1/success/credit-purchase.json +2 -0
  50. package/fixtures/core-v1/success/subscription-change.json +7 -0
  51. package/fixtures/interaction-machine-v1/scenarios/ack-conflict.json +1 -1
  52. package/fixtures/interaction-machine-v1/scenarios/ack-cross-machine.json +1 -1
  53. package/fixtures/interaction-machine-v1/scenarios/ack-out-of-order.json +1 -1
  54. package/fixtures/interaction-machine-v1/scenarios/ack-replay.json +1 -1
  55. package/fixtures/interaction-machine-v1/scenarios/ambiguous-free-text.json +1 -1
  56. package/fixtures/interaction-machine-v1/scenarios/callback-token-opacity.json +1 -1
  57. package/fixtures/interaction-machine-v1/scenarios/credential-registration-conflict.json +1 -1
  58. package/fixtures/interaction-machine-v1/scenarios/credential-registration-replay.json +1 -1
  59. package/fixtures/interaction-machine-v1/scenarios/credential-revocation-replay.json +1 -1
  60. package/fixtures/interaction-machine-v1/scenarios/credential-rotation.json +1 -1
  61. package/fixtures/interaction-machine-v1/scenarios/first-credential-activates.json +1 -1
  62. package/fixtures/interaction-machine-v1/scenarios/interaction-duplicate.json +1 -1
  63. package/fixtures/interaction-machine-v1/scenarios/interaction-expired.json +1 -1
  64. package/fixtures/interaction-machine-v1/scenarios/interaction-mismatch.json +1 -1
  65. package/fixtures/interaction-machine-v1/scenarios/interaction-retired.json +1 -1
  66. package/fixtures/interaction-machine-v1/scenarios/interaction-stale.json +1 -1
  67. package/fixtures/interaction-machine-v1/scenarios/machine-admin-denied.json +1 -1
  68. package/fixtures/interaction-machine-v1/scenarios/machine-bound-message-create.json +1 -1
  69. package/fixtures/interaction-machine-v1/scenarios/machine-cancel-denied.json +1 -1
  70. package/fixtures/interaction-machine-v1/scenarios/machine-crash-before-ack.json +1 -1
  71. package/fixtures/interaction-machine-v1/scenarios/machine-notifier-mismatch.json +1 -1
  72. package/fixtures/interaction-machine-v1/scenarios/machine-other-message-read.json +1 -1
  73. package/fixtures/interaction-machine-v1/scenarios/machine-owned-message-read.json +1 -1
  74. package/fixtures/interaction-machine-v1/scenarios/machine-subscriber-mismatch.json +1 -1
  75. package/fixtures/interaction-machine-v1/scenarios/poll-uncommitted-cursor-rejected.json +1 -1
  76. package/fixtures/interaction-machine-v1/scenarios/project-credential-machine-endpoint-denied.json +1 -1
  77. package/fixtures/interaction-machine-v1/scenarios/quarantine-integrity-rejected.json +1 -1
  78. package/fixtures/interaction-machine-v1/scenarios/quarantine-signature-rejected.json +1 -1
  79. package/fixtures/interaction-machine-v1/scenarios/quarantine-valid.json +1 -1
  80. package/fixtures/interaction-machine-v1/scenarios/revoked-machine-credential.json +1 -1
  81. package/fixtures/recipient-experience-v1/scenarios/membership-broadcast-snapshot.json +1 -1
  82. package/fixtures/recipient-experience-v1/scenarios/preferred-binding-resolution.json +1 -1
  83. package/fixtures/recipient-experience-v1/scenarios/recipient-session-security.json +1 -1
  84. package/fixtures/recipient-experience-v1/scenarios/subscriber-initial-groups-transaction.json +1 -1
  85. package/openapi/generated/whooshbang-v1.bundle.json +1338 -254
  86. package/openapi/whooshbang-v1.yaml +547 -73
  87. package/package.json +1 -1
  88. package/schemas/core-scenario.schema.json +1 -1
  89. package/schemas/events/agent-binding-degraded-v1.schema.json +1 -1
  90. package/schemas/events/interaction-expired-v1.schema.json +1 -1
  91. package/schemas/events/interaction-received-v1.schema.json +1 -1
  92. package/schemas/events/message-delivered-v1.schema.json +1 -1
  93. package/schemas/events/message-expired-v1.schema.json +1 -1
  94. package/schemas/events/message-failed-v1.schema.json +1 -1
  95. package/schemas/events/message-provider-accepted-v1.schema.json +1 -1
  96. package/schemas/events/subscription-activated-v1.schema.json +1 -1
  97. package/schemas/events/subscription-revoked-v1.schema.json +1 -1
  98. package/schemas/events/whooshbang-event-v1.schema.json +1 -1
  99. package/schemas/fixture-index.schema.json +1 -1
  100. package/schemas/interaction-machine-scenario.schema.json +1 -1
  101. package/schemas/interaction-v1.schema.json +192 -5
  102. package/schemas/machine-v1.schema.json +1 -1
  103. package/schemas/provider-capabilities-v1.schema.json +1 -1
  104. package/schemas/webhooks/webhook-delivery-case-v1.schema.json +1 -1
  105. package/schemas/webhooks/webhook-signing-vector-v1.schema.json +1 -1
  106. package/schemas/whooshbang-v1.schema.json +769 -63
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.1",
3
3
  "info": {
4
4
  "title": "WhooshBang API",
5
- "version": "2.4.0",
5
+ "version": "2.6.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": {
@@ -524,6 +524,92 @@
524
524
  }
525
525
  }
526
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
+ },
527
613
  "/v1/billing/auto-top-up": {
528
614
  "get": {
529
615
  "operationId": "getAutoTopUpSettings",
@@ -2311,7 +2397,7 @@
2311
2397
  "x-required-scopes": ["projects:write"],
2312
2398
  "x-organization-membership": "required",
2313
2399
  "x-idempotency": {
2314
- "required": true,
2400
+ "required": false,
2315
2401
  "retention_seconds": 604800,
2316
2402
  "scope": ["authenticated_organization", "operation_id", "key"],
2317
2403
  "equivalence": "validated_request",
@@ -2984,7 +3070,7 @@
2984
3070
  "security": [{ "bearerCredential": [] }],
2985
3071
  "x-required-scopes": ["subscriptions:write"],
2986
3072
  "x-idempotency": {
2987
- "required": true,
3073
+ "required": false,
2988
3074
  "retention_seconds": 604800,
2989
3075
  "scope": ["authenticated_environment", "operation_id", "key"],
2990
3076
  "equivalence": "validated_request_after_documented_defaults",
@@ -3139,7 +3225,7 @@
3139
3225
  "security": [{ "bearerCredential": [] }],
3140
3226
  "x-required-scopes": ["subscriptions:write"],
3141
3227
  "x-idempotency": {
3142
- "required": true,
3228
+ "required": false,
3143
3229
  "retention_seconds": 604800,
3144
3230
  "scope": ["authenticated_environment", "operation_id", "key"],
3145
3231
  "equivalence": "validated_request_after_documented_defaults",
@@ -3220,7 +3306,7 @@
3220
3306
  ],
3221
3307
  "x-required-scopes": ["messages:write"],
3222
3308
  "x-idempotency": {
3223
- "required": true,
3309
+ "required": false,
3224
3310
  "retention_seconds": 604800,
3225
3311
  "scope": ["authenticated_environment", "operation_id", "key"],
3226
3312
  "equivalence": "canonical_rgba_png_after_validation",
@@ -3342,7 +3428,7 @@
3342
3428
  "bound_subscriber"
3343
3429
  ],
3344
3430
  "x-idempotency": {
3345
- "required": true,
3431
+ "required": false,
3346
3432
  "retention_seconds": 604800,
3347
3433
  "scope": ["authenticated_environment", "operation_id", "key"],
3348
3434
  "equivalence": "validated_request_after_documented_defaults",
@@ -3770,7 +3856,7 @@
3770
3856
  "security": [{ "bearerCredential": [] }],
3771
3857
  "x-required-scopes": ["messages:write"],
3772
3858
  "x-idempotency": {
3773
- "required": true,
3859
+ "required": false,
3774
3860
  "retention_seconds": 604800,
3775
3861
  "scope": ["authenticated_environment", "operation_id", "key"],
3776
3862
  "equivalence": "validated_request_after_documented_defaults",
@@ -3876,7 +3962,7 @@
3876
3962
  "security": [{ "bearerCredential": [] }],
3877
3963
  "x-required-scopes": ["machine-clients:write"],
3878
3964
  "x-idempotency": {
3879
- "required": true,
3965
+ "required": false,
3880
3966
  "retention_seconds": 604800,
3881
3967
  "scope": ["authenticated_environment", "operation_id", "key"],
3882
3968
  "equivalence": "validated_request_after_documented_defaults",
@@ -4013,7 +4099,7 @@
4013
4099
  "security": [{ "bearerCredential": [] }],
4014
4100
  "x-required-scopes": ["machine-clients:write"],
4015
4101
  "x-idempotency": {
4016
- "required": true,
4102
+ "required": false,
4017
4103
  "retention_seconds": 604800,
4018
4104
  "scope": ["authenticated_environment", "operation_id", "key"],
4019
4105
  "equivalence": "validated_request_after_documented_defaults",
@@ -4121,7 +4207,7 @@
4121
4207
  "security": [{ "bearerCredential": [] }],
4122
4208
  "x-required-scopes": ["machine-clients:write"],
4123
4209
  "x-idempotency": {
4124
- "required": true,
4210
+ "required": false,
4125
4211
  "retention_seconds": 604800,
4126
4212
  "scope": ["authenticated_environment", "operation_id", "key"],
4127
4213
  "equivalence": "validated_request_after_documented_defaults",
@@ -4179,7 +4265,7 @@
4179
4265
  "security": [{ "bearerCredential": [] }],
4180
4266
  "x-required-scopes": ["machine-clients:write"],
4181
4267
  "x-idempotency": {
4182
- "required": true,
4268
+ "required": false,
4183
4269
  "retention_seconds": 604800,
4184
4270
  "scope": ["authenticated_environment", "operation_id", "key"],
4185
4271
  "equivalence": "validated_request_after_documented_defaults",
@@ -4342,7 +4428,7 @@
4342
4428
  "security": [{ "machineCredential": [] }],
4343
4429
  "x-required-scopes": ["machine-events:ack"],
4344
4430
  "x-idempotency": {
4345
- "required": true,
4431
+ "required": false,
4346
4432
  "retention_seconds": 604800,
4347
4433
  "scope": ["authenticated_environment", "operation_id", "key"],
4348
4434
  "equivalence": "validated_request_after_documented_defaults",
@@ -4513,7 +4599,7 @@
4513
4599
  },
4514
4600
  "post": {
4515
4601
  "operationId": "createCustomerEndpoint",
4516
- "summary": "Create an inactive customer endpoint and reveal its signing secret once",
4602
+ "summary": "Create an active customer endpoint and reveal its signing secret once",
4517
4603
  "description": "Validates the final normalized public HTTPS destination before storing it.\nThe initial response includes the new signing secret. An equivalent\nidempotent replay returns the same logical endpoint without the secret;\ndifferent input for the same key returns `idempotency_conflict`.\n",
4518
4604
  "tags": ["Customer endpoints"],
4519
4605
  "security": [
@@ -4524,7 +4610,7 @@
4524
4610
  ],
4525
4611
  "x-required-scopes": ["messages:write"],
4526
4612
  "x-idempotency": {
4527
- "required": true,
4613
+ "required": false,
4528
4614
  "retention_seconds": 604800,
4529
4615
  "scope": ["authenticated_environment", "operation_id", "key"],
4530
4616
  "equivalence": "final_normalized_url_and_validated_request",
@@ -4593,7 +4679,7 @@
4593
4679
  }
4594
4680
  },
4595
4681
  "201": {
4596
- "description": "Newly created unverified endpoint and its one-time signing secret.",
4682
+ "description": "Newly active endpoint, recorded signed ping result, and its one-time signing secret.",
4597
4683
  "headers": {
4598
4684
  "WhooshBang-Diagnostic-Id": {
4599
4685
  "$ref": "#/components/headers/DiagnosticId"
@@ -4680,7 +4766,7 @@
4680
4766
  ],
4681
4767
  "post": {
4682
4768
  "operationId": "verifyCustomerEndpoint",
4683
- "summary": "Send a signed challenge and activate the endpoint only after a signed echo",
4769
+ "summary": "Send a signed test ping and record whether the receiver returns 2xx",
4684
4770
  "tags": ["Customer endpoints"],
4685
4771
  "security": [
4686
4772
  { "clerkSession": [] },
@@ -4724,7 +4810,7 @@
4724
4810
  ],
4725
4811
  "x-required-scopes": ["messages:write"],
4726
4812
  "x-idempotency": {
4727
- "required": true,
4813
+ "required": false,
4728
4814
  "retention_seconds": 604800,
4729
4815
  "scope": ["authenticated_environment", "operation_id", "key"],
4730
4816
  "equivalence": "endpoint_and_validated_request",
@@ -5072,7 +5158,7 @@
5072
5158
  ],
5073
5159
  "x-required-scopes": ["connections:write"],
5074
5160
  "x-idempotency": {
5075
- "required": true,
5161
+ "required": false,
5076
5162
  "retention_seconds": 604800,
5077
5163
  "scope": ["authenticated_environment", "operation_id", "key"],
5078
5164
  "equivalence": "validated_request",
@@ -5563,7 +5649,7 @@
5563
5649
  ],
5564
5650
  "x-required-scopes": ["connections:write"],
5565
5651
  "x-idempotency": {
5566
- "required": true,
5652
+ "required": false,
5567
5653
  "retention_seconds": 604800,
5568
5654
  "scope": ["authenticated_environment", "operation_id", "key"],
5569
5655
  "equivalence": "selected_connection",
@@ -6173,7 +6259,7 @@
6173
6259
  ],
6174
6260
  "x-required-scopes": ["connections:write"],
6175
6261
  "x-idempotency": {
6176
- "required": true,
6262
+ "required": false,
6177
6263
  "retention_seconds": 604800,
6178
6264
  "scope": ["authenticated_environment", "operation_id", "key"],
6179
6265
  "equivalence": "exact_request_body",
@@ -6959,7 +7045,7 @@
6959
7045
  "security": [{ "bearerCredential": [] }],
6960
7046
  "x-required-scopes": ["subscriptions:write"],
6961
7047
  "x-idempotency": {
6962
- "required": true,
7048
+ "required": false,
6963
7049
  "retention_seconds": 604800,
6964
7050
  "scope": ["authenticated_project_environment", "operation_id", "key"],
6965
7051
  "equivalence": "validated_request",
@@ -7387,7 +7473,7 @@
7387
7473
  "security": [{ "bearerCredential": [] }],
7388
7474
  "x-required-scopes": ["subscriptions:write"],
7389
7475
  "x-idempotency": {
7390
- "required": true,
7476
+ "required": false,
7391
7477
  "retention_seconds": 604800,
7392
7478
  "scope": ["authenticated_project_environment", "operation_id", "key"],
7393
7479
  "equivalence": "validated_request",
@@ -7520,7 +7606,7 @@
7520
7606
  "in": "query",
7521
7607
  "required": false,
7522
7608
  "description": "Only current members of the static group with this key, in the\nenvironment asked about. Combines with every other filter, so `q`\nsearches within the group.\n",
7523
- "schema": { "$ref": "#/components/schemas/DeveloperKey" }
7609
+ "schema": { "$ref": "#/components/schemas/GroupKey" }
7524
7610
  },
7525
7611
  {
7526
7612
  "name": "created_after",
@@ -7595,7 +7681,7 @@
7595
7681
  "security": [{ "bearerCredential": [] }],
7596
7682
  "x-required-scopes": ["subscriptions:write"],
7597
7683
  "x-idempotency": {
7598
- "required": true,
7684
+ "required": false,
7599
7685
  "retention_seconds": 604800,
7600
7686
  "scope": ["authenticated_project_environment", "operation_id", "key"],
7601
7687
  "equivalence": "validated_request",
@@ -7790,7 +7876,7 @@
7790
7876
  "security": [{ "bearerCredential": [] }],
7791
7877
  "x-required-scopes": ["subscriptions:write"],
7792
7878
  "x-idempotency": {
7793
- "required": true,
7879
+ "required": false,
7794
7880
  "retention_seconds": 604800,
7795
7881
  "scope": ["authenticated_project_environment", "operation_id", "key"],
7796
7882
  "equivalence": "validated_request",
@@ -7856,12 +7942,7 @@
7856
7942
  "name": "group_key",
7857
7943
  "in": "path",
7858
7944
  "required": true,
7859
- "schema": {
7860
- "type": "string",
7861
- "minLength": 1,
7862
- "maxLength": 63,
7863
- "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
7864
- }
7945
+ "schema": { "$ref": "#/components/schemas/GroupKey" }
7865
7946
  }
7866
7947
  ],
7867
7948
  "get": {
@@ -7952,12 +8033,7 @@
7952
8033
  "name": "group_key",
7953
8034
  "in": "path",
7954
8035
  "required": true,
7955
- "schema": {
7956
- "type": "string",
7957
- "minLength": 1,
7958
- "maxLength": 63,
7959
- "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
7960
- }
8036
+ "schema": { "$ref": "#/components/schemas/GroupKey" }
7961
8037
  }
7962
8038
  ],
7963
8039
  "get": {
@@ -8039,12 +8115,7 @@
8039
8115
  "name": "group_key",
8040
8116
  "in": "path",
8041
8117
  "required": true,
8042
- "schema": {
8043
- "type": "string",
8044
- "minLength": 1,
8045
- "maxLength": 63,
8046
- "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
8047
- }
8118
+ "schema": { "$ref": "#/components/schemas/GroupKey" }
8048
8119
  },
8049
8120
  {
8050
8121
  "name": "subscriber_id",
@@ -8114,12 +8185,7 @@
8114
8185
  "name": "group_key",
8115
8186
  "in": "path",
8116
8187
  "required": true,
8117
- "schema": {
8118
- "type": "string",
8119
- "minLength": 1,
8120
- "maxLength": 63,
8121
- "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
8122
- }
8188
+ "schema": { "$ref": "#/components/schemas/GroupKey" }
8123
8189
  }
8124
8190
  ],
8125
8191
  "post": {
@@ -8130,7 +8196,7 @@
8130
8196
  "security": [{ "bearerCredential": [] }],
8131
8197
  "x-required-scopes": ["messages:write"],
8132
8198
  "x-idempotency": {
8133
- "required": true,
8199
+ "required": false,
8134
8200
  "retention_seconds": 604800,
8135
8201
  "scope": ["authenticated_project_environment", "operation_id", "key"],
8136
8202
  "equivalence": "validated_request_and_group_key",
@@ -8406,7 +8472,7 @@
8406
8472
  ],
8407
8473
  "x-required-scopes": ["subscriptions:write"],
8408
8474
  "x-idempotency": {
8409
- "required": true,
8475
+ "required": false,
8410
8476
  "retention_seconds": 604800,
8411
8477
  "scope": ["authenticated_organization", "operation_id", "key"],
8412
8478
  "equivalence": "validated_request",
@@ -8655,7 +8721,7 @@
8655
8721
  ],
8656
8722
  "x-required-scopes": ["messages:write"],
8657
8723
  "x-idempotency": {
8658
- "required": true,
8724
+ "required": false,
8659
8725
  "retention_seconds": 604800,
8660
8726
  "scope": ["authenticated_organization", "operation_id", "key"],
8661
8727
  "equivalence": "validated_request",
@@ -8735,15 +8801,18 @@
8735
8801
  ],
8736
8802
  "get": {
8737
8803
  "operationId": "getMessageInEnvironment",
8804
+ "x-notification-scopes": ["notifications:self", "notifications:team"],
8738
8805
  "summary": "Inspect a message in a named project environment",
8739
- "description": "The environment-scoped form of `getMessage`, including the same bounded\nlong poll. Inspection acknowledges, consumes, retries, and replays\nnothing.\n",
8806
+ "description": "The environment-scoped form of `getMessage`, including the same bounded\nlong poll. Inspection acknowledges, consumes, retries, and replays\nnothing.\nNotification grants can read only current operator recipients in their\naudience: self means the signed-in person; team includes current\nteammates with access to this project. Customer subscribers and retired\nmemberships are hidden with 404, including during a held poll.\n",
8740
8807
  "parameters": [
8741
8808
  { "$ref": "#/components/parameters/ResponseInspectionWait" }
8742
8809
  ],
8743
8810
  "tags": ["Messages"],
8744
8811
  "security": [
8745
8812
  { "clerkSession": [] },
8746
- { "bearerGrant": [] },
8813
+ { "bearerGrant": ["messages:read"] },
8814
+ { "bearerGrant": ["notifications:self"] },
8815
+ { "bearerGrant": ["notifications:team"] },
8747
8816
  { "bearerOrganizationCredential": [] }
8748
8817
  ],
8749
8818
  "x-required-scopes": ["messages:read"],
@@ -8810,14 +8879,17 @@
8810
8879
  ],
8811
8880
  "get": {
8812
8881
  "operationId": "getInteractionInEnvironment",
8882
+ "x-notification-scopes": ["notifications:self", "notifications:team"],
8813
8883
  "summary": "Inspect an interaction response in a named project environment",
8814
- "description": "The environment-scoped form of `getInteraction`, including the same\nbounded long poll and the same retained-answer semantics.\n",
8884
+ "description": "The environment-scoped form of `getInteraction`, including the same\nbounded long poll and the same retained-answer semantics.\nNotification grants can read only current operator recipients in their\naudience: self means the signed-in person; team includes current\nteammates with access to this project. Customer subscribers and retired\nmemberships are hidden with 404, including during a held poll.\n",
8815
8885
  "parameters": [
8816
8886
  { "$ref": "#/components/parameters/ResponseInspectionWait" }
8817
8887
  ],
8818
8888
  "tags": ["Messages"],
8819
8889
  "security": [
8820
- { "bearerGrant": [] },
8890
+ { "bearerGrant": ["messages:read"] },
8891
+ { "bearerGrant": ["notifications:self"] },
8892
+ { "bearerGrant": ["notifications:team"] },
8821
8893
  { "bearerOrganizationCredential": [] }
8822
8894
  ],
8823
8895
  "x-required-scopes": ["messages:read"],
@@ -9008,7 +9080,7 @@
9008
9080
  ],
9009
9081
  "x-required-scopes": ["machine-clients:write"],
9010
9082
  "x-idempotency": {
9011
- "required": true,
9083
+ "required": false,
9012
9084
  "retention_seconds": 604800,
9013
9085
  "scope": ["authenticated_organization", "operation_id", "key"],
9014
9086
  "equivalence": "validated_request",
@@ -9089,7 +9161,7 @@
9089
9161
  ],
9090
9162
  "x-required-scopes": ["machine-clients:write"],
9091
9163
  "x-idempotency": {
9092
- "required": true,
9164
+ "required": false,
9093
9165
  "retention_seconds": 604800,
9094
9166
  "scope": ["authenticated_environment", "operation_id", "key"],
9095
9167
  "equivalence": "validated_request_after_documented_defaults",
@@ -9297,7 +9369,7 @@
9297
9369
  ],
9298
9370
  "x-required-scopes": ["subscriptions:write"],
9299
9371
  "x-idempotency": {
9300
- "required": true,
9372
+ "required": false,
9301
9373
  "retention_seconds": 604800,
9302
9374
  "scope": ["authenticated_project_environment", "operation_id", "key"],
9303
9375
  "equivalence": "validated_request",
@@ -9439,7 +9511,7 @@
9439
9511
  "in": "query",
9440
9512
  "required": false,
9441
9513
  "description": "Only current members of the static group with this key, in the\nenvironment asked about. Combines with every other filter, so `q`\nsearches within the group.\n",
9442
- "schema": { "$ref": "#/components/schemas/DeveloperKey" }
9514
+ "schema": { "$ref": "#/components/schemas/GroupKey" }
9443
9515
  },
9444
9516
  {
9445
9517
  "name": "created_after",
@@ -9519,7 +9591,7 @@
9519
9591
  ],
9520
9592
  "x-required-scopes": ["subscriptions:write"],
9521
9593
  "x-idempotency": {
9522
- "required": true,
9594
+ "required": false,
9523
9595
  "retention_seconds": 604800,
9524
9596
  "scope": ["authenticated_project_environment", "operation_id", "key"],
9525
9597
  "equivalence": "validated_request",
@@ -9834,7 +9906,7 @@
9834
9906
  ],
9835
9907
  "x-required-scopes": ["subscriptions:write"],
9836
9908
  "x-idempotency": {
9837
- "required": true,
9909
+ "required": false,
9838
9910
  "retention_seconds": 604800,
9839
9911
  "scope": ["authenticated_project_environment", "operation_id", "key"],
9840
9912
  "equivalence": "validated_request",
@@ -9902,12 +9974,7 @@
9902
9974
  "name": "group_key",
9903
9975
  "in": "path",
9904
9976
  "required": true,
9905
- "schema": {
9906
- "type": "string",
9907
- "minLength": 1,
9908
- "maxLength": 63,
9909
- "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
9910
- }
9977
+ "schema": { "$ref": "#/components/schemas/GroupKey" }
9911
9978
  }
9912
9979
  ],
9913
9980
  "get": {
@@ -10018,12 +10085,7 @@
10018
10085
  "name": "group_key",
10019
10086
  "in": "path",
10020
10087
  "required": true,
10021
- "schema": {
10022
- "type": "string",
10023
- "minLength": 1,
10024
- "maxLength": 63,
10025
- "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
10026
- }
10088
+ "schema": { "$ref": "#/components/schemas/GroupKey" }
10027
10089
  }
10028
10090
  ],
10029
10091
  "get": {
@@ -10113,12 +10175,7 @@
10113
10175
  "name": "group_key",
10114
10176
  "in": "path",
10115
10177
  "required": true,
10116
- "schema": {
10117
- "type": "string",
10118
- "minLength": 1,
10119
- "maxLength": 63,
10120
- "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
10121
- }
10178
+ "schema": { "$ref": "#/components/schemas/GroupKey" }
10122
10179
  },
10123
10180
  {
10124
10181
  "name": "subscriber_id",
@@ -10202,12 +10259,7 @@
10202
10259
  "name": "group_key",
10203
10260
  "in": "path",
10204
10261
  "required": true,
10205
- "schema": {
10206
- "type": "string",
10207
- "minLength": 1,
10208
- "maxLength": 63,
10209
- "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
10210
- }
10262
+ "schema": { "$ref": "#/components/schemas/GroupKey" }
10211
10263
  }
10212
10264
  ],
10213
10265
  "post": {
@@ -10223,7 +10275,7 @@
10223
10275
  ],
10224
10276
  "x-required-scopes": ["messages:write"],
10225
10277
  "x-idempotency": {
10226
- "required": true,
10278
+ "required": false,
10227
10279
  "retention_seconds": 604800,
10228
10280
  "scope": ["authenticated_project_environment", "operation_id", "key"],
10229
10281
  "equivalence": "validated_request_and_group_key",
@@ -10725,7 +10777,7 @@
10725
10777
  ],
10726
10778
  "x-required-scopes": ["machine-clients:write"],
10727
10779
  "x-idempotency": {
10728
- "required": true,
10780
+ "required": false,
10729
10781
  "retention_seconds": 604800,
10730
10782
  "scope": ["authenticated_environment", "operation_id", "key"],
10731
10783
  "equivalence": "validated_request_after_documented_defaults",
@@ -10799,74 +10851,591 @@
10799
10851
  "429": { "$ref": "#/components/responses/RateLimited" }
10800
10852
  }
10801
10853
  }
10802
- }
10803
- },
10804
- "components": {
10805
- "securitySchemes": {
10806
- "clerkSession": {
10807
- "type": "http",
10808
- "scheme": "bearer",
10809
- "bearerFormat": "Clerk session JWT",
10810
- "description": "A verified human session whose active Clerk organization selects exactly\none WhooshBang organization. Every administration operation rechecks the\nprojected organization membership; session identity never selects a project\nor environment from request data.\n"
10811
- },
10812
- "bearerGrant": {
10813
- "type": "http",
10814
- "scheme": "bearer",
10815
- "bearerFormat": "OAuth 2.0 access token",
10816
- "description": "An access token issued by the WhooshBang authorization server for one\nconsenting organization member. It selects exactly one organization and carries\nthe scopes that member consented to, and never a project: an operation\nnames its project and environment in the path, and the service checks\nboth against the granting person's membership before acting. A project\ncredential can name no project but its own; an organization credential names\none per call the same way this does.\n"
10817
- },
10818
- "bearerOrganizationCredential": {
10819
- "type": "http",
10820
- "scheme": "bearer",
10821
- "bearerFormat": "wb_oc1.ocred_<16-byte-base64url>.<32-byte-base64url-secret>",
10822
- "description": "Selects exactly one organization and nothing below it. It names no project\nand no environment, so it authenticates only operations whose path does,\nand its authority there is the intersection of its scopes with the\nproject-credential vocabulary — never wider than a project credential\nwould have been for the same project environment. A path naming a\nproject outside its organization is refused with `credential_organization_mismatch`\nrather than a bare scope failure. `organization:read` is the one scope it can\nhold that a project credential cannot, and it authorizes only the\norganization's project directory: listing the organization's projects and reading\none, which is the organization-wide question a project credential cannot ask\nand the reason a `--project <slug>` selector can work at all. It is\nrequired *in addition to* each of those operations' own\n`x-required-scopes`, never instead of them.\nCredentials cannot be read after creation.\n"
10823
- },
10824
- "bearerCredential": {
10825
- "type": "http",
10826
- "scheme": "bearer",
10827
- "bearerFormat": "wb_pc1.pcred_<16-byte-base64url>.<32-byte-base64url-secret>",
10828
- "description": "Selects exactly one organization, project, and `test` or `live` environment.\nCredentials cannot be read after creation. Except for explicitly\nsynthetic creation/rotation reveal fixtures, complete bearer values\nmust never appear in fixtures, errors, logs, metrics, or inspection\nsurfaces.\n"
10829
- },
10830
- "machineCredential": {
10831
- "type": "http",
10832
- "scheme": "bearer",
10833
- "bearerFormat": "wb_mc1.mcred_<16-byte-base64url>.<32-byte-base64url-secret>",
10834
- "description": "A locally generated runtime credential bound server-side to exactly one\nproject/environment, machine client, notifier, subscriber, binding, and\nevent stream. Project credentials cannot use machine-only endpoints.\nRevoked credentials fail before resource lookup.\n"
10835
- },
10836
- "recipientSessionCapability": {
10837
- "type": "apiKey",
10838
- "in": "header",
10839
- "name": "WhooshBang-Recipient-Capability",
10840
- "description": "A creation-only, short-lived capability bound server-side to one exact\norganization, project, environment, subscriber, configuration revision,\npresenting origin, and registered return destination. It is accepted\nonly on the `/v1/recipient-session` browser surface and never as bearer\nauthority on an ordinary application route.\n"
10854
+ },
10855
+ "/v1/operator-messages": {
10856
+ "post": {
10857
+ "operationId": "sendOperatorMessageWithCredential",
10858
+ "summary": "sendOperatorMessage",
10859
+ "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.",
10860
+ "tags": ["Operators"],
10861
+ "security": [{ "bearerCredential": [] }],
10862
+ "x-required-scopes": ["messages:write"],
10863
+ "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
10864
+ "requestBody": {
10865
+ "required": true,
10866
+ "content": {
10867
+ "application/json": {
10868
+ "schema": {
10869
+ "$ref": "#/components/schemas/CreateOperatorMessageRequest"
10870
+ }
10871
+ }
10872
+ }
10873
+ },
10874
+ "responses": {
10875
+ "200": {
10876
+ "description": "Authorized result.",
10877
+ "content": {
10878
+ "application/json": {
10879
+ "schema": {
10880
+ "$ref": "#/components/schemas/OperatorMessageResult"
10881
+ }
10882
+ }
10883
+ }
10884
+ },
10885
+ "401": { "$ref": "#/components/responses/Unauthorized" },
10886
+ "403": { "$ref": "#/components/responses/Forbidden" }
10887
+ }
10841
10888
  }
10842
10889
  },
10843
- "parameters": {
10844
- "IdempotencyKey": {
10845
- "name": "Idempotency-Key",
10846
- "in": "header",
10847
- "required": true,
10848
- "description": "Visible ASCII key retained for seven days from first acceptance.\nEquivalent replay returns the original status and logical resource;\nmaterially different validated input returns `idempotency_conflict`.\n",
10849
- "schema": { "$ref": "#/components/schemas/IdempotencyKey" }
10850
- },
10851
- "SubscriptionLinkId": {
10852
- "name": "subscription_link_id",
10853
- "in": "path",
10854
- "required": true,
10855
- "description": "Opaque subscription-link identity; never authorization.",
10856
- "schema": { "$ref": "#/components/schemas/OpaqueIdentifier" }
10857
- },
10858
- "ProjectId": {
10859
- "name": "project_id",
10860
- "in": "path",
10861
- "required": true,
10862
- "description": "Opaque project identity; never authorization.",
10863
- "schema": { "$ref": "#/components/schemas/OpaqueIdentifier" }
10864
- },
10865
- "EnvironmentId": {
10866
- "name": "environment_id",
10867
- "in": "path",
10868
- "required": true,
10869
- "description": "Opaque project-environment identity; never authorization.",
10890
+ "/v1/operators/broadcasts": {
10891
+ "post": {
10892
+ "operationId": "broadcastOperatorsWithCredential",
10893
+ "summary": "broadcastOperators",
10894
+ "description": "Membership-derived operator messaging. Only the authenticated human can manage personal preferences.",
10895
+ "tags": ["Operators"],
10896
+ "security": [{ "bearerCredential": [] }],
10897
+ "x-required-scopes": ["messages:write"],
10898
+ "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
10899
+ "requestBody": {
10900
+ "required": true,
10901
+ "content": {
10902
+ "application/json": {
10903
+ "schema": {
10904
+ "$ref": "#/components/schemas/CreateGroupBroadcastRequest"
10905
+ }
10906
+ }
10907
+ }
10908
+ },
10909
+ "responses": {
10910
+ "200": {
10911
+ "description": "Authorized result.",
10912
+ "content": {
10913
+ "application/json": {
10914
+ "schema": { "$ref": "#/components/schemas/OperatorBroadcast" }
10915
+ }
10916
+ }
10917
+ },
10918
+ "401": { "$ref": "#/components/responses/Unauthorized" },
10919
+ "403": { "$ref": "#/components/responses/Forbidden" }
10920
+ }
10921
+ }
10922
+ },
10923
+ "/v1/operators/broadcasts/{broadcast_id}": {
10924
+ "get": {
10925
+ "operationId": "getOperatorBroadcastWithCredential",
10926
+ "summary": "getOperatorBroadcast",
10927
+ "description": "Membership-derived operator messaging. Only the authenticated human can manage personal preferences.",
10928
+ "tags": ["Operators"],
10929
+ "security": [{ "bearerCredential": [] }],
10930
+ "x-required-scopes": ["messages:read"],
10931
+ "parameters": [
10932
+ {
10933
+ "name": "broadcast_id",
10934
+ "in": "path",
10935
+ "required": true,
10936
+ "schema": { "type": "string" }
10937
+ }
10938
+ ],
10939
+ "responses": {
10940
+ "200": {
10941
+ "description": "Authorized result.",
10942
+ "content": {
10943
+ "application/json": {
10944
+ "schema": { "$ref": "#/components/schemas/OperatorBroadcast" }
10945
+ }
10946
+ }
10947
+ },
10948
+ "401": { "$ref": "#/components/responses/Unauthorized" },
10949
+ "403": { "$ref": "#/components/responses/Forbidden" }
10950
+ }
10951
+ }
10952
+ },
10953
+ "/v1/customer-endpoints": {
10954
+ "get": {
10955
+ "operationId": "listCustomerEndpointsWithCredential",
10956
+ "summary": "List safe customer-endpoint health metadata",
10957
+ "tags": ["Customer endpoints"],
10958
+ "security": [{ "bearerCredential": [] }],
10959
+ "x-required-scopes": ["messages:read"],
10960
+ "parameters": [
10961
+ { "$ref": "#/components/parameters/ListAfterCursor" },
10962
+ { "$ref": "#/components/parameters/ListPageLimit" },
10963
+ { "$ref": "#/components/parameters/PageNumber" }
10964
+ ],
10965
+ "responses": {
10966
+ "200": {
10967
+ "description": "Safe endpoint lifecycle and health metadata without URLs or protected content.",
10968
+ "headers": {
10969
+ "WhooshBang-Diagnostic-Id": {
10970
+ "$ref": "#/components/headers/DiagnosticId"
10971
+ }
10972
+ },
10973
+ "content": {
10974
+ "application/json": {
10975
+ "schema": {
10976
+ "$ref": "#/components/schemas/CustomerEndpointCollection"
10977
+ },
10978
+ "examples": {
10979
+ "endpoints": {
10980
+ "value": {
10981
+ "items": [
10982
+ {
10983
+ "id": "endpoint_11111111111111111111111111111111",
10984
+ "project_id": "project_synthetic_alpha",
10985
+ "environment_id": "environment_test_synthetic_alpha",
10986
+ "environment": "test",
10987
+ "status": "active",
10988
+ "verification_status": "verified",
10989
+ "event_types": [
10990
+ "interaction.received",
10991
+ "message.delivered"
10992
+ ],
10993
+ "secret_version": 2,
10994
+ "health": "failing",
10995
+ "created_at": "2026-08-06T12:00:00.000Z",
10996
+ "updated_at": "2026-08-06T14:00:00.000Z",
10997
+ "verification_attempted_at": "2026-08-06T12:05:00.000Z",
10998
+ "verified_at": "2026-08-06T12:05:00.000Z",
10999
+ "rotated_at": "2026-08-06T13:00:00.000Z",
11000
+ "last_success_at": "2026-08-06T13:30:00.000Z",
11001
+ "last_failure": {
11002
+ "at": "2026-08-06T14:00:00.000Z",
11003
+ "category": "network_failure"
11004
+ },
11005
+ "diagnostic_id": "diag_endpoint_list_001"
11006
+ }
11007
+ ],
11008
+ "total": 1,
11009
+ "page": 1,
11010
+ "page_size": 50
11011
+ }
11012
+ }
11013
+ }
11014
+ }
11015
+ }
11016
+ },
11017
+ "401": { "$ref": "#/components/responses/Unauthorized" },
11018
+ "403": { "$ref": "#/components/responses/Forbidden" },
11019
+ "404": { "$ref": "#/components/responses/NotFound" }
11020
+ }
11021
+ },
11022
+ "post": {
11023
+ "operationId": "createCustomerEndpointWithCredential",
11024
+ "summary": "Create an active customer endpoint and reveal its signing secret once",
11025
+ "description": "Creates an active endpoint and sends a signed diagnostic ping. A 2xx acknowledges the ping; no echo handshake is required. SSRF destination validation applies.",
11026
+ "tags": ["Customer endpoints"],
11027
+ "security": [{ "bearerCredential": [] }],
11028
+ "x-required-scopes": ["messages:write"],
11029
+ "x-idempotency": {
11030
+ "required": false,
11031
+ "retention_seconds": 604800,
11032
+ "scope": ["authenticated_environment", "operation_id", "key"],
11033
+ "equivalence": "final_normalized_url_and_validated_request",
11034
+ "replay_retention_extends": false
11035
+ },
11036
+ "x-secret-reveal": {
11037
+ "responses": 1,
11038
+ "replayable": false,
11039
+ "later_reads": "metadata_only"
11040
+ },
11041
+ "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
11042
+ "requestBody": {
11043
+ "required": true,
11044
+ "content": {
11045
+ "application/json": {
11046
+ "schema": {
11047
+ "$ref": "#/components/schemas/CreateCustomerEndpointRequest"
11048
+ },
11049
+ "examples": {
11050
+ "createEndpoint": {
11051
+ "value": {
11052
+ "url": "https://events.example.com/whooshbang",
11053
+ "event_types": [
11054
+ "interaction.received",
11055
+ "message.delivered"
11056
+ ],
11057
+ "local_test": false
11058
+ }
11059
+ }
11060
+ }
11061
+ }
11062
+ }
11063
+ },
11064
+ "responses": {
11065
+ "200": {
11066
+ "description": "Equivalent replay returning safe endpoint metadata without the secret.",
11067
+ "content": {
11068
+ "application/json": {
11069
+ "schema": {
11070
+ "$ref": "#/components/schemas/CustomerEndpointMutationResult"
11071
+ },
11072
+ "examples": {
11073
+ "replayed": {
11074
+ "value": {
11075
+ "endpoint": {
11076
+ "id": "endpoint_11111111111111111111111111111111",
11077
+ "project_id": "project_synthetic_alpha",
11078
+ "environment_id": "environment_test_synthetic_alpha",
11079
+ "environment": "test",
11080
+ "status": "unverified",
11081
+ "verification_status": "pending",
11082
+ "event_types": [
11083
+ "interaction.received",
11084
+ "message.delivered"
11085
+ ],
11086
+ "secret_version": 1,
11087
+ "health": "never_delivered",
11088
+ "created_at": "2026-08-06T12:00:00.000Z",
11089
+ "updated_at": "2026-08-06T12:00:00.000Z",
11090
+ "diagnostic_id": "diag_endpoint_replay_001"
11091
+ }
11092
+ }
11093
+ }
11094
+ }
11095
+ }
11096
+ }
11097
+ },
11098
+ "201": {
11099
+ "description": "Newly active endpoint, recorded signed ping result, and its one-time signing secret.",
11100
+ "headers": {
11101
+ "WhooshBang-Diagnostic-Id": {
11102
+ "$ref": "#/components/headers/DiagnosticId"
11103
+ }
11104
+ },
11105
+ "content": {
11106
+ "application/json": {
11107
+ "schema": {
11108
+ "$ref": "#/components/schemas/CustomerEndpointMutationResult"
11109
+ },
11110
+ "examples": {
11111
+ "created": {
11112
+ "value": {
11113
+ "endpoint": {
11114
+ "id": "endpoint_11111111111111111111111111111111",
11115
+ "project_id": "project_synthetic_alpha",
11116
+ "environment_id": "environment_test_synthetic_alpha",
11117
+ "environment": "test",
11118
+ "status": "unverified",
11119
+ "verification_status": "pending",
11120
+ "event_types": [
11121
+ "interaction.received",
11122
+ "message.delivered"
11123
+ ],
11124
+ "secret_version": 1,
11125
+ "health": "never_delivered",
11126
+ "created_at": "2026-08-06T12:00:00.000Z",
11127
+ "updated_at": "2026-08-06T12:00:00.000Z",
11128
+ "diagnostic_id": "diag_endpoint_create_001"
11129
+ },
11130
+ "secret": "whsec_AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="
11131
+ }
11132
+ }
11133
+ }
11134
+ }
11135
+ }
11136
+ },
11137
+ "400": { "$ref": "#/components/responses/BadRequest" },
11138
+ "401": { "$ref": "#/components/responses/Unauthorized" },
11139
+ "403": { "$ref": "#/components/responses/Forbidden" },
11140
+ "404": { "$ref": "#/components/responses/NotFound" },
11141
+ "409": { "$ref": "#/components/responses/Conflict" },
11142
+ "503": { "$ref": "#/components/responses/ProviderRetryable" }
11143
+ }
11144
+ }
11145
+ },
11146
+ "/v1/customer-endpoints/{endpoint_id}": {
11147
+ "parameters": [{ "$ref": "#/components/parameters/CustomerEndpointId" }],
11148
+ "get": {
11149
+ "operationId": "getCustomerEndpointWithCredential",
11150
+ "summary": "Read safe lifecycle and health metadata for one endpoint",
11151
+ "tags": ["Customer endpoints"],
11152
+ "security": [{ "bearerCredential": [] }],
11153
+ "x-required-scopes": ["messages:read"],
11154
+ "responses": {
11155
+ "200": {
11156
+ "description": "Safe endpoint metadata.",
11157
+ "content": {
11158
+ "application/json": {
11159
+ "schema": { "$ref": "#/components/schemas/CustomerEndpoint" }
11160
+ }
11161
+ }
11162
+ },
11163
+ "401": { "$ref": "#/components/responses/Unauthorized" },
11164
+ "403": { "$ref": "#/components/responses/Forbidden" },
11165
+ "404": { "$ref": "#/components/responses/NotFound" }
11166
+ }
11167
+ }
11168
+ },
11169
+ "/v1/customer-endpoints/{endpoint_id}/verify": {
11170
+ "parameters": [{ "$ref": "#/components/parameters/CustomerEndpointId" }],
11171
+ "post": {
11172
+ "operationId": "verifyCustomerEndpointWithCredential",
11173
+ "summary": "Send a signed test ping and record whether the receiver returns 2xx",
11174
+ "tags": ["Customer endpoints"],
11175
+ "security": [{ "bearerCredential": [] }],
11176
+ "x-required-scopes": ["messages:write"],
11177
+ "responses": {
11178
+ "200": {
11179
+ "description": "Verified active endpoint metadata.",
11180
+ "content": {
11181
+ "application/json": {
11182
+ "schema": { "$ref": "#/components/schemas/CustomerEndpoint" }
11183
+ }
11184
+ }
11185
+ },
11186
+ "401": { "$ref": "#/components/responses/Unauthorized" },
11187
+ "403": { "$ref": "#/components/responses/Forbidden" },
11188
+ "404": { "$ref": "#/components/responses/NotFound" },
11189
+ "502": { "$ref": "#/components/responses/ProviderTerminal" },
11190
+ "503": { "$ref": "#/components/responses/ProviderRetryable" }
11191
+ }
11192
+ }
11193
+ },
11194
+ "/v1/customer-endpoints/{endpoint_id}/rotate": {
11195
+ "parameters": [{ "$ref": "#/components/parameters/CustomerEndpointId" }],
11196
+ "post": {
11197
+ "operationId": "rotateCustomerEndpointWithCredential",
11198
+ "summary": "Create a new signing version with bounded old/new overlap",
11199
+ "tags": ["Customer endpoints"],
11200
+ "security": [{ "bearerCredential": [] }],
11201
+ "x-required-scopes": ["messages:write"],
11202
+ "x-idempotency": {
11203
+ "required": false,
11204
+ "retention_seconds": 604800,
11205
+ "scope": ["authenticated_environment", "operation_id", "key"],
11206
+ "equivalence": "endpoint_and_validated_request",
11207
+ "replay_retention_extends": false
11208
+ },
11209
+ "x-secret-reveal": {
11210
+ "responses": 1,
11211
+ "replayable": false,
11212
+ "later_reads": "metadata_only"
11213
+ },
11214
+ "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
11215
+ "requestBody": {
11216
+ "required": true,
11217
+ "content": {
11218
+ "application/json": {
11219
+ "schema": {
11220
+ "$ref": "#/components/schemas/RotateCustomerEndpointRequest"
11221
+ },
11222
+ "examples": { "rotate": { "value": { "overlap_seconds": 3600 } } }
11223
+ }
11224
+ }
11225
+ },
11226
+ "responses": {
11227
+ "200": {
11228
+ "description": "Equivalent replay without another secret reveal.",
11229
+ "content": {
11230
+ "application/json": {
11231
+ "schema": {
11232
+ "$ref": "#/components/schemas/CustomerEndpointMutationResult"
11233
+ }
11234
+ }
11235
+ }
11236
+ },
11237
+ "201": {
11238
+ "description": "Rotated endpoint and one-time new secret.",
11239
+ "content": {
11240
+ "application/json": {
11241
+ "schema": {
11242
+ "$ref": "#/components/schemas/CustomerEndpointMutationResult"
11243
+ },
11244
+ "examples": {
11245
+ "rotated": {
11246
+ "value": {
11247
+ "endpoint": {
11248
+ "id": "endpoint_11111111111111111111111111111111",
11249
+ "project_id": "project_synthetic_alpha",
11250
+ "environment_id": "environment_test_synthetic_alpha",
11251
+ "environment": "test",
11252
+ "status": "active",
11253
+ "verification_status": "verified",
11254
+ "event_types": [
11255
+ "interaction.received",
11256
+ "message.delivered"
11257
+ ],
11258
+ "secret_version": 2,
11259
+ "health": "healthy",
11260
+ "created_at": "2026-08-06T12:00:00.000Z",
11261
+ "updated_at": "2026-08-06T13:00:00.000Z",
11262
+ "verification_attempted_at": "2026-08-06T12:05:00.000Z",
11263
+ "verified_at": "2026-08-06T12:05:00.000Z",
11264
+ "rotated_at": "2026-08-06T13:00:00.000Z",
11265
+ "last_success_at": "2026-08-06T12:30:00.000Z",
11266
+ "diagnostic_id": "diag_endpoint_rotate_001"
11267
+ },
11268
+ "secret": "whsec_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
11269
+ }
11270
+ }
11271
+ }
11272
+ }
11273
+ }
11274
+ },
11275
+ "400": { "$ref": "#/components/responses/BadRequest" },
11276
+ "401": { "$ref": "#/components/responses/Unauthorized" },
11277
+ "403": { "$ref": "#/components/responses/Forbidden" },
11278
+ "404": { "$ref": "#/components/responses/NotFound" },
11279
+ "409": { "$ref": "#/components/responses/EndpointRotationConflict" }
11280
+ }
11281
+ }
11282
+ },
11283
+ "/v1/customer-endpoints/{endpoint_id}/{action}": {
11284
+ "parameters": [
11285
+ { "$ref": "#/components/parameters/CustomerEndpointId" },
11286
+ {
11287
+ "name": "action",
11288
+ "in": "path",
11289
+ "required": true,
11290
+ "schema": { "type": "string", "enum": ["pause", "resume", "disable"] }
11291
+ }
11292
+ ],
11293
+ "post": {
11294
+ "operationId": "transitionCustomerEndpointWithCredential",
11295
+ "summary": "Pause, resume, or terminally disable an endpoint",
11296
+ "tags": ["Customer endpoints"],
11297
+ "security": [{ "bearerCredential": [] }],
11298
+ "x-required-scopes": ["messages:write"],
11299
+ "x-repeat-semantics": {
11300
+ "same_target": "idempotent",
11301
+ "secret_returned": false
11302
+ },
11303
+ "responses": {
11304
+ "200": {
11305
+ "description": "Updated safe endpoint metadata.",
11306
+ "content": {
11307
+ "application/json": {
11308
+ "schema": { "$ref": "#/components/schemas/CustomerEndpoint" }
11309
+ }
11310
+ }
11311
+ },
11312
+ "400": { "$ref": "#/components/responses/BadRequest" },
11313
+ "401": { "$ref": "#/components/responses/Unauthorized" },
11314
+ "403": { "$ref": "#/components/responses/Forbidden" },
11315
+ "404": { "$ref": "#/components/responses/NotFound" }
11316
+ },
11317
+ "description": "Creates an active endpoint and sends a signed diagnostic ping. A 2xx acknowledges the ping; no echo handshake is required. SSRF destination validation applies."
11318
+ }
11319
+ },
11320
+ "/v1/customer-endpoints/{endpoint_id}/attempts": {
11321
+ "parameters": [{ "$ref": "#/components/parameters/CustomerEndpointId" }],
11322
+ "get": {
11323
+ "operationId": "listCustomerEndpointAttemptsWithCredential",
11324
+ "summary": "Inspect protected content-free delivery diagnostics",
11325
+ "tags": ["Customer endpoints"],
11326
+ "security": [{ "bearerCredential": [] }],
11327
+ "x-required-scopes": ["messages:read"],
11328
+ "parameters": [
11329
+ { "$ref": "#/components/parameters/ListAfterCursor" },
11330
+ { "$ref": "#/components/parameters/ListPageLimit" },
11331
+ { "$ref": "#/components/parameters/PageNumber" }
11332
+ ],
11333
+ "responses": {
11334
+ "200": {
11335
+ "description": "Safe status, timing, and failure categories only.",
11336
+ "content": {
11337
+ "application/json": {
11338
+ "schema": {
11339
+ "$ref": "#/components/schemas/CustomerEndpointAttemptCollection"
11340
+ },
11341
+ "examples": {
11342
+ "attempts": {
11343
+ "value": {
11344
+ "endpoint_id": "endpoint_11111111111111111111111111111111",
11345
+ "attempts": [
11346
+ {
11347
+ "id": "ceattempt_11111111111111111111111111111111",
11348
+ "event_id": "evt_interaction_received_synthetic",
11349
+ "attempt": 1,
11350
+ "status": "terminal_failed",
11351
+ "diagnostic_code": "network_failure",
11352
+ "started_at": "2026-08-06T14:00:00.000Z",
11353
+ "completed_at": "2026-08-06T14:00:01.000Z",
11354
+ "created_at": "2026-08-06T14:00:00.000Z"
11355
+ }
11356
+ ],
11357
+ "total": 1,
11358
+ "page": 1,
11359
+ "page_size": 50
11360
+ }
11361
+ }
11362
+ }
11363
+ }
11364
+ }
11365
+ },
11366
+ "401": { "$ref": "#/components/responses/Unauthorized" },
11367
+ "403": { "$ref": "#/components/responses/Forbidden" },
11368
+ "404": { "$ref": "#/components/responses/NotFound" }
11369
+ }
11370
+ }
11371
+ }
11372
+ },
11373
+ "components": {
11374
+ "securitySchemes": {
11375
+ "clerkSession": {
11376
+ "type": "http",
11377
+ "scheme": "bearer",
11378
+ "bearerFormat": "Clerk session JWT",
11379
+ "description": "A verified human session whose active Clerk organization selects exactly\none WhooshBang organization. Every administration operation rechecks the\nprojected organization membership; session identity never selects a project\nor environment from request data.\n"
11380
+ },
11381
+ "bearerGrant": {
11382
+ "type": "http",
11383
+ "scheme": "bearer",
11384
+ "bearerFormat": "OAuth 2.0 access token",
11385
+ "description": "An access token issued by the WhooshBang authorization server for one\nconsenting organization member. It selects exactly one organization and carries\nthe scopes that member consented to, and never a project: an operation\nnames its project and environment in the path, and the service checks\nboth against the granting person's membership before acting. A project\ncredential can name no project but its own; an organization credential names\none per call the same way this does.\n"
11386
+ },
11387
+ "bearerOrganizationCredential": {
11388
+ "type": "http",
11389
+ "scheme": "bearer",
11390
+ "bearerFormat": "wb_oc1.ocred_<16-byte-base64url>.<32-byte-base64url-secret>",
11391
+ "description": "Selects exactly one organization and nothing below it. It names no project\nand no environment, so it authenticates only operations whose path does,\nand its authority there is the intersection of its scopes with the\nproject-credential vocabulary — never wider than a project credential\nwould have been for the same project environment. A path naming a\nproject outside its organization is refused with `credential_organization_mismatch`\nrather than a bare scope failure. `organization:read` is the one scope it can\nhold that a project credential cannot, and it authorizes only the\norganization's project directory: listing the organization's projects and reading\none, which is the organization-wide question a project credential cannot ask\nand the reason a `--project <slug>` selector can work at all. It is\nrequired *in addition to* each of those operations' own\n`x-required-scopes`, never instead of them.\nCredentials cannot be read after creation.\n"
11392
+ },
11393
+ "bearerCredential": {
11394
+ "type": "http",
11395
+ "scheme": "bearer",
11396
+ "bearerFormat": "wb_pc1.pcred_<16-byte-base64url>.<32-byte-base64url-secret>",
11397
+ "description": "Selects exactly one organization, project, and `test` or `live` environment.\nCredentials cannot be read after creation. Except for explicitly\nsynthetic creation/rotation reveal fixtures, complete bearer values\nmust never appear in fixtures, errors, logs, metrics, or inspection\nsurfaces.\n"
11398
+ },
11399
+ "machineCredential": {
11400
+ "type": "http",
11401
+ "scheme": "bearer",
11402
+ "bearerFormat": "wb_mc1.mcred_<16-byte-base64url>.<32-byte-base64url-secret>",
11403
+ "description": "A locally generated runtime credential bound server-side to exactly one\nproject/environment, machine client, notifier, subscriber, binding, and\nevent stream. Project credentials cannot use machine-only endpoints.\nRevoked credentials fail before resource lookup.\n"
11404
+ },
11405
+ "recipientSessionCapability": {
11406
+ "type": "apiKey",
11407
+ "in": "header",
11408
+ "name": "WhooshBang-Recipient-Capability",
11409
+ "description": "A creation-only, short-lived capability bound server-side to one exact\norganization, project, environment, subscriber, configuration revision,\npresenting origin, and registered return destination. It is accepted\nonly on the `/v1/recipient-session` browser surface and never as bearer\nauthority on an ordinary application route.\n"
11410
+ }
11411
+ },
11412
+ "parameters": {
11413
+ "IdempotencyKey": {
11414
+ "name": "Idempotency-Key",
11415
+ "in": "header",
11416
+ "required": false,
11417
+ "description": "Optional visible ASCII key retained for seven days from first acceptance.\nA retry with the same key returns the original result and never acts twice.\nWithout a key each request is new.\nEquivalent replay returns the original status and logical resource;\nmaterially different validated input returns `idempotency_conflict`.\n",
11418
+ "schema": { "$ref": "#/components/schemas/IdempotencyKey" }
11419
+ },
11420
+ "SubscriptionLinkId": {
11421
+ "name": "subscription_link_id",
11422
+ "in": "path",
11423
+ "required": true,
11424
+ "description": "Opaque subscription-link identity; never authorization.",
11425
+ "schema": { "$ref": "#/components/schemas/OpaqueIdentifier" }
11426
+ },
11427
+ "ProjectId": {
11428
+ "name": "project_id",
11429
+ "in": "path",
11430
+ "required": true,
11431
+ "description": "Opaque project identity; never authorization.",
11432
+ "schema": { "$ref": "#/components/schemas/OpaqueIdentifier" }
11433
+ },
11434
+ "EnvironmentId": {
11435
+ "name": "environment_id",
11436
+ "in": "path",
11437
+ "required": true,
11438
+ "description": "Opaque project-environment identity; never authorization.",
10870
11439
  "schema": { "$ref": "#/components/schemas/OpaqueIdentifier" }
10871
11440
  },
10872
11441
  "ApiCredentialId": {
@@ -11218,13 +11787,13 @@
11218
11787
  "CreateOperatorMessageRequest": {
11219
11788
  "type": "object",
11220
11789
  "description": "Send to a teammate by exact name or operator ID, or use me. Omitted recipient means the verified authenticated human only. Shared and service credentials must name a recipient. Preferred channel, service-email billing and marketing restrictions still apply.",
11221
- "required": ["content"],
11790
+ "required": [],
11222
11791
  "properties": {
11223
11792
  "recipient": { "type": "string", "minLength": 1, "maxLength": 128 },
11224
- "content": { "$ref": "#/components/schemas/MessageContent" },
11793
+ "content": { "$ref": "#/components/schemas/MessageContentInput" },
11225
11794
  "expires_at": {
11226
11795
  "$ref": "#/components/schemas/Timestamp",
11227
- "description": "Optional server-authoritative expiry no more than 24 hours after acceptance."
11796
+ "description": "Optional server-authoritative expiry after acceptance, through year 9999. Omit for 24 hours."
11228
11797
  },
11229
11798
  "correlation_id": {
11230
11799
  "type": "string",
@@ -11241,7 +11810,31 @@
11241
11810
  "description": "Omitted means service. Marketing refuses service-only bindings before charging and at every delayed dispatch."
11242
11811
  }
11243
11812
  },
11244
- "additionalProperties": false
11813
+ "additionalProperties": false,
11814
+ "allOf": [
11815
+ {
11816
+ "anyOf": [
11817
+ {
11818
+ "required": ["content"],
11819
+ "properties": {
11820
+ "content": {
11821
+ "$ref": "#/components/schemas/MessageContentInput"
11822
+ }
11823
+ },
11824
+ "additionalProperties": true
11825
+ },
11826
+ {
11827
+ "required": ["interaction"],
11828
+ "properties": {
11829
+ "interaction": {
11830
+ "$ref": "#/components/schemas/InteractionSendRequest"
11831
+ }
11832
+ },
11833
+ "additionalProperties": true
11834
+ }
11835
+ ]
11836
+ }
11837
+ ]
11245
11838
  },
11246
11839
  "OperatorMessageResult": {
11247
11840
  "type": "object",
@@ -12865,6 +13458,202 @@
12865
13458
  "additionalProperties": false,
12866
13459
  "description": "Replace the requested active project selection with owned, active projects within current paid capacity. An empty selection releases all slots."
12867
13460
  },
13461
+ "BillingPromotions": {
13462
+ "type": "object",
13463
+ "required": [
13464
+ "promotional_credit_usd",
13465
+ "free_shared_emails",
13466
+ "branding_removed_until",
13467
+ "promotions"
13468
+ ],
13469
+ "properties": {
13470
+ "promotional_credit_usd": {
13471
+ "type": "string",
13472
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13473
+ "maxLength": 40,
13474
+ "description": "Unexpired promotional message credit available now."
13475
+ },
13476
+ "free_shared_emails": {
13477
+ "type": "integer",
13478
+ "minimum": 0,
13479
+ "maximum": 2147483647,
13480
+ "description": "Unexpired free shared emails available now."
13481
+ },
13482
+ "branding_removed_until": {
13483
+ "anyOf": [
13484
+ { "type": "string", "format": "date-time" },
13485
+ { "type": "null" }
13486
+ ],
13487
+ "description": "When the latest active branding promotion ends, if one is active."
13488
+ },
13489
+ "promotions": {
13490
+ "type": "array",
13491
+ "items": { "$ref": "#/components/schemas/BillingPromotion" },
13492
+ "maxItems": 200
13493
+ }
13494
+ },
13495
+ "additionalProperties": false,
13496
+ "description": "An organization's promotions: rewards WhooshBang gave it and promotion codes it redeemed, newest first."
13497
+ },
13498
+ "BillingPromotion": {
13499
+ "type": "object",
13500
+ "required": [
13501
+ "id",
13502
+ "source",
13503
+ "name",
13504
+ "status",
13505
+ "starts_at",
13506
+ "ends_at",
13507
+ "ended_at",
13508
+ "benefit"
13509
+ ],
13510
+ "properties": {
13511
+ "id": { "type": "string", "pattern": "^grn_[0-9a-z]{26}$" },
13512
+ "source": {
13513
+ "type": "string",
13514
+ "enum": ["whooshbang", "code"],
13515
+ "description": "`whooshbang` for a reward WhooshBang gave the organization directly; `code` for a promotion code the organization redeemed."
13516
+ },
13517
+ "name": {
13518
+ "anyOf": [
13519
+ { "type": "string", "minLength": 1, "maxLength": 120 },
13520
+ { "type": "null" }
13521
+ ],
13522
+ "description": "The promotion's name for a redeemed code. Null for a direct reward."
13523
+ },
13524
+ "status": {
13525
+ "type": "string",
13526
+ "enum": ["active", "pending", "expired", "used", "revoked"],
13527
+ "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."
13528
+ },
13529
+ "starts_at": { "type": "string", "format": "date-time" },
13530
+ "ends_at": {
13531
+ "anyOf": [
13532
+ { "type": "string", "format": "date-time" },
13533
+ { "type": "null" }
13534
+ ],
13535
+ "description": "When the benefit stops. Null for a discount, which ends when it is used or, for a plan discount, after its months."
13536
+ },
13537
+ "ended_at": {
13538
+ "anyOf": [
13539
+ { "type": "string", "format": "date-time" },
13540
+ { "type": "null" }
13541
+ ],
13542
+ "description": "When the promotion was used or withdrawn, if it was."
13543
+ },
13544
+ "benefit": {
13545
+ "oneOf": [
13546
+ {
13547
+ "type": "object",
13548
+ "required": ["kind", "credit_usd", "remaining_usd"],
13549
+ "properties": {
13550
+ "kind": { "const": "message_credit" },
13551
+ "credit_usd": {
13552
+ "type": "string",
13553
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13554
+ "maxLength": 40
13555
+ },
13556
+ "remaining_usd": {
13557
+ "type": "string",
13558
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13559
+ "maxLength": 40
13560
+ }
13561
+ },
13562
+ "additionalProperties": false,
13563
+ "description": "Promotional credit for messages. Spent before purchased, bonus and welcome credit; it never pays the shared-email charge."
13564
+ },
13565
+ {
13566
+ "type": "object",
13567
+ "required": ["kind", "shared_emails", "remaining"],
13568
+ "properties": {
13569
+ "kind": { "const": "shared_emails" },
13570
+ "shared_emails": {
13571
+ "type": "integer",
13572
+ "minimum": 1,
13573
+ "maximum": 1000000
13574
+ },
13575
+ "remaining": {
13576
+ "type": "integer",
13577
+ "minimum": 0,
13578
+ "maximum": 1000000
13579
+ }
13580
+ },
13581
+ "additionalProperties": false,
13582
+ "description": "Emails through WhooshBang's shared email service with no shared-email charge. The message itself is still paid for."
13583
+ },
13584
+ {
13585
+ "type": "object",
13586
+ "required": ["kind"],
13587
+ "properties": { "kind": { "const": "branding" } },
13588
+ "additionalProperties": false,
13589
+ "description": "WhooshBang branding can be removed from recipient messages until ends_at."
13590
+ },
13591
+ {
13592
+ "type": "object",
13593
+ "required": [
13594
+ "kind",
13595
+ "applies_to",
13596
+ "percent_off",
13597
+ "amount_off_usd",
13598
+ "duration",
13599
+ "months"
13600
+ ],
13601
+ "properties": {
13602
+ "kind": { "const": "discount" },
13603
+ "applies_to": {
13604
+ "type": "string",
13605
+ "enum": ["credit_packs", "subscriptions"]
13606
+ },
13607
+ "percent_off": {
13608
+ "anyOf": [
13609
+ { "type": "integer", "minimum": 1, "maximum": 90 },
13610
+ { "type": "null" }
13611
+ ]
13612
+ },
13613
+ "amount_off_usd": {
13614
+ "anyOf": [
13615
+ {
13616
+ "type": "string",
13617
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13618
+ "maxLength": 40
13619
+ },
13620
+ { "type": "null" }
13621
+ ]
13622
+ },
13623
+ "duration": {
13624
+ "type": "string",
13625
+ "enum": ["once", "repeating"]
13626
+ },
13627
+ "months": {
13628
+ "anyOf": [
13629
+ { "type": "integer", "minimum": 1, "maximum": 12 },
13630
+ { "type": "null" }
13631
+ ]
13632
+ }
13633
+ },
13634
+ "additionalProperties": false,
13635
+ "description": "Money off the next credit pack, or off plan and add-on charges for one charge or a number of months."
13636
+ }
13637
+ ]
13638
+ }
13639
+ },
13640
+ "additionalProperties": false,
13641
+ "description": "One promotion an organization holds. Promotional value has no cash value, is never refunded and cannot move to another organization."
13642
+ },
13643
+ "RedeemBillingPromotionRequest": {
13644
+ "type": "object",
13645
+ "required": ["code"],
13646
+ "properties": {
13647
+ "code": {
13648
+ "type": "string",
13649
+ "minLength": 4,
13650
+ "maxLength": 64,
13651
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9 _-]*$",
13652
+ "description": "The promotion code. Case, spaces, hyphens and underscores are ignored."
13653
+ }
13654
+ },
13655
+ "additionalProperties": false
13656
+ },
12868
13657
  "BillingEntitlements": {
12869
13658
  "type": "object",
12870
13659
  "required": [
@@ -13029,6 +13818,8 @@
13029
13818
  "monthly_price_usd",
13030
13819
  "charge_now_usd",
13031
13820
  "unused_time_credit_usd",
13821
+ "promotion",
13822
+ "renewal_promotion",
13032
13823
  "effective_at",
13033
13824
  "expires_at",
13034
13825
  "client_secret",
@@ -13084,6 +13875,85 @@
13084
13875
  ],
13085
13876
  "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."
13086
13877
  },
13878
+ "promotion": {
13879
+ "anyOf": [
13880
+ {
13881
+ "type": "object",
13882
+ "required": [
13883
+ "percent_off",
13884
+ "duration",
13885
+ "months",
13886
+ "discount_usd"
13887
+ ],
13888
+ "properties": {
13889
+ "percent_off": {
13890
+ "type": "integer",
13891
+ "minimum": 1,
13892
+ "maximum": 90
13893
+ },
13894
+ "duration": {
13895
+ "type": "string",
13896
+ "enum": ["once", "repeating"]
13897
+ },
13898
+ "months": {
13899
+ "anyOf": [
13900
+ { "type": "integer", "minimum": 1, "maximum": 12 },
13901
+ { "type": "null" }
13902
+ ]
13903
+ },
13904
+ "discount_usd": {
13905
+ "type": "string",
13906
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13907
+ "maxLength": 40,
13908
+ "description": "What the promotion takes off charge_now_usd."
13909
+ }
13910
+ },
13911
+ "additionalProperties": false
13912
+ },
13913
+ { "type": "null" }
13914
+ ],
13915
+ "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."
13916
+ },
13917
+ "renewal_promotion": {
13918
+ "anyOf": [
13919
+ {
13920
+ "type": "object",
13921
+ "required": [
13922
+ "percent_off",
13923
+ "duration",
13924
+ "renewal_price_usd",
13925
+ "ends_at"
13926
+ ],
13927
+ "properties": {
13928
+ "percent_off": {
13929
+ "type": "integer",
13930
+ "minimum": 1,
13931
+ "maximum": 90
13932
+ },
13933
+ "duration": {
13934
+ "type": "string",
13935
+ "enum": ["once", "repeating"]
13936
+ },
13937
+ "renewal_price_usd": {
13938
+ "type": "string",
13939
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13940
+ "maxLength": 40,
13941
+ "description": "What the next monthly charge for this selection comes to with the promotion: monthly_price_usd less its percentage."
13942
+ },
13943
+ "ends_at": {
13944
+ "anyOf": [
13945
+ { "type": "string", "format": "date-time" },
13946
+ { "type": "null" }
13947
+ ],
13948
+ "description": "When a repeating promotion stops applying. Null for a once promotion, which applies to the next monthly charge only."
13949
+ }
13950
+ },
13951
+ "additionalProperties": false
13952
+ },
13953
+ { "type": "null" }
13954
+ ],
13955
+ "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."
13956
+ },
13087
13957
  "effective_at": {
13088
13958
  "anyOf": [
13089
13959
  { "type": "string", "format": "date-time" },
@@ -13416,6 +14286,12 @@
13416
14286
  "purchase_cents": {
13417
14287
  "type": "integer",
13418
14288
  "enum": [500, 1000, 2500, 5000, 10000]
14289
+ },
14290
+ "approved_charge_usd": {
14291
+ "type": "string",
14292
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
14293
+ "maxLength": 40,
14294
+ "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."
13419
14295
  }
13420
14296
  },
13421
14297
  "description": "Buy one credit pack now with the default card. The amount is the explicit approval of the charge."
@@ -13427,6 +14303,8 @@
13427
14303
  "id",
13428
14304
  "purchase_cents",
13429
14305
  "credit_usd",
14306
+ "charged_usd",
14307
+ "discount_usd",
13430
14308
  "status",
13431
14309
  "failure",
13432
14310
  "client_secret",
@@ -13447,6 +14325,18 @@
13447
14325
  "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
13448
14326
  "maxLength": 40
13449
14327
  },
14328
+ "charged_usd": {
14329
+ "type": "string",
14330
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
14331
+ "maxLength": 40,
14332
+ "description": "What the card is charged: the pack price less any promotional discount. The pack's credit is unchanged."
14333
+ },
14334
+ "discount_usd": {
14335
+ "type": "string",
14336
+ "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$",
14337
+ "maxLength": 40,
14338
+ "description": "The promotional discount taken off the pack price, or 0.000000."
14339
+ },
13450
14340
  "status": {
13451
14341
  "type": "string",
13452
14342
  "enum": ["processing", "requires_action", "paid", "failed"]
@@ -14798,7 +15688,7 @@
14798
15688
  "CreateCustomerEndpointRequest": {
14799
15689
  "type": "object",
14800
15690
  "description": "Creates one project/environment endpoint. The service validates the final normalized destination and never returns it from health or diagnostic reads.",
14801
- "required": ["url", "event_types"],
15691
+ "required": ["url"],
14802
15692
  "properties": {
14803
15693
  "url": {
14804
15694
  "type": "string",
@@ -14824,12 +15714,13 @@
14824
15714
  },
14825
15715
  "RotateCustomerEndpointRequest": {
14826
15716
  "type": "object",
14827
- "required": ["overlap_seconds"],
15717
+ "required": [],
14828
15718
  "properties": {
14829
15719
  "overlap_seconds": {
14830
15720
  "type": "integer",
14831
15721
  "minimum": 60,
14832
- "maximum": 86400
15722
+ "maximum": 86400,
15723
+ "default": 86400
14833
15724
  }
14834
15725
  },
14835
15726
  "additionalProperties": false
@@ -15330,7 +16221,7 @@
15330
16221
  "CreateSubscriptionLinkRequest": {
15331
16222
  "type": "object",
15332
16223
  "description": "Creates one project- and environment-scoped, single-use authorization link.",
15333
- "required": ["subscriber_id", "channels"],
16224
+ "required": ["subscriber_id"],
15334
16225
  "properties": {
15335
16226
  "subscriber_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
15336
16227
  "notifier_id": {
@@ -15346,7 +16237,7 @@
15346
16237
  },
15347
16238
  "connection_id": {
15348
16239
  "$ref": "#/components/schemas/OpaqueIdentifier",
15349
- "description": "The exact channel connection to authorize. Required for Slack. For Telegram or email, omission resolves an unambiguous default or sole compatible connection; it never selects an arbitrary fallback."
16240
+ "description": "Optional exact connection. Otherwise the default or only compatible connection is used."
15350
16241
  },
15351
16242
  "address": {
15352
16243
  "$ref": "#/components/schemas/EmailAddress",
@@ -15381,16 +16272,6 @@
15381
16272
  {
15382
16273
  "if": { "properties": { "address": {} }, "required": ["address"] },
15383
16274
  "then": { "properties": { "channels": { "const": ["email"] } } }
15384
- },
15385
- {
15386
- "if": {
15387
- "properties": { "channels": { "const": ["slack"] } },
15388
- "required": ["channels"]
15389
- },
15390
- "then": {
15391
- "required": ["connection_id"],
15392
- "properties": { "connection_id": {} }
15393
- }
15394
16275
  }
15395
16276
  ],
15396
16277
  "additionalProperties": false
@@ -15509,9 +16390,14 @@
15509
16390
  "CreateMessageRequest": {
15510
16391
  "type": "object",
15511
16392
  "description": "Creates one logical message for one subscriber and one resolved channel binding.",
15512
- "required": ["to", "content"],
16393
+ "required": ["to"],
15513
16394
  "properties": {
15514
- "to": { "$ref": "#/components/schemas/MessageTarget" },
16395
+ "to": {
16396
+ "oneOf": [
16397
+ { "$ref": "#/components/schemas/MessageTarget" },
16398
+ { "$ref": "#/components/schemas/OpaqueIdentifier" }
16399
+ ]
16400
+ },
15515
16401
  "notifier_id": {
15516
16402
  "allOf": [{ "$ref": "#/components/schemas/OpaqueIdentifier" }],
15517
16403
  "default": "default"
@@ -15524,10 +16410,10 @@
15524
16410
  "allOf": [{ "$ref": "#/components/schemas/OpaqueIdentifier" }],
15525
16411
  "description": "Optional channel connection to send through, from the environment's connections. The subscriber must be connected through it; the service never substitutes another connection or provider. A connection the subscriber has not connected through is refused as `subscriber_not_connected` when they are connected through another, and as an unchosen send would be (`subscriber_unbound` or `preferred_binding_unavailable`) when no connected channel can take the message. Omitted, the service chooses: the subscriber's preferred channel when they set one, otherwise the default connection when they are connected there, otherwise the connection they connected through most recently. The accepted message's `binding.connection_id` records the connection used. Implies `delivery_target: connection`."
15526
16412
  },
15527
- "content": { "$ref": "#/components/schemas/MessageContent" },
16413
+ "content": { "$ref": "#/components/schemas/MessageContentInput" },
15528
16414
  "expires_at": {
15529
16415
  "$ref": "#/components/schemas/Timestamp",
15530
- "description": "Optional server-authoritative expiry no more than 24 hours after acceptance."
16416
+ "description": "Optional server-authoritative expiry after acceptance, through year 9999. Omit for 24 hours."
15531
16417
  },
15532
16418
  "correlation_id": {
15533
16419
  "type": "string",
@@ -15553,6 +16439,28 @@
15553
16439
  "then": {
15554
16440
  "properties": { "delivery_target": { "const": "connection" } }
15555
16441
  }
16442
+ },
16443
+ {
16444
+ "anyOf": [
16445
+ {
16446
+ "required": ["content"],
16447
+ "properties": {
16448
+ "content": {
16449
+ "$ref": "#/components/schemas/MessageContentInput"
16450
+ }
16451
+ },
16452
+ "additionalProperties": true
16453
+ },
16454
+ {
16455
+ "required": ["interaction"],
16456
+ "properties": {
16457
+ "interaction": {
16458
+ "$ref": "#/components/schemas/InteractionSendRequest"
16459
+ }
16460
+ },
16461
+ "additionalProperties": true
16462
+ }
16463
+ ]
15556
16464
  }
15557
16465
  ],
15558
16466
  "additionalProperties": false
@@ -16086,7 +16994,9 @@
16086
16994
  "type": {
16087
16995
  "const": "https://whooshbang.flowxo.com/problems/idempotency_key_required"
16088
16996
  },
16089
- "title": { "const": "Idempotency key required" },
16997
+ "title": {
16998
+ "enum": ["Invalid idempotency key", "Idempotency key required"]
16999
+ },
16090
17000
  "status": { "const": 400 },
16091
17001
  "code": { "const": "idempotency_key_required" },
16092
17002
  "retryable": { "const": false }
@@ -16399,7 +17309,7 @@
16399
17309
  "additionalProperties": false
16400
17310
  },
16401
17311
  "InteractionSendRequest": {
16402
- "description": "Send-time interaction. Choose expires_in_seconds or expires_at; omit both for 24 hours from server acceptance. Idempotent replays retain the original resolved deadline.",
17312
+ "description": "Options infer select; otherwise confirm. Typed replies require type: input. Omit expiry for 24 hours.",
16403
17313
  "oneOf": [
16404
17314
  { "$ref": "#/components/schemas/ConfirmInteractionSendRequest" },
16405
17315
  { "$ref": "#/components/schemas/SelectInteractionSendRequest" },
@@ -17682,9 +18592,6 @@
17682
18592
  "description": "Atomically create or resolve one subscriber and assert existing consent for an exact customer-owned email connection and domain. No verification message is sent. Revocation, suppression, recipient pause, holds and unhealthy authority take precedence. An omitted source is recorded as customer.",
17683
18593
  "required": [
17684
18594
  "subscriber_id",
17685
- "notifier_id",
17686
- "connection_id",
17687
- "domain_id",
17688
18595
  "address",
17689
18596
  "consented",
17690
18597
  "consented_at"
@@ -17694,7 +18601,8 @@
17694
18601
  "$ref": "#/components/schemas/OpaqueIdentifier"
17695
18602
  },
17696
18603
  "notifier_id": {
17697
- "$ref": "#/components/schemas/OpaqueIdentifier"
18604
+ "$ref": "#/components/schemas/OpaqueIdentifier",
18605
+ "default": "default"
17698
18606
  },
17699
18607
  "connection_id": {
17700
18608
  "$ref": "#/components/schemas/OpaqueIdentifier"
@@ -17721,9 +18629,6 @@
17721
18629
  "description": "Enroll an application-verified user for service email. This is relationship evidence, never consent or WhooshBang mailbox verification. An existing prior-consent binding with the same subscriber, address, notifier, connection and domain is reused with its original evidence and status.",
17722
18630
  "required": [
17723
18631
  "subscriber_id",
17724
- "notifier_id",
17725
- "connection_id",
17726
- "domain_id",
17727
18632
  "address",
17728
18633
  "basis",
17729
18634
  "source",
@@ -17734,7 +18639,8 @@
17734
18639
  "$ref": "#/components/schemas/OpaqueIdentifier"
17735
18640
  },
17736
18641
  "notifier_id": {
17737
- "$ref": "#/components/schemas/OpaqueIdentifier"
18642
+ "$ref": "#/components/schemas/OpaqueIdentifier",
18643
+ "default": "default"
17738
18644
  },
17739
18645
  "connection_id": {
17740
18646
  "$ref": "#/components/schemas/OpaqueIdentifier"
@@ -17776,7 +18682,7 @@
17776
18682
  "type": "array",
17777
18683
  "maxItems": 50,
17778
18684
  "uniqueItems": true,
17779
- "items": { "$ref": "#/components/schemas/DeveloperKey" }
18685
+ "items": { "$ref": "#/components/schemas/GroupKey" }
17780
18686
  }
17781
18687
  },
17782
18688
  "additionalProperties": false
@@ -17848,7 +18754,7 @@
17848
18754
  "maxItems": 200,
17849
18755
  "uniqueItems": true,
17850
18756
  "description": "The keys of the static groups this subscriber belongs to in the environment asked about. Present when one subscriber is read.",
17851
- "items": { "$ref": "#/components/schemas/DeveloperKey" }
18757
+ "items": { "$ref": "#/components/schemas/GroupKey" }
17852
18758
  },
17853
18759
  "last_activity_at": {
17854
18760
  "$ref": "#/components/schemas/Timestamp",
@@ -18030,9 +18936,9 @@
18030
18936
  "CreateSubscriberGroupRequest": {
18031
18937
  "type": "object",
18032
18938
  "description": "Creates one static, developer-managed subscriber group.",
18033
- "required": ["key", "display_name"],
18939
+ "required": ["key"],
18034
18940
  "properties": {
18035
- "key": { "$ref": "#/components/schemas/DeveloperKey" },
18941
+ "key": { "$ref": "#/components/schemas/GroupKey" },
18036
18942
  "display_name": {
18037
18943
  "type": "string",
18038
18944
  "minLength": 1,
@@ -18045,8 +18951,8 @@
18045
18951
  "UpdateSubscriberGroupRequest": {
18046
18952
  "type": "object",
18047
18953
  "description": "Renames or redescribes an active group. Its key and static membership model cannot change.",
18048
- "required": ["row_version"],
18049
- "minProperties": 2,
18954
+ "required": [],
18955
+ "minProperties": 1,
18050
18956
  "properties": {
18051
18957
  "row_version": { "type": "integer", "minimum": 1 },
18052
18958
  "display_name": {
@@ -18056,7 +18962,14 @@
18056
18962
  },
18057
18963
  "description": { "type": ["string", "null"], "maxLength": 500 }
18058
18964
  },
18059
- "additionalProperties": false
18965
+ "additionalProperties": false,
18966
+ "anyOf": [
18967
+ {
18968
+ "required": ["display_name"],
18969
+ "properties": { "display_name": {} }
18970
+ },
18971
+ { "required": ["description"], "properties": { "description": {} } }
18972
+ ]
18060
18973
  },
18061
18974
  "SubscriberGroup": {
18062
18975
  "type": "object",
@@ -18083,7 +18996,7 @@
18083
18996
  "id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
18084
18997
  "project_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
18085
18998
  "environment": { "$ref": "#/components/schemas/Environment" },
18086
- "key": { "$ref": "#/components/schemas/DeveloperKey" },
18999
+ "key": { "$ref": "#/components/schemas/GroupKey" },
18087
19000
  "display_name": {
18088
19001
  "type": "string",
18089
19002
  "minLength": 1,
@@ -18150,7 +19063,7 @@
18150
19063
  ],
18151
19064
  "properties": {
18152
19065
  "group_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
18153
- "group_key": { "$ref": "#/components/schemas/DeveloperKey" },
19066
+ "group_key": { "$ref": "#/components/schemas/GroupKey" },
18154
19067
  "subscriber_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
18155
19068
  "subscriber_name": {
18156
19069
  "type": "string",
@@ -18191,7 +19104,7 @@
18191
19104
  "type": "object",
18192
19105
  "required": ["group_key", "membership_revision", "items"],
18193
19106
  "properties": {
18194
- "group_key": { "$ref": "#/components/schemas/DeveloperKey" },
19107
+ "group_key": { "$ref": "#/components/schemas/GroupKey" },
18195
19108
  "membership_revision": { "type": "integer", "minimum": 0 },
18196
19109
  "items": {
18197
19110
  "type": "array",
@@ -18215,14 +19128,17 @@
18215
19128
  "CreateGroupBroadcastRequest": {
18216
19129
  "type": "object",
18217
19130
  "description": "Creates one group broadcast using the existing ordinary message envelope. The group target comes only from the path.",
18218
- "required": ["content"],
19131
+ "required": [],
18219
19132
  "properties": {
18220
19133
  "notifier_id": {
18221
19134
  "allOf": [{ "$ref": "#/components/schemas/OpaqueIdentifier" }],
18222
19135
  "default": "default"
18223
19136
  },
18224
- "content": { "$ref": "#/components/schemas/MessageContent" },
18225
- "expires_at": { "$ref": "#/components/schemas/Timestamp" },
19137
+ "content": { "$ref": "#/components/schemas/MessageContentInput" },
19138
+ "expires_at": {
19139
+ "$ref": "#/components/schemas/Timestamp",
19140
+ "description": "Optional server-authoritative expiry after acceptance, through year 9999. Omit for 24 hours."
19141
+ },
18226
19142
  "correlation_id": {
18227
19143
  "type": "string",
18228
19144
  "minLength": 1,
@@ -18238,7 +19154,31 @@
18238
19154
  "description": "Omitted means service. Marketing refuses service-only bindings before charging and at every delayed dispatch."
18239
19155
  }
18240
19156
  },
18241
- "additionalProperties": false
19157
+ "additionalProperties": false,
19158
+ "allOf": [
19159
+ {
19160
+ "anyOf": [
19161
+ {
19162
+ "required": ["content"],
19163
+ "properties": {
19164
+ "content": {
19165
+ "$ref": "#/components/schemas/MessageContentInput"
19166
+ }
19167
+ },
19168
+ "additionalProperties": true
19169
+ },
19170
+ {
19171
+ "required": ["interaction"],
19172
+ "properties": {
19173
+ "interaction": {
19174
+ "$ref": "#/components/schemas/InteractionSendRequest"
19175
+ }
19176
+ },
19177
+ "additionalProperties": true
19178
+ }
19179
+ ]
19180
+ }
19181
+ ]
18242
19182
  },
18243
19183
  "GroupBroadcast": {
18244
19184
  "type": "object",
@@ -18266,7 +19206,7 @@
18266
19206
  "project_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
18267
19207
  "environment": { "$ref": "#/components/schemas/Environment" },
18268
19208
  "group_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
18269
- "group_key": { "$ref": "#/components/schemas/DeveloperKey" },
19209
+ "group_key": { "$ref": "#/components/schemas/GroupKey" },
18270
19210
  "audience_revision": { "type": "integer", "minimum": 0 },
18271
19211
  "status": {
18272
19212
  "type": "string",
@@ -18522,6 +19462,61 @@
18522
19462
  },
18523
19463
  "additionalProperties": false
18524
19464
  },
19465
+ "NotificationImageInput": {
19466
+ "type": "object",
19467
+ "description": "One optional provider-neutral illustrative image. The notification content remains complete without the pixels.",
19468
+ "required": ["asset_id"],
19469
+ "properties": {
19470
+ "asset_id": {
19471
+ "$ref": "#/components/schemas/NotificationImageAssetId"
19472
+ },
19473
+ "purpose": { "const": "illustration", "default": "illustration" },
19474
+ "description": {
19475
+ "type": "string",
19476
+ "minLength": 1,
19477
+ "maxLength": 1000,
19478
+ "pattern": "^(?=.*\\S)[^\\u0000-\\u001F\\u007F]+$",
19479
+ "x-max-utf8-bytes": 4000,
19480
+ "description": "Optional accessible description preserved by every adapter. Omit it for a decorative illustration; notification text must remain complete without the image."
19481
+ }
19482
+ },
19483
+ "additionalProperties": false
19484
+ },
19485
+ "TextContentInput": {
19486
+ "type": "object",
19487
+ "required": ["text"],
19488
+ "properties": {
19489
+ "type": { "const": "text" },
19490
+ "format": {
19491
+ "type": "string",
19492
+ "enum": ["plain", "markdown"],
19493
+ "description": "Text format. Omitted means plain text. Markdown supports headings, paragraphs, emphasis, strong emphasis, code, lists, quotes and safe HTTP(S) links; raw HTML and remote images are never executed or fetched."
19494
+ },
19495
+ "text": {
19496
+ "description": "The complete notification content. This remains authoritative and sufficient when an adapter cannot render optional structure.",
19497
+ "type": "string",
19498
+ "minLength": 1,
19499
+ "maxLength": 8192,
19500
+ "x-max-utf8-bytes": 8192
19501
+ },
19502
+ "title": {
19503
+ "description": "An optional semantic title. The adapter chooses its native presentation.",
19504
+ "type": "string",
19505
+ "minLength": 1,
19506
+ "maxLength": 200,
19507
+ "x-max-utf8-bytes": 800
19508
+ },
19509
+ "sections": {
19510
+ "description": "Optional ordered semantic sections that enhance, but never replace, the complete text.",
19511
+ "type": "array",
19512
+ "minItems": 1,
19513
+ "maxItems": 12,
19514
+ "items": { "$ref": "#/components/schemas/StructuredTextSection" }
19515
+ },
19516
+ "image": { "$ref": "#/components/schemas/NotificationImageInput" }
19517
+ },
19518
+ "additionalProperties": false
19519
+ },
18525
19520
  "DocumentSectionBlock": {
18526
19521
  "description": "One prose section in an ordered portable document.",
18527
19522
  "type": "object",
@@ -18655,10 +19650,10 @@
18655
19650
  { "$ref": "#/components/schemas/DocumentDetailsBlock" }
18656
19651
  ]
18657
19652
  },
18658
- "DocumentContent": {
19653
+ "DocumentContentInput": {
18659
19654
  "description": "An authoritative ordered portable notification document that every delivery adapter renders faithfully.",
18660
19655
  "type": "object",
18661
- "required": ["type", "blocks"],
19656
+ "required": ["blocks"],
18662
19657
  "properties": {
18663
19658
  "type": { "const": "document" },
18664
19659
  "title": {
@@ -18675,34 +19670,22 @@
18675
19670
  "maxItems": 24,
18676
19671
  "items": { "$ref": "#/components/schemas/DocumentBlock" }
18677
19672
  },
18678
- "image": { "$ref": "#/components/schemas/NotificationImage" }
19673
+ "image": { "$ref": "#/components/schemas/NotificationImageInput" }
18679
19674
  },
18680
19675
  "additionalProperties": false
18681
19676
  },
18682
- "MessageContent": {
18683
- "oneOf": [
18684
- { "$ref": "#/components/schemas/TextContent" },
18685
- { "$ref": "#/components/schemas/DocumentContent" }
18686
- ]
18687
- },
18688
- "MetadataValue": {
19677
+ "MessageContentInput": {
18689
19678
  "oneOf": [
18690
- { "type": "string" },
18691
- { "type": "number" },
18692
- { "type": "boolean" },
18693
- { "type": "null" }
19679
+ {
19680
+ "type": "string",
19681
+ "minLength": 1,
19682
+ "maxLength": 8192,
19683
+ "x-max-utf8-bytes": 8192
19684
+ },
19685
+ { "$ref": "#/components/schemas/TextContentInput" },
19686
+ { "$ref": "#/components/schemas/DocumentContentInput" }
18694
19687
  ]
18695
19688
  },
18696
- "Metadata": {
18697
- "type": "object",
18698
- "description": "Bounded customer correlation data. Do not include secrets or personal data.",
18699
- "maxProperties": 20,
18700
- "propertyNames": { "minLength": 1, "maxLength": 64 },
18701
- "additionalProperties": {
18702
- "$ref": "#/components/schemas/MetadataValue"
18703
- },
18704
- "x-max-json-bytes": 4096
18705
- },
18706
19689
  "InteractionPrompt": {
18707
19690
  "type": "string",
18708
19691
  "minLength": 1,
@@ -18715,14 +19698,14 @@
18715
19698
  },
18716
19699
  "InteractionCorrelationId": {
18717
19700
  "type": "string",
18718
- "minLength": 8,
19701
+ "minLength": 1,
18719
19702
  "maxLength": 128,
18720
19703
  "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
18721
19704
  "description": "Caller-owned opaque correlation returned as data, never treated as execution authority."
18722
19705
  },
18723
19706
  "ConfirmInteractionSendRequest": {
18724
19707
  "type": "object",
18725
- "required": ["type", "prompt"],
19708
+ "required": ["prompt"],
18726
19709
  "properties": {
18727
19710
  "type": { "const": "confirm" },
18728
19711
  "prompt": { "$ref": "#/components/schemas/InteractionPrompt" },
@@ -18751,30 +19734,55 @@
18751
19734
  "properties": { "expires_at": {}, "expires_in_seconds": {} }
18752
19735
  }
18753
19736
  },
18754
- "InteractionOption": {
18755
- "type": "object",
18756
- "required": ["value", "label"],
18757
- "properties": {
18758
- "value": {
19737
+ "InteractionOptionInput": {
19738
+ "oneOf": [
19739
+ {
18759
19740
  "type": "string",
18760
19741
  "minLength": 1,
18761
- "maxLength": 128,
18762
- "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
18763
- "description": "Opaque customer data. It is never interpreted as a command."
19742
+ "maxLength": 120,
19743
+ "pattern": "^[^\\u0000-\\u001F\\u007F]+$"
18764
19744
  },
18765
- "label": { "type": "string", "minLength": 1, "maxLength": 120 },
18766
- "intent": {
18767
- "type": "string",
18768
- "enum": ["accept", "destructive", "informational", "neutral"],
18769
- "default": "neutral",
18770
- "description": "What choosing this option does, so a channel can present it accordingly. Describes consequence rather than appearance: a channel renders `accept` green where it can, `destructive` red, `informational` prominent, and `neutral` plainly. A channel that cannot colour a control ignores this and the option still renders. Omitted means `neutral`."
19745
+ {
19746
+ "type": "object",
19747
+ "required": ["label"],
19748
+ "properties": {
19749
+ "value": {
19750
+ "type": "string",
19751
+ "minLength": 1,
19752
+ "maxLength": 128,
19753
+ "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
19754
+ "description": "Defaults to label. Opaque customer data, never a command."
19755
+ },
19756
+ "label": { "type": "string", "minLength": 1, "maxLength": 120 },
19757
+ "intent": {
19758
+ "type": "string",
19759
+ "enum": ["accept", "destructive", "informational", "neutral"],
19760
+ "default": "neutral",
19761
+ "description": "What choosing this option does, so a channel can present it accordingly. Describes consequence rather than appearance: a channel renders `accept` green where it can, `destructive` red, `informational` prominent, and `neutral` plainly. A channel that cannot colour a control ignores this and the option still renders. Omitted means `neutral`."
19762
+ }
19763
+ },
19764
+ "additionalProperties": false,
19765
+ "allOf": [
19766
+ {
19767
+ "if": { "properties": { "value": {} }, "required": ["value"] },
19768
+ "else": {
19769
+ "properties": {
19770
+ "label": {
19771
+ "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
19772
+ "type": "string"
19773
+ }
19774
+ }
19775
+ }
19776
+ }
19777
+ ],
19778
+ "$comment": "The conditional constraint applies only when the label is used as the value. Keep the TypeScript input shape explicit because the generator omits object properties for conditional schemas.",
19779
+ "x-typescript-type": "{ label: string; value?: string; intent?: \"accept\" | \"destructive\" | \"informational\" | \"neutral\" }"
18771
19780
  }
18772
- },
18773
- "additionalProperties": false
19781
+ ]
18774
19782
  },
18775
19783
  "SelectInteractionSendRequest": {
18776
19784
  "type": "object",
18777
- "required": ["type", "prompt", "options"],
19785
+ "required": ["prompt", "options"],
18778
19786
  "properties": {
18779
19787
  "type": { "const": "select" },
18780
19788
  "prompt": { "$ref": "#/components/schemas/InteractionPrompt" },
@@ -18782,7 +19790,7 @@
18782
19790
  "type": "array",
18783
19791
  "minItems": 2,
18784
19792
  "maxItems": 6,
18785
- "items": { "$ref": "#/components/schemas/InteractionOption" },
19793
+ "items": { "$ref": "#/components/schemas/InteractionOptionInput" },
18786
19794
  "x-unique-property": "value"
18787
19795
  },
18788
19796
  "expires_at": {
@@ -18829,6 +19837,24 @@
18829
19837
  "properties": { "expires_at": {}, "expires_in_seconds": {} }
18830
19838
  }
18831
19839
  },
19840
+ "MetadataValue": {
19841
+ "oneOf": [
19842
+ { "type": "string" },
19843
+ { "type": "number" },
19844
+ { "type": "boolean" },
19845
+ { "type": "null" }
19846
+ ]
19847
+ },
19848
+ "Metadata": {
19849
+ "type": "object",
19850
+ "description": "Bounded customer correlation data. Do not include secrets or personal data.",
19851
+ "maxProperties": 20,
19852
+ "propertyNames": { "minLength": 1, "maxLength": 64 },
19853
+ "additionalProperties": {
19854
+ "$ref": "#/components/schemas/MetadataValue"
19855
+ },
19856
+ "x-max-json-bytes": 4096
19857
+ },
18832
19858
  "HighestProvenProviderState": {
18833
19859
  "type": "string",
18834
19860
  "enum": ["none", "provider_accepted"],
@@ -18892,12 +19918,12 @@
18892
19918
  "type": "string",
18893
19919
  "enum": ["open", "answered", "expired", "cancelled", "retired"]
18894
19920
  },
18895
- "DeveloperKey": {
19921
+ "GroupKey": {
18896
19922
  "type": "string",
18897
19923
  "minLength": 1,
18898
19924
  "maxLength": 63,
18899
- "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
18900
- "description": "A stable, lowercase developer key used by registered return destinations and subscriber groups."
19925
+ "pattern": "^[A-Za-z0-9]+(?:[-_][A-Za-z0-9]+)*$",
19926
+ "description": "A stable, case-sensitive subscriber group key. Existing keys are preserved exactly."
18901
19927
  },
18902
19928
  "BroadcastAggregate": {
18903
19929
  "type": "object",
@@ -20231,6 +21257,36 @@
20231
21257
  }
20232
21258
  ]
20233
21259
  },
21260
+ "DocumentContent": {
21261
+ "description": "An authoritative ordered portable notification document that every delivery adapter renders faithfully.",
21262
+ "type": "object",
21263
+ "required": ["type", "blocks"],
21264
+ "properties": {
21265
+ "type": { "const": "document" },
21266
+ "title": {
21267
+ "description": "An optional semantic document title. The adapter chooses its native presentation.",
21268
+ "type": "string",
21269
+ "minLength": 1,
21270
+ "maxLength": 200,
21271
+ "x-max-utf8-bytes": 800
21272
+ },
21273
+ "blocks": {
21274
+ "description": "The complete portable document in exact authored order.",
21275
+ "type": "array",
21276
+ "minItems": 1,
21277
+ "maxItems": 24,
21278
+ "items": { "$ref": "#/components/schemas/DocumentBlock" }
21279
+ },
21280
+ "image": { "$ref": "#/components/schemas/NotificationImage" }
21281
+ },
21282
+ "additionalProperties": false
21283
+ },
21284
+ "MessageContent": {
21285
+ "oneOf": [
21286
+ { "$ref": "#/components/schemas/TextContent" },
21287
+ { "$ref": "#/components/schemas/DocumentContent" }
21288
+ ]
21289
+ },
20234
21290
  "RecipientBrand": {
20235
21291
  "type": "object",
20236
21292
  "description": "Constrained recipient-facing brand tokens. Arbitrary markup, CSS, scripts, remote assets, fonts, and tracking inputs are structurally absent.",
@@ -20261,6 +21317,13 @@
20261
21317
  "pattern": "^https?://[^/?#*\\s]+(?::[0-9]{1,5})?/?$",
20262
21318
  "description": "One exact scheme, host, and optional port, in any spelling that means that same origin. A trailing slash, an uppercase host, and an explicitly written default port are accepted and stored in canonical form. Paths, queries, fragments, credentials, and wildcards are forbidden; HTTP is accepted only for localhost or synthetic .test configuration."
20263
21319
  },
21320
+ "DeveloperKey": {
21321
+ "type": "string",
21322
+ "minLength": 1,
21323
+ "maxLength": 63,
21324
+ "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
21325
+ "description": "A stable, lowercase developer key used by registered return destinations and subscriber groups."
21326
+ },
20264
21327
  "RegisteredReturnUrl": {
20265
21328
  "type": "string",
20266
21329
  "format": "uri",
@@ -20416,7 +21479,7 @@
20416
21479
  "provider-capabilities-v1.schema": {
20417
21480
  "$schema": "https://json-schema.org/draft/2020-12/schema",
20418
21481
  "$id": "https://whooshbang.flowxo.com/schemas/provider-capabilities-v1.schema.json",
20419
- "x-contract-version": "2.4.0",
21482
+ "x-contract-version": "2.6.0",
20420
21483
  "title": "ProviderCapabilitiesV1",
20421
21484
  "description": "Provider-neutral, renderer-facing delivery and interaction capabilities with explicit degradation.",
20422
21485
  "type": "object",
@@ -20625,7 +21688,7 @@
20625
21688
  "description": "How many of a day's messages fall in one group. `key` is a `MessageState` when grouping by state, a `Channel` when grouping by channel, and a channel connection id when grouping by connection. Groups with no messages are omitted, and a list holds at most the 200 largest groups, largest first.",
20626
21689
  "required": ["key", "count"],
20627
21690
  "properties": {
20628
- "key": { "type": "string", "minLength": 1, "maxLength": 128 },
21691
+ "key": { "$ref": "#/components/schemas/GroupKey" },
20629
21692
  "count": { "type": "integer", "minimum": 1 }
20630
21693
  },
20631
21694
  "additionalProperties": false
@@ -20699,6 +21762,27 @@
20699
21762
  },
20700
21763
  "additionalProperties": false
20701
21764
  },
21765
+ "InteractionOption": {
21766
+ "type": "object",
21767
+ "required": ["value", "label"],
21768
+ "properties": {
21769
+ "value": {
21770
+ "type": "string",
21771
+ "minLength": 1,
21772
+ "maxLength": 128,
21773
+ "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
21774
+ "description": "Opaque customer data. It is never interpreted as a command."
21775
+ },
21776
+ "label": { "type": "string", "minLength": 1, "maxLength": 120 },
21777
+ "intent": {
21778
+ "type": "string",
21779
+ "enum": ["accept", "destructive", "informational", "neutral"],
21780
+ "default": "neutral",
21781
+ "description": "What choosing this option does, so a channel can present it accordingly. Describes consequence rather than appearance: a channel renders `accept` green where it can, `destructive` red, `informational` prominent, and `neutral` plainly. A channel that cannot colour a control ignores this and the option still renders. Omitted means `neutral`."
21782
+ }
21783
+ },
21784
+ "additionalProperties": false
21785
+ },
20702
21786
  "SelectInteractionRequest": {
20703
21787
  "type": "object",
20704
21788
  "required": ["type", "prompt", "options", "expires_at"],
@@ -20783,9 +21867,9 @@
20783
21867
  "idempotencyKeyRequired": {
20784
21868
  "value": {
20785
21869
  "type": "https://whooshbang.flowxo.com/problems/idempotency_key_required",
20786
- "title": "Idempotency key required",
21870
+ "title": "Invalid idempotency key",
20787
21871
  "status": 400,
20788
- "detail": "Provide an Idempotency-Key containing 8 to 255 visible ASCII characters.",
21872
+ "detail": "Idempotency-Key must be 8 to 255 visible ASCII characters.",
20789
21873
  "code": "idempotency_key_required",
20790
21874
  "diagnostic_id": "diag_idem_required_001",
20791
21875
  "retryable": false
@@ -21197,5 +22281,5 @@
21197
22281
  }
21198
22282
  }
21199
22283
  },
21200
- "x-contract-version": "2.4.0"
22284
+ "x-contract-version": "2.6.0"
21201
22285
  }