@whooshbang/contracts 2.0.0-rc.7 → 2.0.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 (97) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +4 -5
  3. package/contract-manifest.json +33 -32
  4. package/dist/generated/artifact-index.d.ts +34 -34
  5. package/dist/generated/artifact-index.js +33 -32
  6. package/dist/generated/artifact-index.js.map +1 -1
  7. package/dist/generated/validators-standalone.d.ts +488 -437
  8. package/dist/generated/validators-standalone.js +40130 -29709
  9. package/dist/generated/validators-standalone.js.map +1 -1
  10. package/dist/generated/validators.d.ts +5 -1
  11. package/dist/generated/validators.js +9 -2
  12. package/dist/generated/validators.js.map +1 -1
  13. package/dist/generated/whooshbang-event-v1.d.ts +34 -18
  14. package/dist/generated/whooshbang-v1.d.ts +220 -45
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.js +1 -1
  17. package/dist/index.js.map +1 -1
  18. package/fixtures/core-v1/fixture-index.json +7 -1
  19. package/fixtures/core-v1/requests/assert-email-service.json +10 -0
  20. package/fixtures/core-v1/scenarios/api-credential-invalid-indistinguishable.json +1 -1
  21. package/fixtures/core-v1/scenarios/api-credential-one-time-reveal.json +1 -1
  22. package/fixtures/core-v1/scenarios/api-credential-revocation-replay.json +1 -1
  23. package/fixtures/core-v1/scenarios/api-credential-rotation-overlap.json +1 -1
  24. package/fixtures/core-v1/scenarios/cancellation-too-late.json +1 -1
  25. package/fixtures/core-v1/scenarios/cross-organization-project.json +1 -1
  26. package/fixtures/core-v1/scenarios/cross-project-resource.json +1 -1
  27. package/fixtures/core-v1/scenarios/environment-mismatch.json +1 -1
  28. package/fixtures/core-v1/scenarios/idempotency-conflict.json +1 -1
  29. package/fixtures/core-v1/scenarios/idempotency-equivalent-replay.json +1 -1
  30. package/fixtures/core-v1/scenarios/malformed-bearer.json +1 -1
  31. package/fixtures/core-v1/scenarios/metadata-overrun.json +1 -1
  32. package/fixtures/core-v1/scenarios/missing-scope.json +1 -1
  33. package/fixtures/core-v1/scenarios/project-create-equivalent-replay.json +1 -1
  34. package/fixtures/core-v1/scenarios/project-slug-conflict.json +1 -1
  35. package/fixtures/core-v1/scenarios/provider-outcome-unknown.json +1 -1
  36. package/fixtures/core-v1/scenarios/subscription-activation-replay.json +1 -1
  37. package/fixtures/core-v1/scenarios/subscription-authorization-capability-opacity.json +1 -1
  38. package/fixtures/core-v1/scenarios/subscription-cross-connection-callback-rejected.json +1 -1
  39. package/fixtures/core-v1/scenarios/subscription-expired-replay.json +1 -1
  40. package/fixtures/core-v1/scenarios/subscription-selected-connection-replay.json +1 -1
  41. package/fixtures/core-v1/success/subscription-change.json +1 -1
  42. package/fixtures/interaction-machine-v1/scenarios/ack-conflict.json +1 -1
  43. package/fixtures/interaction-machine-v1/scenarios/ack-cross-machine.json +1 -1
  44. package/fixtures/interaction-machine-v1/scenarios/ack-out-of-order.json +1 -1
  45. package/fixtures/interaction-machine-v1/scenarios/ack-replay.json +1 -1
  46. package/fixtures/interaction-machine-v1/scenarios/ambiguous-free-text.json +1 -1
  47. package/fixtures/interaction-machine-v1/scenarios/callback-token-opacity.json +1 -1
  48. package/fixtures/interaction-machine-v1/scenarios/credential-registration-conflict.json +1 -1
  49. package/fixtures/interaction-machine-v1/scenarios/credential-registration-replay.json +1 -1
  50. package/fixtures/interaction-machine-v1/scenarios/credential-revocation-replay.json +1 -1
  51. package/fixtures/interaction-machine-v1/scenarios/credential-rotation.json +1 -1
  52. package/fixtures/interaction-machine-v1/scenarios/first-credential-activates.json +1 -1
  53. package/fixtures/interaction-machine-v1/scenarios/interaction-duplicate.json +1 -1
  54. package/fixtures/interaction-machine-v1/scenarios/interaction-expired.json +1 -1
  55. package/fixtures/interaction-machine-v1/scenarios/interaction-mismatch.json +1 -1
  56. package/fixtures/interaction-machine-v1/scenarios/interaction-retired.json +1 -1
  57. package/fixtures/interaction-machine-v1/scenarios/interaction-stale.json +1 -1
  58. package/fixtures/interaction-machine-v1/scenarios/machine-admin-denied.json +1 -1
  59. package/fixtures/interaction-machine-v1/scenarios/machine-bound-message-create.json +1 -1
  60. package/fixtures/interaction-machine-v1/scenarios/machine-cancel-denied.json +1 -1
  61. package/fixtures/interaction-machine-v1/scenarios/machine-crash-before-ack.json +1 -1
  62. package/fixtures/interaction-machine-v1/scenarios/machine-notifier-mismatch.json +1 -1
  63. package/fixtures/interaction-machine-v1/scenarios/machine-other-message-read.json +1 -1
  64. package/fixtures/interaction-machine-v1/scenarios/machine-owned-message-read.json +1 -1
  65. package/fixtures/interaction-machine-v1/scenarios/machine-subscriber-mismatch.json +1 -1
  66. package/fixtures/interaction-machine-v1/scenarios/poll-uncommitted-cursor-rejected.json +1 -1
  67. package/fixtures/interaction-machine-v1/scenarios/project-credential-machine-endpoint-denied.json +1 -1
  68. package/fixtures/interaction-machine-v1/scenarios/quarantine-integrity-rejected.json +1 -1
  69. package/fixtures/interaction-machine-v1/scenarios/quarantine-signature-rejected.json +1 -1
  70. package/fixtures/interaction-machine-v1/scenarios/quarantine-valid.json +1 -1
  71. package/fixtures/interaction-machine-v1/scenarios/revoked-machine-credential.json +1 -1
  72. package/fixtures/recipient-experience-v1/scenarios/membership-broadcast-snapshot.json +1 -1
  73. package/fixtures/recipient-experience-v1/scenarios/preferred-binding-resolution.json +1 -1
  74. package/fixtures/recipient-experience-v1/scenarios/recipient-session-security.json +1 -1
  75. package/fixtures/recipient-experience-v1/scenarios/subscriber-initial-groups-transaction.json +1 -1
  76. package/openapi/generated/whooshbang-v1.bundle.json +630 -48
  77. package/openapi/whooshbang-v1.yaml +230 -5
  78. package/package.json +2 -3
  79. package/schemas/core-scenario.schema.json +1 -1
  80. package/schemas/events/agent-binding-degraded-v1.schema.json +1 -1
  81. package/schemas/events/interaction-expired-v1.schema.json +1 -1
  82. package/schemas/events/interaction-received-v1.schema.json +1 -1
  83. package/schemas/events/message-delivered-v1.schema.json +1 -1
  84. package/schemas/events/message-expired-v1.schema.json +1 -1
  85. package/schemas/events/message-failed-v1.schema.json +1 -1
  86. package/schemas/events/message-provider-accepted-v1.schema.json +1 -1
  87. package/schemas/events/subscription-activated-v1.schema.json +1 -1
  88. package/schemas/events/subscription-revoked-v1.schema.json +1 -1
  89. package/schemas/events/whooshbang-event-v1.schema.json +145 -65
  90. package/schemas/fixture-index.schema.json +1 -1
  91. package/schemas/interaction-machine-scenario.schema.json +1 -1
  92. package/schemas/interaction-v1.schema.json +1 -1
  93. package/schemas/machine-v1.schema.json +1 -1
  94. package/schemas/provider-capabilities-v1.schema.json +1 -1
  95. package/schemas/webhooks/webhook-delivery-case-v1.schema.json +1 -1
  96. package/schemas/webhooks/webhook-signing-vector-v1.schema.json +1 -1
  97. package/schemas/whooshbang-v1.schema.json +552 -53
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.1",
3
3
  "info": {
4
4
  "title": "WhooshBang API",
5
- "version": "2.0.0-rc.7",
5
+ "version": "2.0.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": {
@@ -24,6 +24,10 @@
24
24
  "name": "Organization",
25
25
  "description": "The current Clerk organization projected as a WhooshBang organization."
26
26
  },
27
+ {
28
+ "name": "Teammates",
29
+ "description": "The people in an organization, their roles and the projects each can see."
30
+ },
27
31
  {
28
32
  "name": "Projects",
29
33
  "description": "Organization-scoped applications with transactionally bootstrapped test/live environments."
@@ -269,8 +273,8 @@
269
273
  "/v1/billing/subscription-changes": {
270
274
  "post": {
271
275
  "operationId": "createSubscriptionChange",
276
+ "description": "Quote a new recurring selection without charging. Upgrades keep renewal and charge the new plan or additional add-on units for the remaining period, without unused-time credits or offsets. Paid dropped extras remain available through their original expiry and do not renew. Requires an administrator in the current organization. Without a current plan or add-on the change starts one. Confirmation matches the saved exact cash amount and charges the default card. Uncertain outcomes recover the same owned change.",
272
277
  "summary": "Quote an approved recurring change",
273
- "description": "Requires an administrator in the current organization. A quote does not charge. Without a current plan or add-on the change starts one. Confirmation matches the saved exact cash amount and charges the default card. Uncertain outcomes recover the same owned change.",
274
278
  "tags": ["Organization"],
275
279
  "security": [{ "clerkSession": [] }, { "bearerGrant": [] }],
276
280
  "x-required-scopes": ["billing:write"],
@@ -311,8 +315,7 @@
311
315
  "/v1/billing/subscription-changes/{changeId}": {
312
316
  "get": {
313
317
  "operationId": "getSubscriptionChange",
314
- "summary": "Recover an owned recurring change",
315
- "description": "Requires an administrator in the current organization. A quote does not charge. Without a current plan or add-on the change starts one. Confirmation matches the saved exact cash amount and charges the default card. Uncertain outcomes recover the same owned change.",
318
+ "summary": "Recover an owned recurring change Requires an administrator in the current organization. Without a current plan or add-on the change starts one. Confirmation matches the saved exact cash amount and charges the default card. Uncertain outcomes recover the same owned change.",
316
319
  "tags": ["Organization"],
317
320
  "security": [{ "clerkSession": [] }, { "bearerGrant": [] }],
318
321
  "x-required-scopes": ["billing:read"],
@@ -355,8 +358,7 @@
355
358
  "/v1/billing/subscription-changes/{changeId}/confirm": {
356
359
  "post": {
357
360
  "operationId": "confirmSubscriptionChange",
358
- "summary": "Confirm the quoted card charge or scheduled reduction",
359
- "description": "Requires an administrator in the current organization. A quote does not charge. Without a current plan or add-on the change starts one. Confirmation matches the saved exact cash amount and charges the default card. Uncertain outcomes recover the same owned change.",
361
+ "summary": "Confirm the quoted card charge or scheduled reduction Requires an administrator in the current organization. Without a current plan or add-on the change starts one. Confirmation matches the saved exact cash amount and charges the default card. Uncertain outcomes recover the same owned change.",
360
362
  "tags": ["Organization"],
361
363
  "security": [{ "clerkSession": [] }, { "bearerGrant": [] }],
362
364
  "x-required-scopes": ["billing:write"],
@@ -912,6 +914,217 @@
912
914
  }
913
915
  }
914
916
  },
917
+ "/v1/organization/teammates": {
918
+ "get": {
919
+ "operationId": "listTeammates",
920
+ "summary": "List the organization's teammates and open invitations",
921
+ "description": "Returns every person in the organization with their role, the projects\nthey can see and whether they have joined, plus every invitation not yet\naccepted. Any teammate may read it; only an admin may change it.\n",
922
+ "tags": ["Teammates"],
923
+ "security": [
924
+ { "clerkSession": [] },
925
+ { "bearerGrant": ["teammates:read"] }
926
+ ],
927
+ "x-required-scopes": ["teammates:read"],
928
+ "x-organization-membership": "required",
929
+ "parameters": [
930
+ { "$ref": "#/components/parameters/ListAfterCursor" },
931
+ { "$ref": "#/components/parameters/ListPageLimit" },
932
+ { "$ref": "#/components/parameters/PageNumber" }
933
+ ],
934
+ "responses": {
935
+ "200": {
936
+ "description": "The organization's teammates.",
937
+ "headers": {
938
+ "WhooshBang-Diagnostic-Id": {
939
+ "$ref": "#/components/headers/DiagnosticId"
940
+ }
941
+ },
942
+ "content": {
943
+ "application/json": {
944
+ "schema": { "$ref": "#/components/schemas/TeammateCollection" }
945
+ }
946
+ }
947
+ },
948
+ "401": { "$ref": "#/components/responses/Unauthorized" },
949
+ "403": { "$ref": "#/components/responses/Forbidden" },
950
+ "429": { "$ref": "#/components/responses/RateLimited" }
951
+ }
952
+ },
953
+ "post": {
954
+ "operationId": "inviteTeammate",
955
+ "summary": "Invite a teammate",
956
+ "description": "Emails one invitation carrying the chosen role and, for a member or\nmessenger, the projects they will see. Nothing else is needed after they\naccept. Inviting an address that is already invited or already a\nteammate is refused rather than repeated.\n",
957
+ "tags": ["Teammates"],
958
+ "security": [
959
+ { "clerkSession": [] },
960
+ { "bearerGrant": ["teammates:write"] }
961
+ ],
962
+ "x-required-scopes": ["teammates:write"],
963
+ "x-organization-membership": "required",
964
+ "x-required-membership-role": "admin",
965
+ "requestBody": {
966
+ "required": true,
967
+ "content": {
968
+ "application/json": {
969
+ "schema": { "$ref": "#/components/schemas/InviteTeammateRequest" }
970
+ }
971
+ }
972
+ },
973
+ "responses": {
974
+ "201": {
975
+ "description": "The invitation.",
976
+ "headers": {
977
+ "WhooshBang-Diagnostic-Id": {
978
+ "$ref": "#/components/headers/DiagnosticId"
979
+ }
980
+ },
981
+ "content": {
982
+ "application/json": {
983
+ "schema": { "$ref": "#/components/schemas/Teammate" }
984
+ }
985
+ }
986
+ },
987
+ "400": { "$ref": "#/components/responses/BadRequest" },
988
+ "401": { "$ref": "#/components/responses/Unauthorized" },
989
+ "403": { "$ref": "#/components/responses/Forbidden" },
990
+ "429": { "$ref": "#/components/responses/RateLimited" }
991
+ }
992
+ }
993
+ },
994
+ "/v1/organization/teammates/{teammate_id}": {
995
+ "parameters": [
996
+ {
997
+ "name": "teammate_id",
998
+ "in": "path",
999
+ "required": true,
1000
+ "schema": { "$ref": "#/components/schemas/OpaqueIdentifier" }
1001
+ }
1002
+ ],
1003
+ "delete": {
1004
+ "operationId": "removeTeammate",
1005
+ "summary": "Remove a teammate or revoke an invitation",
1006
+ "description": "Removes an active teammate from the organization, or revokes an\ninvitation. Removal applies to their next request, including requests\nfrom MCP connections they authorized. The last admin cannot be removed.\n",
1007
+ "tags": ["Teammates"],
1008
+ "security": [
1009
+ { "clerkSession": [] },
1010
+ { "bearerGrant": ["teammates:write"] }
1011
+ ],
1012
+ "x-required-scopes": ["teammates:write"],
1013
+ "x-organization-membership": "required",
1014
+ "x-required-membership-role": "admin",
1015
+ "responses": {
1016
+ "204": {
1017
+ "description": "The teammate was removed or the invitation revoked.",
1018
+ "headers": {
1019
+ "WhooshBang-Diagnostic-Id": {
1020
+ "$ref": "#/components/headers/DiagnosticId"
1021
+ }
1022
+ }
1023
+ },
1024
+ "401": { "$ref": "#/components/responses/Unauthorized" },
1025
+ "403": { "$ref": "#/components/responses/Forbidden" },
1026
+ "404": { "$ref": "#/components/responses/NotFound" },
1027
+ "429": { "$ref": "#/components/responses/RateLimited" }
1028
+ }
1029
+ }
1030
+ },
1031
+ "/v1/organization/teammates/{teammate_id}/access": {
1032
+ "parameters": [
1033
+ {
1034
+ "name": "teammate_id",
1035
+ "in": "path",
1036
+ "required": true,
1037
+ "schema": { "$ref": "#/components/schemas/OpaqueIdentifier" }
1038
+ }
1039
+ ],
1040
+ "put": {
1041
+ "operationId": "setTeammateAccess",
1042
+ "summary": "Change a teammate's role and projects",
1043
+ "description": "Replaces the role and, for a member or messenger, the projects of an\nactive teammate or an open invitation. Narrowed access applies to their\nnext request. The last admin cannot be demoted.\n",
1044
+ "tags": ["Teammates"],
1045
+ "security": [
1046
+ { "clerkSession": [] },
1047
+ { "bearerGrant": ["teammates:write"] }
1048
+ ],
1049
+ "x-required-scopes": ["teammates:write"],
1050
+ "x-organization-membership": "required",
1051
+ "x-required-membership-role": "admin",
1052
+ "x-repeat-semantics": {
1053
+ "same_target": "idempotent",
1054
+ "secret_returned": false
1055
+ },
1056
+ "requestBody": {
1057
+ "required": true,
1058
+ "content": {
1059
+ "application/json": {
1060
+ "schema": { "$ref": "#/components/schemas/TeammateAccessRequest" }
1061
+ }
1062
+ }
1063
+ },
1064
+ "responses": {
1065
+ "200": {
1066
+ "description": "The teammate with their new access.",
1067
+ "headers": {
1068
+ "WhooshBang-Diagnostic-Id": {
1069
+ "$ref": "#/components/headers/DiagnosticId"
1070
+ }
1071
+ },
1072
+ "content": {
1073
+ "application/json": {
1074
+ "schema": { "$ref": "#/components/schemas/Teammate" }
1075
+ }
1076
+ }
1077
+ },
1078
+ "400": { "$ref": "#/components/responses/BadRequest" },
1079
+ "401": { "$ref": "#/components/responses/Unauthorized" },
1080
+ "403": { "$ref": "#/components/responses/Forbidden" },
1081
+ "404": { "$ref": "#/components/responses/NotFound" },
1082
+ "429": { "$ref": "#/components/responses/RateLimited" }
1083
+ }
1084
+ }
1085
+ },
1086
+ "/v1/organization/teammates/{teammate_id}/resend": {
1087
+ "parameters": [
1088
+ {
1089
+ "name": "teammate_id",
1090
+ "in": "path",
1091
+ "required": true,
1092
+ "schema": { "$ref": "#/components/schemas/OpaqueIdentifier" }
1093
+ }
1094
+ ],
1095
+ "post": {
1096
+ "operationId": "resendTeammateInvitation",
1097
+ "summary": "Resend an invitation",
1098
+ "description": "Replaces an open invitation with a fresh one carrying the same role and\nprojects, and emails it again. The earlier link stops working.\n",
1099
+ "tags": ["Teammates"],
1100
+ "security": [
1101
+ { "clerkSession": [] },
1102
+ { "bearerGrant": ["teammates:write"] }
1103
+ ],
1104
+ "x-required-scopes": ["teammates:write"],
1105
+ "x-organization-membership": "required",
1106
+ "x-required-membership-role": "admin",
1107
+ "responses": {
1108
+ "200": {
1109
+ "description": "The renewed invitation.",
1110
+ "headers": {
1111
+ "WhooshBang-Diagnostic-Id": {
1112
+ "$ref": "#/components/headers/DiagnosticId"
1113
+ }
1114
+ },
1115
+ "content": {
1116
+ "application/json": {
1117
+ "schema": { "$ref": "#/components/schemas/Teammate" }
1118
+ }
1119
+ }
1120
+ },
1121
+ "401": { "$ref": "#/components/responses/Unauthorized" },
1122
+ "403": { "$ref": "#/components/responses/Forbidden" },
1123
+ "404": { "$ref": "#/components/responses/NotFound" },
1124
+ "429": { "$ref": "#/components/responses/RateLimited" }
1125
+ }
1126
+ }
1127
+ },
915
1128
  "/v1/provider-installations": {
916
1129
  "get": {
917
1130
  "operationId": "listProviderInstallations",
@@ -11999,23 +12212,23 @@
11999
12212
  "project_capacity": {
12000
12213
  "type": "integer",
12001
12214
  "minimum": 1,
12002
- "maximum": 10025
12215
+ "maximum": 2147483647
12003
12216
  },
12004
12217
  "requested_project_ids": {
12005
12218
  "type": "array",
12006
12219
  "items": { "$ref": "#/components/schemas/OpaqueIdentifier" },
12007
12220
  "uniqueItems": true,
12008
- "maxItems": 10025
12221
+ "maxItems": 2147483647
12009
12222
  },
12010
12223
  "active_project_ids": {
12011
12224
  "type": "array",
12012
12225
  "items": { "$ref": "#/components/schemas/OpaqueIdentifier" },
12013
12226
  "uniqueItems": true,
12014
- "maxItems": 10025
12227
+ "maxItems": 2147483647
12015
12228
  }
12016
12229
  },
12017
12230
  "additionalProperties": false,
12018
- "description": "Reusable account project slots. After a downgrade the oldest requested projects retain active slots. Unassigned projects retain data, accepted delivery and answers; new live sends require an active slot."
12231
+ "description": "Reusable account project slots. After a downgrade the oldest requested projects retain active slots. Unassigned projects retain data, accepted delivery and answers; new live sends require an active slot. Paid capacity includes all unexpired project purchases, so retained dropped extras can exceed the maximum renewing selection."
12019
12232
  },
12020
12233
  "AssignBillingProjectSlotsRequest": {
12021
12234
  "type": "object",
@@ -12158,6 +12371,10 @@
12158
12371
  }
12159
12372
  ],
12160
12373
  "description": "A recurring charge the default card did not pay. Paid access continues until the paid boundary; another attempt follows at next_attempt_at, or none when null. Update the card to settle it."
12374
+ },
12375
+ "recurring_selection": {
12376
+ "$ref": "#/components/schemas/SubscriptionSelection",
12377
+ "description": "Selection that will renew; paid project capacity can also include dropped extras until their original expiry. Optional for compatibility with older servers."
12161
12378
  }
12162
12379
  },
12163
12380
  "additionalProperties": false
@@ -12243,7 +12460,8 @@
12243
12460
  "maxLength": 40
12244
12461
  },
12245
12462
  { "type": "null" }
12246
- ]
12463
+ ],
12464
+ "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."
12247
12465
  },
12248
12466
  "effective_at": {
12249
12467
  "anyOf": [
@@ -12800,6 +13018,122 @@
12800
13018
  },
12801
13019
  "additionalProperties": false
12802
13020
  },
13021
+ "Teammate": {
13022
+ "type": "object",
13023
+ "description": "One person in the organization, or one invitation to it. Identity-provider identifiers are not public fields.",
13024
+ "required": [
13025
+ "id",
13026
+ "status",
13027
+ "role",
13028
+ "name",
13029
+ "email",
13030
+ "projects",
13031
+ "is_current_user",
13032
+ "created_at"
13033
+ ],
13034
+ "properties": {
13035
+ "id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
13036
+ "status": { "$ref": "#/components/schemas/TeammateStatus" },
13037
+ "role": { "$ref": "#/components/schemas/OrganizationRole" },
13038
+ "name": {
13039
+ "anyOf": [
13040
+ { "type": "null" },
13041
+ { "type": "string", "minLength": 1, "maxLength": 200 }
13042
+ ],
13043
+ "description": "The person's name, when they have given one. Null for an invitation."
13044
+ },
13045
+ "email": {
13046
+ "anyOf": [
13047
+ { "type": "null" },
13048
+ { "type": "string", "minLength": 1, "maxLength": 320 }
13049
+ ],
13050
+ "description": "The address the person signs in with, or the address invited."
13051
+ },
13052
+ "projects": { "$ref": "#/components/schemas/TeammateProjectAccess" },
13053
+ "is_current_user": {
13054
+ "type": "boolean",
13055
+ "description": "Whether this is the person making the request."
13056
+ },
13057
+ "created_at": {
13058
+ "$ref": "#/components/schemas/Timestamp",
13059
+ "description": "When they joined, or when the invitation was sent."
13060
+ },
13061
+ "expires_at": {
13062
+ "$ref": "#/components/schemas/Timestamp",
13063
+ "description": "When an invitation stops working. Absent for an active teammate."
13064
+ }
13065
+ },
13066
+ "additionalProperties": false
13067
+ },
13068
+ "TeammateCollection": {
13069
+ "type": "object",
13070
+ "description": "Everybody in the organization and every open invitation, people first.",
13071
+ "required": ["items", "total"],
13072
+ "properties": {
13073
+ "items": {
13074
+ "type": "array",
13075
+ "maxItems": 100,
13076
+ "items": { "$ref": "#/components/schemas/Teammate" }
13077
+ },
13078
+ "next_cursor": {
13079
+ "type": "string",
13080
+ "minLength": 1,
13081
+ "maxLength": 256,
13082
+ "pattern": "^[A-Za-z0-9_-]+$"
13083
+ },
13084
+ "page": { "$ref": "#/components/schemas/ListPageNumber" },
13085
+ "page_size": { "$ref": "#/components/schemas/ListPageSize" },
13086
+ "total": { "$ref": "#/components/schemas/ListTotal" }
13087
+ },
13088
+ "additionalProperties": false
13089
+ },
13090
+ "TeammateAccessRequest": {
13091
+ "type": "object",
13092
+ "description": "A role and, except for an admin, at least one project. An admin sees every project and takes no list.",
13093
+ "required": ["role"],
13094
+ "properties": {
13095
+ "role": { "$ref": "#/components/schemas/OrganizationRole" },
13096
+ "project_ids": {
13097
+ "type": "array",
13098
+ "maxItems": 1000,
13099
+ "uniqueItems": true,
13100
+ "items": { "$ref": "#/components/schemas/OpaqueIdentifier" }
13101
+ }
13102
+ },
13103
+ "if": { "properties": { "role": { "const": "admin" } } },
13104
+ "then": {
13105
+ "properties": { "project_ids": { "type": "array", "maxItems": 0 } }
13106
+ },
13107
+ "else": {
13108
+ "required": ["project_ids"],
13109
+ "properties": { "project_ids": { "type": "array", "minItems": 1 } }
13110
+ },
13111
+ "additionalProperties": false
13112
+ },
13113
+ "InviteTeammateRequest": {
13114
+ "type": "object",
13115
+ "description": "Invites one person by email with their role and projects in one step.",
13116
+ "required": ["email", "role"],
13117
+ "properties": {
13118
+ "email": { "$ref": "#/components/schemas/EmailAddress" },
13119
+ "role": { "$ref": "#/components/schemas/OrganizationRole" },
13120
+ "project_ids": {
13121
+ "type": "array",
13122
+ "maxItems": 1000,
13123
+ "uniqueItems": true,
13124
+ "items": { "$ref": "#/components/schemas/OpaqueIdentifier" }
13125
+ }
13126
+ },
13127
+ "if": { "properties": { "role": { "const": "admin" } } },
13128
+ "then": {
13129
+ "properties": { "project_ids": { "type": "array", "maxItems": 0 } }
13130
+ },
13131
+ "else": {
13132
+ "required": ["project_ids"],
13133
+ "properties": { "project_ids": { "type": "array", "minItems": 1 } }
13134
+ },
13135
+ "additionalProperties": false
13136
+ },
12803
13137
  "UpdateProjectRequest": {
12804
13138
  "type": "object",
12805
13139
  "description": "Sets the project privacy policy. Moving from standard to minimal erases stored identifying profile data; moving to private also erases recipient language. Increasing collection does not restore erased data.",
@@ -14210,6 +14544,149 @@
14210
14544
  },
14211
14545
  "additionalProperties": false,
14212
14546
  "description": "Address-free asserted consent captured on a message. The snapshot survives binding deletion; it never claims recipient verification."
14547
+ },
14548
+ {
14549
+ "type": "object",
14550
+ "required": [
14551
+ "id",
14552
+ "project_id",
14553
+ "environment",
14554
+ "subscriber_id",
14555
+ "notifier_id",
14556
+ "channel",
14557
+ "connection_id",
14558
+ "status",
14559
+ "address",
14560
+ "basis",
14561
+ "service"
14562
+ ],
14563
+ "properties": {
14564
+ "id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
14565
+ "project_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
14566
+ "environment": { "$ref": "#/components/schemas/Environment" },
14567
+ "subscriber_id": {
14568
+ "$ref": "#/components/schemas/OpaqueIdentifier"
14569
+ },
14570
+ "notifier_id": {
14571
+ "$ref": "#/components/schemas/OpaqueIdentifier"
14572
+ },
14573
+ "channel": { "const": "email" },
14574
+ "connection_id": {
14575
+ "$ref": "#/components/schemas/OpaqueIdentifier"
14576
+ },
14577
+ "status": { "enum": ["active", "paused", "revoked"] },
14578
+ "address": {
14579
+ "$ref": "#/components/schemas/EmailAddress",
14580
+ "description": "The application-verified email destination enrolled for service notices."
14581
+ },
14582
+ "basis": { "const": "service" },
14583
+ "service": {
14584
+ "type": "object",
14585
+ "required": [
14586
+ "relationship_started_at",
14587
+ "source",
14588
+ "asserted_at",
14589
+ "asserted_by",
14590
+ "domain_id"
14591
+ ],
14592
+ "properties": {
14593
+ "source": {
14594
+ "type": "string",
14595
+ "minLength": 1,
14596
+ "maxLength": 100,
14597
+ "pattern": "^[^\u0000-\u001f-Ÿ

]+$"
14598
+ },
14599
+ "asserted_at": { "$ref": "#/components/schemas/Timestamp" },
14600
+ "asserted_by": {
14601
+ "type": "object",
14602
+ "required": ["kind", "id"],
14603
+ "properties": {
14604
+ "kind": { "enum": ["credential", "grant", "session"] },
14605
+ "id": { "$ref": "#/components/schemas/OpaqueIdentifier" }
14606
+ },
14607
+ "additionalProperties": false
14608
+ },
14609
+ "domain_id": {
14610
+ "$ref": "#/components/schemas/OpaqueIdentifier"
14611
+ },
14612
+ "relationship_started_at": {
14613
+ "$ref": "#/components/schemas/Timestamp"
14614
+ }
14615
+ },
14616
+ "additionalProperties": false
14617
+ }
14618
+ },
14619
+ "additionalProperties": false,
14620
+ "description": "Service relationship evidence. No consent claim. Address-free snapshots survive binding deletion."
14621
+ },
14622
+ {
14623
+ "type": "object",
14624
+ "required": [
14625
+ "id",
14626
+ "project_id",
14627
+ "environment",
14628
+ "subscriber_id",
14629
+ "notifier_id",
14630
+ "channel",
14631
+ "connection_id",
14632
+ "status",
14633
+ "basis",
14634
+ "service"
14635
+ ],
14636
+ "properties": {
14637
+ "id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
14638
+ "project_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
14639
+ "environment": { "$ref": "#/components/schemas/Environment" },
14640
+ "subscriber_id": {
14641
+ "$ref": "#/components/schemas/OpaqueIdentifier"
14642
+ },
14643
+ "notifier_id": {
14644
+ "$ref": "#/components/schemas/OpaqueIdentifier"
14645
+ },
14646
+ "channel": { "const": "email" },
14647
+ "connection_id": {
14648
+ "$ref": "#/components/schemas/OpaqueIdentifier"
14649
+ },
14650
+ "status": { "enum": ["active", "paused", "revoked"] },
14651
+ "basis": { "const": "service" },
14652
+ "service": {
14653
+ "type": "object",
14654
+ "required": [
14655
+ "relationship_started_at",
14656
+ "source",
14657
+ "asserted_at",
14658
+ "asserted_by",
14659
+ "domain_id"
14660
+ ],
14661
+ "properties": {
14662
+ "source": {
14663
+ "type": "string",
14664
+ "minLength": 1,
14665
+ "maxLength": 100,
14666
+ "pattern": "^[^\u0000-\u001f-Ÿ

]+$"
14667
+ },
14668
+ "asserted_at": { "$ref": "#/components/schemas/Timestamp" },
14669
+ "asserted_by": {
14670
+ "type": "object",
14671
+ "required": ["kind", "id"],
14672
+ "properties": {
14673
+ "kind": { "enum": ["credential", "grant", "session"] },
14674
+ "id": { "$ref": "#/components/schemas/OpaqueIdentifier" }
14675
+ },
14676
+ "additionalProperties": false
14677
+ },
14678
+ "domain_id": {
14679
+ "$ref": "#/components/schemas/OpaqueIdentifier"
14680
+ },
14681
+ "relationship_started_at": {
14682
+ "$ref": "#/components/schemas/Timestamp"
14683
+ }
14684
+ },
14685
+ "additionalProperties": false
14686
+ }
14687
+ },
14688
+ "additionalProperties": false,
14689
+ "description": "Service relationship evidence. No consent claim. Address-free snapshots survive binding deletion."
14213
14690
  }
14214
14691
  ]
14215
14692
  },
@@ -14437,7 +14914,12 @@
14437
14914
  "maxLength": 128
14438
14915
  },
14439
14916
  "metadata": { "$ref": "#/components/schemas/Metadata" },
14440
- "interaction": { "$ref": "#/components/schemas/InteractionRequest" }
14917
+ "interaction": { "$ref": "#/components/schemas/InteractionRequest" },
14918
+ "purpose": {
14919
+ "enum": ["service", "marketing"],
14920
+ "default": "service",
14921
+ "description": "Omitted means service. Marketing refuses service-only bindings before charging and at every delayed dispatch."
14922
+ }
14441
14923
  },
14442
14924
  "allOf": [
14443
14925
  {
@@ -14564,6 +15046,11 @@
14564
15046
  "outcome_guidance": {
14565
15047
  "type": "string",
14566
15048
  "const": "Delivery may have been accepted by the provider. No automatic retry is scheduled; create a new message explicitly if another attempt is appropriate."
15049
+ },
15050
+ "purpose": {
15051
+ "enum": ["service", "marketing"],
15052
+ "default": "service",
15053
+ "description": "Omitted means service. Marketing refuses service-only bindings before charging and at every delayed dispatch."
14567
15054
  }
14568
15055
  },
14569
15056
  "allOf": [
@@ -14772,7 +15259,12 @@
14772
15259
  {
14773
15260
  "type": "object",
14774
15261
  "properties": {
14775
- "code": { "const": "provider_terminal" },
15262
+ "code": {
15263
+ "enum": [
15264
+ "provider_terminal",
15265
+ "not_consented_for_marketing"
15266
+ ]
15267
+ },
14776
15268
  "retryable": { "const": false }
14777
15269
  }
14778
15270
  }
@@ -15267,6 +15759,18 @@
15267
15759
  "code": { "const": "internal_error" },
15268
15760
  "retryable": { "const": true }
15269
15761
  }
15762
+ },
15763
+ {
15764
+ "type": "object",
15765
+ "properties": {
15766
+ "type": {
15767
+ "const": "https://whooshbang.flowxo.com/problems/not_consented_for_marketing"
15768
+ },
15769
+ "title": { "const": "Marketing consent required" },
15770
+ "status": { "const": 422 },
15771
+ "code": { "const": "not_consented_for_marketing" },
15772
+ "retryable": { "const": false }
15773
+ }
15270
15774
  }
15271
15775
  ],
15272
15776
  "additionalProperties": false
@@ -16540,37 +17044,88 @@
16540
17044
  "additionalProperties": false
16541
17045
  },
16542
17046
  "AssertEmailConsentRequest": {
16543
- "type": "object",
16544
- "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.",
16545
- "required": [
16546
- "subscriber_id",
16547
- "notifier_id",
16548
- "connection_id",
16549
- "domain_id",
16550
- "address",
16551
- "consented",
16552
- "consented_at"
16553
- ],
16554
- "properties": {
16555
- "subscriber_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
16556
- "notifier_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
16557
- "connection_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
16558
- "domain_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
16559
- "address": { "$ref": "#/components/schemas/EmailAddress" },
16560
- "consented": { "const": true },
16561
- "consented_at": {
16562
- "$ref": "#/components/schemas/Timestamp",
16563
- "description": "When the application obtained consent; must not be in the future."
17047
+ "description": "Assert prior consent or enroll an application-verified user for service email on an exact customer-owned connection. Revocation and sending restrictions always win.",
17048
+ "oneOf": [
17049
+ {
17050
+ "type": "object",
17051
+ "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.",
17052
+ "required": [
17053
+ "subscriber_id",
17054
+ "notifier_id",
17055
+ "connection_id",
17056
+ "domain_id",
17057
+ "address",
17058
+ "consented",
17059
+ "consented_at"
17060
+ ],
17061
+ "properties": {
17062
+ "subscriber_id": {
17063
+ "$ref": "#/components/schemas/OpaqueIdentifier"
17064
+ },
17065
+ "notifier_id": {
17066
+ "$ref": "#/components/schemas/OpaqueIdentifier"
17067
+ },
17068
+ "connection_id": {
17069
+ "$ref": "#/components/schemas/OpaqueIdentifier"
17070
+ },
17071
+ "domain_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
17072
+ "address": { "$ref": "#/components/schemas/EmailAddress" },
17073
+ "consented": { "const": true },
17074
+ "consented_at": {
17075
+ "$ref": "#/components/schemas/Timestamp",
17076
+ "description": "When the application obtained consent; must not be in the future."
17077
+ },
17078
+ "source": {
17079
+ "type": "string",
17080
+ "minLength": 1,
17081
+ "maxLength": 100,
17082
+ "pattern": "^[^\u0000-\u001f-Ÿ

]+$",
17083
+ "description": "A bounded customer description of where consent was collected. The authenticated asserting actor is recorded by the service, never accepted from this object."
17084
+ }
17085
+ },
17086
+ "additionalProperties": false
16564
17087
  },
16565
- "source": {
16566
- "type": "string",
16567
- "minLength": 1,
16568
- "maxLength": 100,
16569
- "pattern": "^[^\u0000-\u001f-Ÿ

]+$",
16570
- "description": "A bounded customer description of where consent was collected. The authenticated asserting actor is recorded by the service, never accepted from this object."
17088
+ {
17089
+ "type": "object",
17090
+ "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.",
17091
+ "required": [
17092
+ "subscriber_id": "^[^\u0000-\u001f-Ÿ

]+$",
17093
+ "notifier_id",
17094
+ "connection_id",
17095
+ "domain_id",
17096
+ "address",
17097
+ "basis",
17098
+ "source",
17099
+ "relationship_started_at"
17100
+ ],
17101
+ "properties": {
17102
+ "subscriber_id": {
17103
+ "$ref": "#/components/schemas/OpaqueIdentifier"
17104
+ },
17105
+ "notifier_id": {
17106
+ "$ref": "#/components/schemas/OpaqueIdentifier"
17107
+ },
17108
+ "connection_id": {
17109
+ "$ref": "#/components/schemas/OpaqueIdentifier"
17110
+ },
17111
+ "domain_id": { "$ref": "#/components/schemas/OpaqueIdentifier" },
17112
+ "address": { "$ref": "#/components/schemas/EmailAddress" },
17113
+ "source": {
17114
+ "type": "string",
17115
+ "minLength": 1,
17116
+ "maxLength": 100,
17117
+ "pattern": "^[^\u0000-\u001f-Ÿ

]+$",
17118
+ "description": "A bounded customer description of the service relationship. The authenticated asserting actor is recorded by the service, never accepted from this object."
17119
+ },
17120
+ "basis": { "const": "service" },
17121
+ "relationship_started_at": {
17122
+ "$ref": "#/components/schemas/Timestamp",
17123
+ "description": "When the verified user relationship began; must not be in the future."
17124
+ }
17125
+ },
17126
+ "additionalProperties": false
16571
17127
  }
16572
- },
16573
- "additionalProperties": false
17128
+ ]
16574
17129
  },
16575
17130
  "CreateSubscriberRequest": {
16576
17131
  "type": "object",
@@ -17043,7 +17598,12 @@
17043
17598
  "maxLength": 128
17044
17599
  },
17045
17600
  "metadata": { "$ref": "#/components/schemas/Metadata" },
17046
- "interaction": { "$ref": "#/components/schemas/InteractionRequest" }
17601
+ "interaction": { "$ref": "#/components/schemas/InteractionRequest" },
17602
+ "purpose": {
17603
+ "enum": ["service", "marketing"],
17604
+ "default": "service",
17605
+ "description": "Omitted means service. Marketing refuses service-only bindings before charging and at every delayed dispatch."
17606
+ }
17047
17607
  },
17048
17608
  "additionalProperties": false
17049
17609
  },
@@ -17121,7 +17681,8 @@
17121
17681
  "provider_retryable",
17122
17682
  "provider_terminal",
17123
17683
  "provider_outcome_unknown",
17124
- "internal_error"
17684
+ "internal_error",
17685
+ "not_consented_for_marketing"
17125
17686
  ]
17126
17687
  },
17127
17688
  "ProblemFieldError": {
@@ -17312,8 +17873,8 @@
17312
17873
  },
17313
17874
  "OrganizationRole": {
17314
17875
  "type": "string",
17315
- "enum": ["admin", "member"],
17316
- "description": "The stable WhooshBang authorization role projected from the current Clerk organization membership."
17876
+ "enum": ["admin", "member", "messenger"],
17877
+ "description": "The person's WhooshBang role in the organization. `admin` does everything, including billing, API keys and teammates. `member` runs the projects they were given: channels, subscribers, messages and project settings. `messenger` sends messages and reads replies in the projects they were given. Clients must tolerate roles added later."
17317
17878
  },
17318
17879
  "OrganizationMembershipSummary": {
17319
17880
  "type": "object",
@@ -17339,6 +17900,27 @@
17339
17900
  },
17340
17901
  "additionalProperties": false
17341
17902
  },
17903
+ "TeammateStatus": {
17904
+ "type": "string",
17905
+ "enum": ["active", "invited"],
17906
+ "description": "`active` has joined. `invited` has an invitation they have not accepted."
17907
+ },
17908
+ "TeammateProjectAccess": {
17909
+ "type": "object",
17910
+ "description": "Which projects the teammate can see. An admin sees every project, including ones created later.",
17911
+ "required": ["all", "project_ids"],
17912
+ "properties": {
17913
+ "all": { "type": "boolean" },
17914
+ "project_ids": {
17915
+ "type": "array",
17916
+ "maxItems": 1000,
17917
+ "uniqueItems": true,
17918
+ "items": { "$ref": "#/components/schemas/OpaqueIdentifier" },
17919
+ "description": "The projects given to this teammate. Empty when `all` is true."
17920
+ }
17921
+ },
17922
+ "additionalProperties": false
17923
+ },
17342
17924
  "ProviderInstallationStatus": {
17343
17925
  "type": "string",
17344
17926
  "enum": [
@@ -19170,7 +19752,7 @@
19170
19752
  "provider-capabilities-v1.schema": {
19171
19753
  "$schema": "https://json-schema.org/draft/2020-12/schema",
19172
19754
  "$id": "https://whooshbang.flowxo.com/schemas/provider-capabilities-v1.schema.json",
19173
- "x-contract-version": "2.0.0-rc.7",
19755
+ "x-contract-version": "2.0.0",
19174
19756
  "title": "ProviderCapabilitiesV1",
19175
19757
  "description": "Provider-neutral, renderer-facing delivery and interaction capabilities with explicit degradation.",
19176
19758
  "type": "object",
@@ -19890,5 +20472,5 @@
19890
20472
  }
19891
20473
  }
19892
20474
  },
19893
- "x-contract-version": "2.0.0-rc.7"
20475
+ "x-contract-version": "2.0.0"
19894
20476
  }