@businessdash/sdk 0.9.60 → 0.9.80

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 (239) hide show
  1. package/README.md +991 -295
  2. package/assets/biab-sw.js +92 -0
  3. package/dist/account-core/index.d.ts +126 -0
  4. package/dist/account-core/index.d.ts.map +1 -0
  5. package/dist/account-core/index.js +191 -0
  6. package/dist/account-core/index.js.map +1 -0
  7. package/dist/account.d.ts +11 -0
  8. package/dist/account.d.ts.map +1 -0
  9. package/dist/account.js +11 -0
  10. package/dist/account.js.map +1 -0
  11. package/dist/auth-handler.d.ts +1 -1
  12. package/dist/auth-links.d.ts +33 -0
  13. package/dist/auth-links.d.ts.map +1 -0
  14. package/dist/auth-links.js +64 -0
  15. package/dist/auth-links.js.map +1 -0
  16. package/dist/blog-core/index.d.ts +112 -0
  17. package/dist/blog-core/index.d.ts.map +1 -0
  18. package/dist/blog-core/index.js +330 -0
  19. package/dist/blog-core/index.js.map +1 -0
  20. package/dist/blog.d.ts +11 -0
  21. package/dist/blog.d.ts.map +1 -0
  22. package/dist/blog.js +11 -0
  23. package/dist/blog.js.map +1 -0
  24. package/dist/chat-core/index.d.ts +97 -0
  25. package/dist/chat-core/index.d.ts.map +1 -0
  26. package/dist/chat-core/index.js +237 -0
  27. package/dist/chat-core/index.js.map +1 -0
  28. package/dist/chat.d.ts +11 -0
  29. package/dist/chat.d.ts.map +1 -0
  30. package/dist/chat.js +11 -0
  31. package/dist/chat.js.map +1 -0
  32. package/dist/checkout-core/index.d.ts +65 -0
  33. package/dist/checkout-core/index.d.ts.map +1 -0
  34. package/dist/checkout-core/index.js +159 -0
  35. package/dist/checkout-core/index.js.map +1 -0
  36. package/dist/checkout.d.ts +10 -0
  37. package/dist/checkout.d.ts.map +1 -0
  38. package/dist/checkout.js +10 -0
  39. package/dist/checkout.js.map +1 -0
  40. package/dist/cli.js +133 -25
  41. package/dist/cli.js.map +1 -1
  42. package/dist/client.d.ts +622 -1
  43. package/dist/client.d.ts.map +1 -1
  44. package/dist/client.js +888 -1
  45. package/dist/client.js.map +1 -1
  46. package/dist/consent-core/index.cjs +107 -0
  47. package/dist/consent-core/index.d.ts +103 -0
  48. package/dist/consent-core/index.d.ts.map +1 -0
  49. package/dist/consent-core/index.js +157 -0
  50. package/dist/consent-core/index.js.map +1 -0
  51. package/dist/contracts.d.ts +3526 -767
  52. package/dist/contracts.d.ts.map +1 -1
  53. package/dist/contracts.js +571 -4
  54. package/dist/contracts.js.map +1 -1
  55. package/dist/data-model-schema.d.ts +1 -1
  56. package/dist/data-model-schema.d.ts.map +1 -1
  57. package/dist/element.d.ts +30 -0
  58. package/dist/element.d.ts.map +1 -1
  59. package/dist/element.js +56 -0
  60. package/dist/element.js.map +1 -1
  61. package/dist/embed-protocol.d.ts +86 -0
  62. package/dist/embed-protocol.d.ts.map +1 -0
  63. package/dist/embed-protocol.js +60 -0
  64. package/dist/embed-protocol.js.map +1 -0
  65. package/dist/framework-support.d.ts +122 -0
  66. package/dist/framework-support.d.ts.map +1 -0
  67. package/dist/framework-support.js +323 -0
  68. package/dist/framework-support.js.map +1 -0
  69. package/dist/index.cjs +1355 -24
  70. package/dist/legal-core/index.cjs +115 -0
  71. package/dist/legal-core/index.d.ts +160 -0
  72. package/dist/legal-core/index.d.ts.map +1 -0
  73. package/dist/legal-core/index.js +220 -0
  74. package/dist/legal-core/index.js.map +1 -0
  75. package/dist/marketing-core/index.d.ts +66 -0
  76. package/dist/marketing-core/index.d.ts.map +1 -0
  77. package/dist/marketing-core/index.js +154 -0
  78. package/dist/marketing-core/index.js.map +1 -0
  79. package/dist/marketing.d.ts +7 -0
  80. package/dist/marketing.d.ts.map +1 -0
  81. package/dist/marketing.js +7 -0
  82. package/dist/marketing.js.map +1 -0
  83. package/dist/mcp-adapters/index.d.ts +135 -0
  84. package/dist/mcp-adapters/index.d.ts.map +1 -0
  85. package/dist/mcp-adapters/index.js +187 -0
  86. package/dist/mcp-adapters/index.js.map +1 -0
  87. package/dist/mcp-adapters.d.ts +7 -0
  88. package/dist/mcp-adapters.d.ts.map +1 -0
  89. package/dist/mcp-adapters.js +7 -0
  90. package/dist/mcp-adapters.js.map +1 -0
  91. package/dist/model3d-core/index.cjs +95 -0
  92. package/dist/model3d-core/index.d.ts +167 -0
  93. package/dist/model3d-core/index.d.ts.map +1 -0
  94. package/dist/model3d-core/index.js +158 -0
  95. package/dist/model3d-core/index.js.map +1 -0
  96. package/dist/notifications-core/index.cjs +315 -0
  97. package/dist/notifications-core/index.d.ts +229 -0
  98. package/dist/notifications-core/index.d.ts.map +1 -0
  99. package/dist/notifications-core/index.js +445 -0
  100. package/dist/notifications-core/index.js.map +1 -0
  101. package/dist/portal-core/index.d.ts +451 -0
  102. package/dist/portal-core/index.d.ts.map +1 -0
  103. package/dist/portal-core/index.js +819 -0
  104. package/dist/portal-core/index.js.map +1 -0
  105. package/dist/portal.d.ts +7 -0
  106. package/dist/portal.d.ts.map +1 -0
  107. package/dist/portal.js +7 -0
  108. package/dist/portal.js.map +1 -0
  109. package/dist/push-core/index.cjs +171 -0
  110. package/dist/push-core/index.d.ts +149 -0
  111. package/dist/push-core/index.d.ts.map +1 -0
  112. package/dist/push-core/index.js +231 -0
  113. package/dist/push-core/index.js.map +1 -0
  114. package/dist/react-marketing.d.ts +1 -17
  115. package/dist/react-marketing.d.ts.map +1 -1
  116. package/dist/react-marketing.js +4 -0
  117. package/dist/react-marketing.js.map +1 -1
  118. package/dist/react.d.ts +294 -101
  119. package/dist/react.d.ts.map +1 -1
  120. package/dist/react.js +302 -246
  121. package/dist/react.js.map +1 -1
  122. package/dist/seed-core/index.d.ts +354 -0
  123. package/dist/seed-core/index.d.ts.map +1 -0
  124. package/dist/seed-core/index.js +420 -0
  125. package/dist/seed-core/index.js.map +1 -0
  126. package/dist/seed.d.ts +7 -0
  127. package/dist/seed.d.ts.map +1 -0
  128. package/dist/seed.js +7 -0
  129. package/dist/seed.js.map +1 -0
  130. package/dist/seo-core/blog.d.ts +94 -0
  131. package/dist/seo-core/blog.d.ts.map +1 -0
  132. package/dist/seo-core/blog.js +149 -0
  133. package/dist/seo-core/blog.js.map +1 -0
  134. package/dist/seo-core/commerce.d.ts +64 -0
  135. package/dist/seo-core/commerce.d.ts.map +1 -0
  136. package/dist/seo-core/commerce.js +87 -0
  137. package/dist/seo-core/commerce.js.map +1 -0
  138. package/dist/seo-core/index.cjs +751 -0
  139. package/dist/seo-core/index.d.ts +226 -0
  140. package/dist/seo-core/index.d.ts.map +1 -0
  141. package/dist/seo-core/index.js +455 -0
  142. package/dist/seo-core/index.js.map +1 -0
  143. package/dist/seo-core/local.d.ts +181 -0
  144. package/dist/seo-core/local.d.ts.map +1 -0
  145. package/dist/seo-core/local.js +193 -0
  146. package/dist/seo-core/local.js.map +1 -0
  147. package/dist/sitemap-core/index.cjs +167 -0
  148. package/dist/sitemap-core/index.d.ts +155 -0
  149. package/dist/sitemap-core/index.d.ts.map +1 -0
  150. package/dist/sitemap-core/index.js +195 -0
  151. package/dist/sitemap-core/index.js.map +1 -0
  152. package/dist/social-core/index.d.ts +74 -0
  153. package/dist/social-core/index.d.ts.map +1 -0
  154. package/dist/social-core/index.js +72 -0
  155. package/dist/social-core/index.js.map +1 -0
  156. package/dist/social.d.ts +7 -0
  157. package/dist/social.d.ts.map +1 -0
  158. package/dist/social.js +7 -0
  159. package/dist/social.js.map +1 -0
  160. package/dist/solid-compat.d.ts +204 -0
  161. package/dist/solid-compat.d.ts.map +1 -0
  162. package/dist/solid-compat.js +221 -0
  163. package/dist/solid-compat.js.map +1 -0
  164. package/dist/store-core/index.d.ts +141 -0
  165. package/dist/store-core/index.d.ts.map +1 -0
  166. package/dist/store-core/index.js +277 -0
  167. package/dist/store-core/index.js.map +1 -0
  168. package/dist/store.d.ts +12 -0
  169. package/dist/store.d.ts.map +1 -0
  170. package/dist/store.js +12 -0
  171. package/dist/store.js.map +1 -0
  172. package/dist/vanilla.d.ts +111 -0
  173. package/dist/vanilla.d.ts.map +1 -1
  174. package/dist/vanilla.js +206 -0
  175. package/dist/vanilla.js.map +1 -1
  176. package/dist/verify-core/index.d.ts +98 -0
  177. package/dist/verify-core/index.d.ts.map +1 -0
  178. package/dist/verify-core/index.js +186 -0
  179. package/dist/verify-core/index.js.map +1 -0
  180. package/dist/verify.d.ts +8 -0
  181. package/dist/verify.d.ts.map +1 -0
  182. package/dist/verify.js +8 -0
  183. package/dist/verify.js.map +1 -0
  184. package/openapi.json +4516 -1311
  185. package/package.json +128 -11
  186. package/src/account-core/index.ts +309 -0
  187. package/src/account.ts +11 -0
  188. package/src/angular/headless.ts +519 -0
  189. package/src/angular/index.ts +3 -0
  190. package/src/auth-links.ts +76 -0
  191. package/src/blog-core/index.ts +423 -0
  192. package/src/blog.ts +11 -0
  193. package/src/chat-core/index.ts +335 -0
  194. package/src/chat.ts +11 -0
  195. package/src/checkout-core/index.ts +225 -0
  196. package/src/checkout.ts +10 -0
  197. package/src/cli.ts +184 -28
  198. package/src/client.ts +1097 -0
  199. package/src/consent-core/index.ts +213 -0
  200. package/src/contracts.ts +768 -11
  201. package/src/data-model-schema.ts +1 -1
  202. package/src/element.ts +102 -0
  203. package/src/embed-protocol.ts +100 -0
  204. package/src/framework-support.ts +440 -0
  205. package/src/legal-core/index.ts +276 -0
  206. package/src/marketing-core/index.ts +209 -0
  207. package/src/marketing.ts +7 -0
  208. package/src/mcp-adapters/index.ts +264 -0
  209. package/src/mcp-adapters.ts +7 -0
  210. package/src/model3d-core/index.ts +248 -0
  211. package/src/notifications-core/index.ts +650 -0
  212. package/src/portal-core/index.ts +1564 -0
  213. package/src/portal.ts +7 -0
  214. package/src/push-core/index.ts +335 -0
  215. package/src/qwik.tsx +602 -2
  216. package/src/react-marketing.tsx +5 -0
  217. package/src/react.tsx +434 -319
  218. package/src/seed-core/index.ts +735 -0
  219. package/src/seed.ts +7 -0
  220. package/src/seo-core/blog.ts +216 -0
  221. package/src/seo-core/commerce.ts +138 -0
  222. package/src/seo-core/index.ts +605 -0
  223. package/src/seo-core/local.ts +333 -0
  224. package/src/sitemap-core/index.ts +296 -0
  225. package/src/social-core/index.ts +103 -0
  226. package/src/social.ts +7 -0
  227. package/src/solid-compat.tsx +347 -0
  228. package/src/solid-surfaces.tsx +1311 -0
  229. package/src/solid.tsx +82 -60
  230. package/src/store-core/index.ts +426 -0
  231. package/src/store.ts +12 -0
  232. package/src/svelte/SocialLinks.svelte +84 -0
  233. package/src/svelte/headless.ts +435 -0
  234. package/src/svelte/index.ts +9 -0
  235. package/src/vanilla.ts +367 -0
  236. package/src/verify-core/index.ts +288 -0
  237. package/src/verify.ts +8 -0
  238. package/src/vue/headless.ts +527 -0
  239. package/src/vue/index.ts +3 -0
package/src/client.ts CHANGED
@@ -70,6 +70,9 @@ import {
70
70
  type CheckoutStatusResponse,
71
71
  type ConfirmVerificationInput,
72
72
  type ConfirmVerificationResponse,
73
+ type CouponCreateInput,
74
+ type CouponCreateResponse,
75
+ type CouponListResponse,
73
76
  type CouponValidateResponse,
74
77
  type CreateCollectionInput,
75
78
  type CreateCollectionResponse,
@@ -80,9 +83,12 @@ import {
80
83
  type CustomerContactCompanyInput,
81
84
  type CustomerContactCompanyResponse,
82
85
  type CustomerContractDetail,
86
+ type CustomerContractListResponse,
83
87
  type CustomerContractSignResponse,
84
88
  type CustomerDownloadsResponse,
89
+ type CustomerInviteResendResponse,
85
90
  type CustomerInvoiceDetail,
91
+ type CustomerInvoiceListResponse,
86
92
  type CustomerInvoicePayInput,
87
93
  type CustomerInvoicePayResponse,
88
94
  type CustomerInvoicePdf,
@@ -95,24 +101,39 @@ import {
95
101
  type CustomerNotificationPreferencesResponse,
96
102
  type CustomerNotificationPreferencesUpdateInput,
97
103
  type CustomerNotificationPreferencesUpdateResponse,
104
+ type CustomerNotReceivedResult,
98
105
  type CustomerOrderDetail,
99
106
  type CustomerOrderListResponse,
100
107
  type CustomerPortalContextResponse,
108
+ type CustomerPortalMessage,
109
+ type CustomerPortalMessageListResponse,
110
+ type CustomerPortalMessagePostInput,
111
+ type CustomerPortalMessagesMarkReadResponse,
101
112
  type CustomerPortalOtherOrgsResponse,
102
113
  type CustomerPortalProfile,
114
+ type CustomerPortalQuizResultsResponse,
103
115
  type CustomerPortalUpdateProfileInput,
104
116
  type CustomerPortalUpdateProfileResponse,
117
+ type CustomerProductReviewCreated,
118
+ type CustomerProductReviewList,
119
+ type CustomerPushConfig,
105
120
  type CustomerQuoteActionResponse,
106
121
  type CustomerQuoteDetail,
122
+ type CustomerQuoteListResponse,
107
123
  type CustomerReferralHistoryResponse,
108
124
  type CustomerReferralPayoutsResponse,
109
125
  type CustomerReferralStats,
110
126
  type CustomerReturnRequestCreated,
111
127
  type CustomerReturnRequestInput,
128
+ type CustomerReturnRequestListResponse,
112
129
  type CustomerReviewListResponse,
113
130
  type CustomerReviewSubmitInput,
114
131
  type CustomerReviewSubmitResponse,
132
+ type CustomerShipmentListResponse,
115
133
  type CustomerShipmentTracking,
134
+ type CustomerSubscriberContentResponse,
135
+ type CustomerSubscriptionCancelResponse,
136
+ type CustomerSubscriptionResponse,
116
137
  type CustomerWorkBundle,
117
138
  cartAddItemInputSchema,
118
139
  cartCouponInputSchema,
@@ -142,6 +163,9 @@ import {
142
163
  checkoutStatusResponseSchema,
143
164
  confirmVerificationInputSchema,
144
165
  confirmVerificationResponseSchema,
166
+ couponCreateInputSchema,
167
+ couponCreateResponseSchema,
168
+ couponListResponseSchema,
145
169
  couponValidateInputSchema,
146
170
  couponValidateResponseSchema,
147
171
  createCollectionInputSchema,
@@ -153,9 +177,12 @@ import {
153
177
  customerContactCompanyInputSchema,
154
178
  customerContactCompanyResponseSchema,
155
179
  customerContractDetailSchema,
180
+ customerContractListResponseSchema,
156
181
  customerContractSignResponseSchema,
157
182
  customerDownloadsResponseSchema,
183
+ customerInviteResendResponseSchema,
158
184
  customerInvoiceDetailSchema,
185
+ customerInvoiceListResponseSchema,
159
186
  customerInvoicePayInputSchema,
160
187
  customerInvoicePayResponseSchema,
161
188
  customerInvoicePdfSchema,
@@ -168,25 +195,39 @@ import {
168
195
  customerNotificationPreferencesResponseSchema,
169
196
  customerNotificationPreferencesUpdateInputSchema,
170
197
  customerNotificationPreferencesUpdateResponseSchema,
198
+ customerNotReceivedResultSchema,
171
199
  customerOrderDetailSchema,
172
200
  customerOrderListResponseSchema,
173
201
  customerPaymentHistoryResponseSchema,
174
202
  customerPortalContextResponseSchema,
203
+ customerPortalMessageListResponseSchema,
204
+ customerPortalMessageSchema,
205
+ customerPortalMessagesMarkReadResponseSchema,
175
206
  customerPortalOtherOrgsResponseSchema,
176
207
  customerPortalProfileSchema,
208
+ customerPortalQuizResultsResponseSchema,
177
209
  customerPortalUpdateProfileInputSchema,
178
210
  customerPortalUpdateProfileResponseSchema,
211
+ customerProductReviewCreatedSchema,
212
+ customerProductReviewListSchema,
213
+ customerPushConfigSchema,
179
214
  customerQuoteActionResponseSchema,
180
215
  customerQuoteDetailSchema,
216
+ customerQuoteListResponseSchema,
181
217
  customerReferralHistoryResponseSchema,
182
218
  customerReferralPayoutsResponseSchema,
183
219
  customerReferralStatsSchema,
184
220
  customerReturnRequestCreatedSchema,
185
221
  customerReturnRequestInputSchema,
222
+ customerReturnRequestListResponseSchema,
186
223
  customerReviewListResponseSchema,
187
224
  customerReviewSubmitInputSchema,
188
225
  customerReviewSubmitResponseSchema,
226
+ customerShipmentListResponseSchema,
189
227
  customerShipmentTrackingSchema,
228
+ customerSubscriberContentResponseSchema,
229
+ customerSubscriptionCancelResponseSchema,
230
+ customerSubscriptionResponseSchema,
190
231
  customerWorkBundleSchema,
191
232
  type DashboardSessionResponse,
192
233
  type DataModelRecord,
@@ -230,6 +271,8 @@ import {
230
271
  jobCategoryListResponseSchema,
231
272
  jobPostingListResponseSchema,
232
273
  jobPostingSchema,
274
+ type LegalDocument,
275
+ type LegalListResponse,
233
276
  type ListCollectionsResponse,
234
277
  type ListDataModelRecordsInput,
235
278
  type ListDataModelRecordsResponse,
@@ -238,6 +281,8 @@ import {
238
281
  type ListRowsInput,
239
282
  type ListRowsResponse,
240
283
  type ListSiteMarketingPagesResponse,
284
+ legalDocumentSchema,
285
+ legalListResponseSchema,
241
286
  listCollectionsResponseSchema,
242
287
  listDataModelRecordsInputSchema,
243
288
  listDataModelRecordsResponseSchema,
@@ -264,6 +309,13 @@ import {
264
309
  queryRowsResponseSchema,
265
310
  type ReviewWallListResponse,
266
311
  reviewWallListResponseSchema,
312
+ type SchedulingBookingResult,
313
+ type SchedulingBookingView,
314
+ type SchedulingCancelResult,
315
+ type SchedulingEventTypeDetail,
316
+ type SchedulingEventTypeList,
317
+ type SchedulingRescheduleResult,
318
+ type SchedulingSlotsResponse,
267
319
  type ShippingRatesInput,
268
320
  type ShippingRatesResponse,
269
321
  type ShippingSettingsResponse,
@@ -282,10 +334,18 @@ import {
282
334
  type StorefrontSessionInput,
283
335
  type StorefrontSessionResponse,
284
336
  type StorefrontSort,
337
+ type StorefrontSyncToStripeResponse,
285
338
  type SubscriptionCheckoutInput,
286
339
  type SubscriptionCheckoutResponse,
287
340
  type SubscriptionOffering,
288
341
  type SubscriptionOfferingsListResponse,
342
+ schedulingBookingResultSchema,
343
+ schedulingBookingViewSchema,
344
+ schedulingCancelResultSchema,
345
+ schedulingEventTypeDetailSchema,
346
+ schedulingEventTypeListSchema,
347
+ schedulingRescheduleResultSchema,
348
+ schedulingSlotsResponseSchema,
289
349
  shippingRatesInputSchema,
290
350
  shippingRatesResponseSchema,
291
351
  shippingSettingsResponseSchema,
@@ -303,6 +363,7 @@ import {
303
363
  storefrontRelatedProductsResponseSchema,
304
364
  storefrontSessionInputSchema,
305
365
  storefrontSessionResponseSchema,
366
+ storefrontSyncToStripeResponseSchema,
306
367
  subscriptionCheckoutInputSchema,
307
368
  subscriptionCheckoutResponseSchema,
308
369
  subscriptionOfferingSchema,
@@ -513,6 +574,11 @@ export class BiabDevClient {
513
574
  return new BiabDevShippingClient(this);
514
575
  }
515
576
 
577
+ /** Customer-portal invitations — currently resend. */
578
+ get customerInvites() {
579
+ return new BiabDevCustomerInvitesClient(this);
580
+ }
581
+
516
582
  get notifications() {
517
583
  return new BiabDevNotificationsClient(this);
518
584
  }
@@ -529,11 +595,32 @@ export class BiabDevClient {
529
595
  return new BiabDevReviewsClient(this);
530
596
  }
531
597
 
598
+ /**
599
+ * Tracking consent for this org's site.
600
+ *
601
+ * Pair with `@businessdash/sdk/consent`, which handles the visitor cookie
602
+ * and gives you a gate to check before starting analytics.
603
+ */
604
+ get consent() {
605
+ return new BiabDevConsentClient(this);
606
+ }
607
+
532
608
  /** Public job postings — build a careers page on your own domain. */
533
609
  get hiring() {
534
610
  return new BiabDevHiringClient(this);
535
611
  }
536
612
 
613
+ /**
614
+ * The org's published legal pages — privacy, terms, refund policy, whatever
615
+ * they authored in the dashboard.
616
+ *
617
+ * Pair with `@businessdash/sdk/legal` to render them without writing a page
618
+ * per document.
619
+ */
620
+ get legal() {
621
+ return new BiabDevLegalClient(this);
622
+ }
623
+
537
624
  get chatbot() {
538
625
  return new BiabDevChatbotClient(this);
539
626
  }
@@ -648,12 +735,21 @@ export class BiabDevSiteClient {
648
735
  */
649
736
  readonly marketing: BiabDevMarketingClient;
650
737
  readonly parallelPages: BiabDevParallelPagesClient;
738
+ /**
739
+ * Booking and conference calls.
740
+ *
741
+ * Previously reachable only through `sdk.ts`'s separate scheduling
742
+ * resource, which is why the OpenAPI generator — which reads this file —
743
+ * never saw it, and why no non-JS starter had scheduling at all.
744
+ */
745
+ readonly scheduling: BiabDevSchedulingClient;
651
746
 
652
747
  constructor(
653
748
  readonly client: BiabDevClient,
654
749
  readonly siteId: string,
655
750
  ) {
656
751
  this.collections = new BiabDevCollectionsClient(client, siteId);
752
+ this.scheduling = new BiabDevSchedulingClient(client, siteId);
657
753
  this.rows = new BiabDevRowsClient(client, siteId);
658
754
  this.dataModel = new BiabDevDataModelClient(client, siteId);
659
755
  this.analytics = new BiabDevAnalyticsClient(client, siteId);
@@ -677,6 +773,30 @@ export class BiabDevSiteClient {
677
773
  * <img src={b.logoUrl ?? b.orgIcon ?? ""} alt={b.orgName ?? ""} />
678
774
  * ```
679
775
  */
776
+ /**
777
+ * Every URL the PLATFORM owns the shape of, as JSON.
778
+ *
779
+ * Published site-builder pages, published legal documents, and every
780
+ * materialised programmatic-page URL. Blog posts and products are not here
781
+ * — the platform does not know where your site renders them. Merge those in
782
+ * with `@businessdash/sdk/sitemap`, which handles the whole job.
783
+ *
784
+ * Paths are relative, so you join them against your own origin rather than
785
+ * the org's primary verified domain.
786
+ */
787
+ async sitemapEntries(): Promise<{
788
+ entries: Array<{ loc: string; lastmod?: string | undefined }>;
789
+ }> {
790
+ return this.client.request({
791
+ path: `sites/${encodeURIComponent(this.siteId)}/sitemap-entries`,
792
+ responseSchema: z.object({
793
+ entries: z.array(
794
+ z.object({ loc: z.string(), lastmod: z.string().optional() }),
795
+ ),
796
+ }),
797
+ });
798
+ }
799
+
680
800
  async branding(): Promise<SiteBranding> {
681
801
  return this.client.request({
682
802
  path: `sites/${encodeURIComponent(this.siteId)}/branding`,
@@ -1591,6 +1711,45 @@ export class BiabDevStorefrontClient {
1591
1711
  });
1592
1712
  }
1593
1713
 
1714
+ /**
1715
+ * Push your catalog into YOUR OWN Stripe account.
1716
+ *
1717
+ * Products and their variant combinations become Stripe Products and
1718
+ * Prices on the Stripe account connected to your workspace, so you can
1719
+ * manage the catalog here and let Stripe be the system of record for
1720
+ * money. Safe to re-run: an unchanged product is left alone, and a new
1721
+ * Price is minted only when the amount actually changed (Stripe Prices
1722
+ * are immutable on amount).
1723
+ *
1724
+ * Requires a SECRET key holding `storefront:write` — never a publishable
1725
+ * token, because this spends against Stripe on your behalf.
1726
+ *
1727
+ * Pass `productId` to sync one product. Omit it to sync everything not yet
1728
+ * pushed, in bounded batches: check `remaining` and call again until it
1729
+ * reaches 0.
1730
+ *
1731
+ * ```ts
1732
+ * let remaining = 1;
1733
+ * while (remaining > 0) {
1734
+ * const res = await biab.storefront.syncToStripe();
1735
+ * remaining = res.remaining;
1736
+ * }
1737
+ * ```
1738
+ *
1739
+ * Per-product failures are returned in `failed` rather than throwing, so
1740
+ * one bad product doesn't abandon the batch.
1741
+ */
1742
+ async syncToStripe(input?: {
1743
+ productId?: string;
1744
+ }): Promise<StorefrontSyncToStripeResponse> {
1745
+ return this.client.request({
1746
+ method: "POST",
1747
+ path: "storefront/products/sync-to-stripe",
1748
+ body: input?.productId ? { productId: input.productId } : {},
1749
+ responseSchema: storefrontSyncToStripeResponseSchema,
1750
+ });
1751
+ }
1752
+
1594
1753
  /**
1595
1754
  * Paginated list of live products for the calling org's storefront.
1596
1755
  * Cursor is an opaque integer offset — pass back the response's
@@ -1662,6 +1821,43 @@ export class BiabDevStorefrontClient {
1662
1821
  * via `BiabApiError` if the product isn't live or doesn't belong
1663
1822
  * to the org bound to the API key.
1664
1823
  */
1824
+ /**
1825
+ * The CAD file for a product — STEP, IGES, or a native part file.
1826
+ *
1827
+ * ## Call this from YOUR server, then proxy it
1828
+ *
1829
+ * The API key is a server secret, so a browser cannot reach this directly.
1830
+ * Expose your own route (`/api/products/:id/cad`), call this behind it, and
1831
+ * redirect the visitor to the URL it returns.
1832
+ *
1833
+ * The platform withholds the raw URL from every product payload on purpose:
1834
+ * CAD files are the largest thing it serves and are the org's intellectual
1835
+ * property, so a catalog of naked URLs is a design library published to
1836
+ * anyone willing to write a loop. This endpoint requires a `biab_cad` cookie
1837
+ * on the incoming request, rate-limits per IP, and re-checks the
1838
+ * `ecommerce.product_3d` entitlement before answering.
1839
+ *
1840
+ * `cacheSeconds` is what to put on your own response — CAD at a content URL
1841
+ * does not change, so caching hard is where the bandwidth saving comes from.
1842
+ *
1843
+ * Rejects 404 when the product is a draft, has no CAD file, or the org lacks
1844
+ * the add-on. Those are deliberately one answer.
1845
+ */
1846
+ async getCadDownload(productId: string): Promise<{
1847
+ url: string;
1848
+ fileName: string | null;
1849
+ cacheSeconds: number;
1850
+ }> {
1851
+ return this.client.request({
1852
+ path: `storefront/products/${encodeURIComponent(productId)}/cad`,
1853
+ responseSchema: z.object({
1854
+ url: z.string(),
1855
+ fileName: z.string().nullable(),
1856
+ cacheSeconds: z.number(),
1857
+ }),
1858
+ });
1859
+ }
1860
+
1665
1861
  async getProduct(productId: string): Promise<StorefrontProductDetail> {
1666
1862
  return this.client.request({
1667
1863
  path: `storefront/products/${encodeURIComponent(productId)}`,
@@ -2087,6 +2283,49 @@ export class BiabDevCouponsClient {
2087
2283
  * else toast.error(result.message);
2088
2284
  * ```
2089
2285
  */
2286
+ /**
2287
+ * Create a coupon and make it live in YOUR OWN Stripe account in one call —
2288
+ * a Stripe Coupon plus a Promotion Code, so the same string works on your
2289
+ * storefront and on Stripe-hosted Checkout.
2290
+ *
2291
+ * Creation and the Stripe sync are one operation deliberately: a coupon
2292
+ * that exists locally but not in Stripe is the failure worth designing out,
2293
+ * where a customer types a code your storefront accepts and Stripe rejects.
2294
+ * If the sync fails the coupon is still created and `stripeSynced` comes
2295
+ * back `false` with the reason, rather than pretending it is live.
2296
+ *
2297
+ * Requires a SECRET key holding `storefront:write`.
2298
+ *
2299
+ * ```ts
2300
+ * const { coupon, stripeSynced } = await biab.coupons.create({
2301
+ * code: 'WELCOME10',
2302
+ * discountType: 'percent',
2303
+ * value: 10,
2304
+ * })
2305
+ * ```
2306
+ *
2307
+ * Buy-X-get-Y and bundle-gift coupons aren't available here — Stripe has no
2308
+ * native BXGY, so those run through our own cart math and need the offer
2309
+ * rows the dashboard collects.
2310
+ */
2311
+ async create(input: CouponCreateInput): Promise<CouponCreateResponse> {
2312
+ const body = couponCreateInputSchema.parse(input);
2313
+ return this.client.request({
2314
+ method: "POST",
2315
+ path: "coupons",
2316
+ body,
2317
+ responseSchema: couponCreateResponseSchema,
2318
+ });
2319
+ }
2320
+
2321
+ /** Every coupon on the workspace, newest first. Requires `storefront:write`. */
2322
+ async list(): Promise<CouponListResponse> {
2323
+ return this.client.request({
2324
+ path: "coupons",
2325
+ responseSchema: couponListResponseSchema,
2326
+ });
2327
+ }
2328
+
2090
2329
  async validate(code: string): Promise<CouponValidateResponse> {
2091
2330
  const body = couponValidateInputSchema.parse({ code });
2092
2331
  return this.client.request({
@@ -2555,6 +2794,118 @@ export class BiabDevHiringClient {
2555
2794
  }
2556
2795
  }
2557
2796
 
2797
+ /**
2798
+ * The org's published legal pages.
2799
+ *
2800
+ * Everything here is content the org deliberately put on the public internet;
2801
+ * drafts are filtered server-side and unreachable. Requires `legal:read`, which
2802
+ * is publishable — a browser token is the expected caller.
2803
+ *
2804
+ * @example
2805
+ * ```ts
2806
+ * const { items, presentation } = await client.legal.list();
2807
+ * const privacy = await client.legal.get("privacy");
2808
+ * ```
2809
+ */
2810
+ export class BiabDevLegalClient {
2811
+ constructor(private readonly client: BiabDevClient) {}
2812
+
2813
+ /**
2814
+ * Every published document, plus the header/footer presentation.
2815
+ *
2816
+ * Both in one response because the common use — a footer link list — needs
2817
+ * the titles and the presentation together, and a second round trip for the
2818
+ * latter is one a site either makes on every page or skips entirely.
2819
+ */
2820
+ async list(): Promise<LegalListResponse> {
2821
+ return this.client.request({
2822
+ path: "legal",
2823
+ responseSchema: legalListResponseSchema,
2824
+ });
2825
+ }
2826
+
2827
+ /**
2828
+ * One document by slug, body included.
2829
+ *
2830
+ * Rejects with a 404 when the slug is unpublished or absent — the two are
2831
+ * deliberately indistinguishable, since "exists but hidden" would leak that
2832
+ * an org is drafting a policy and is not something a caller can act on.
2833
+ */
2834
+ async get(slug: string): Promise<LegalDocument> {
2835
+ return this.client.request({
2836
+ path: `legal/${encodeURIComponent(slug)}`,
2837
+ responseSchema: legalDocumentSchema,
2838
+ });
2839
+ }
2840
+ }
2841
+
2842
+ const consentStateSchema = z.object({
2843
+ /** Categories this org uses at all — one banner row each. */
2844
+ categories: z.array(z.string()),
2845
+ noticeText: z.string().nullable(),
2846
+ noticeVersion: z.number(),
2847
+ /** What may be used right now. */
2848
+ allowed: z.array(z.string()),
2849
+ mustAsk: z.boolean(),
2850
+ reason: z.string(),
2851
+ });
2852
+
2853
+ /**
2854
+ * Tracking consent, scoped to the calling org.
2855
+ *
2856
+ * Every call is answered for the org whose key made it. There is no shape here
2857
+ * that could read or write another org's consent — consent is given to a
2858
+ * controller, and one controller's record is not another's to act on.
2859
+ *
2860
+ * The visitor is identified by an `x-biab-visitor` header carrying an opaque id
2861
+ * from the caller's own first-party cookie. `@businessdash/sdk/consent` manages
2862
+ * that for you; these methods are the transport underneath it.
2863
+ */
2864
+ export class BiabDevConsentClient {
2865
+ constructor(private readonly client: BiabDevClient) {}
2866
+
2867
+ /** The notice to show, and what this visitor currently allows. */
2868
+ async get(visitorKey?: string | null): Promise<{
2869
+ categories: string[];
2870
+ noticeText: string | null;
2871
+ noticeVersion: number;
2872
+ allowed: string[];
2873
+ mustAsk: boolean;
2874
+ reason: string;
2875
+ }> {
2876
+ return this.client.request({
2877
+ path: "consent",
2878
+ responseSchema: consentStateSchema,
2879
+ ...(visitorKey ? { headers: { "x-biab-visitor": visitorKey } } : {}),
2880
+ });
2881
+ }
2882
+
2883
+ /**
2884
+ * Record the visitor's answer.
2885
+ *
2886
+ * `granted: []` is a real answer — "asked, refused everything" — and is
2887
+ * stored as one, so the banner does not reappear on the next page.
2888
+ */
2889
+ async record(input: {
2890
+ visitorKey: string;
2891
+ granted: string[];
2892
+ noticeVersion: number;
2893
+ source?: "banner" | "settings";
2894
+ }): Promise<{ ok: boolean }> {
2895
+ return this.client.request({
2896
+ method: "POST",
2897
+ path: "consent",
2898
+ body: {
2899
+ granted: input.granted,
2900
+ noticeVersion: input.noticeVersion,
2901
+ source: input.source ?? "banner",
2902
+ },
2903
+ headers: { "x-biab-visitor": input.visitorKey },
2904
+ responseSchema: z.object({ ok: z.boolean() }),
2905
+ });
2906
+ }
2907
+ }
2908
+
2558
2909
  export class BiabDevChatbotClient {
2559
2910
  constructor(private readonly client: BiabDevClient) {}
2560
2911
 
@@ -2918,6 +3269,26 @@ export class BiabDevCustomerPortalClient {
2918
3269
  });
2919
3270
  }
2920
3271
 
3272
+ /**
3273
+ * The signed-in customer's quiz results at this org — the SDK side of
3274
+ * the account-gated reveal: an invitation completed with "create an
3275
+ * account to see how you did" surfaces its result here (and only to
3276
+ * the invited email). Interview-mode quizzes come back with
3277
+ * `scoreWithheld: true` and no verdict, by the org's own setting.
3278
+ */
3279
+ async myQuizResults(options?: {
3280
+ token?: string;
3281
+ }): Promise<CustomerPortalQuizResultsResponse> {
3282
+ const params = new URLSearchParams();
3283
+ if (options?.token) params.set("token", options.token);
3284
+ const qs = params.toString();
3285
+ return this.client.request({
3286
+ path: `customer-portal/quiz-results${qs ? `?${qs}` : ""}`,
3287
+ responseSchema: customerPortalQuizResultsResponseSchema,
3288
+ headers: this.headers(),
3289
+ });
3290
+ }
3291
+
2921
3292
  async updateProfile(
2922
3293
  input: CustomerPortalUpdateProfileInput,
2923
3294
  ): Promise<CustomerPortalUpdateProfileResponse> {
@@ -3225,6 +3596,157 @@ export class BiabDevCustomerPortalClient {
3225
3596
  });
3226
3597
  }
3227
3598
 
3599
+ /**
3600
+ * The return requests on one order, newest first.
3601
+ *
3602
+ * `resolutionNote` carries staff's answer — when a return is denied that IS
3603
+ * the answer, and hiding it just moves the question to email.
3604
+ */
3605
+ async listReturnRequests(
3606
+ orderId: string,
3607
+ ): Promise<CustomerReturnRequestListResponse> {
3608
+ return this.client.request({
3609
+ path: `customer-portal/orders/${encodeURIComponent(orderId)}/return-request`,
3610
+ responseSchema: customerReturnRequestListResponseSchema,
3611
+ headers: this.headers(),
3612
+ });
3613
+ }
3614
+
3615
+ /**
3616
+ * The customer's message thread with the org's staff, newest first.
3617
+ *
3618
+ * Automated rows — the review-ask and similar — are excluded server-side, so
3619
+ * this is only what a person wrote.
3620
+ */
3621
+ async listMessages(opts?: {
3622
+ limit?: number;
3623
+ jobId?: string;
3624
+ }): Promise<CustomerPortalMessageListResponse> {
3625
+ const params = new URLSearchParams();
3626
+ if (typeof opts?.limit === "number")
3627
+ params.set("limit", String(opts.limit));
3628
+ if (opts?.jobId) params.set("jobId", opts.jobId);
3629
+ const qs = params.toString();
3630
+ return this.client.request({
3631
+ path: `customer-portal/messages${qs ? `?${qs}` : ""}`,
3632
+ responseSchema: customerPortalMessageListResponseSchema,
3633
+ headers: this.headers(),
3634
+ });
3635
+ }
3636
+
3637
+ /** Send a message to the org's staff. Lands in the CRM timeline. */
3638
+ async postMessage(
3639
+ input: CustomerPortalMessagePostInput,
3640
+ ): Promise<CustomerPortalMessage> {
3641
+ return this.client.request({
3642
+ path: "customer-portal/messages",
3643
+ method: "POST",
3644
+ body: input,
3645
+ responseSchema: customerPortalMessageSchema,
3646
+ headers: this.headers(),
3647
+ });
3648
+ }
3649
+
3650
+ /**
3651
+ * Mark the staff messages read up to now.
3652
+ *
3653
+ * One-way: it never clears a stamp. A portal that could un-read a message
3654
+ * would let a customer hide from staff that they had seen something.
3655
+ */
3656
+ async markMessagesRead(): Promise<CustomerPortalMessagesMarkReadResponse> {
3657
+ return this.client.request({
3658
+ path: "customer-portal/messages",
3659
+ method: "PATCH",
3660
+ responseSchema: customerPortalMessagesMarkReadResponseSchema,
3661
+ headers: this.headers(),
3662
+ });
3663
+ }
3664
+
3665
+ /**
3666
+ * The customer's invoices, newest first.
3667
+ *
3668
+ * `unpaid: true` filters on the computed BALANCE rather than on the status
3669
+ * string, because a partially-paid invoice still owes money whatever it is
3670
+ * called. This is the call behind "show me everything I owe" — which had no
3671
+ * endpoint at all before 0.9.61.
3672
+ */
3673
+ async listInvoices(opts?: {
3674
+ limit?: number;
3675
+ status?: string;
3676
+ unpaid?: boolean;
3677
+ }): Promise<CustomerInvoiceListResponse> {
3678
+ const params = new URLSearchParams();
3679
+ if (typeof opts?.limit === "number")
3680
+ params.set("limit", String(opts.limit));
3681
+ if (opts?.status) params.set("status", opts.status);
3682
+ if (opts?.unpaid) params.set("unpaid", "1");
3683
+ const qs = params.toString();
3684
+ return this.client.request({
3685
+ path: `customer-portal/invoices${qs ? `?${qs}` : ""}`,
3686
+ responseSchema: customerInvoiceListResponseSchema,
3687
+ headers: this.headers(),
3688
+ });
3689
+ }
3690
+
3691
+ /** The customer's quotes. `status: "sent"` is the set still awaiting them. */
3692
+ async listQuotes(opts?: {
3693
+ limit?: number;
3694
+ status?: string;
3695
+ }): Promise<CustomerQuoteListResponse> {
3696
+ const params = new URLSearchParams();
3697
+ if (typeof opts?.limit === "number")
3698
+ params.set("limit", String(opts.limit));
3699
+ if (opts?.status) params.set("status", opts.status);
3700
+ const qs = params.toString();
3701
+ return this.client.request({
3702
+ path: `customer-portal/quotes${qs ? `?${qs}` : ""}`,
3703
+ responseSchema: customerQuoteListResponseSchema,
3704
+ headers: this.headers(),
3705
+ });
3706
+ }
3707
+
3708
+ /** The customer's contracts. `status: "sent"` still awaits a signature. */
3709
+ async listContracts(opts?: {
3710
+ limit?: number;
3711
+ status?: string;
3712
+ }): Promise<CustomerContractListResponse> {
3713
+ const params = new URLSearchParams();
3714
+ if (typeof opts?.limit === "number")
3715
+ params.set("limit", String(opts.limit));
3716
+ if (opts?.status) params.set("status", opts.status);
3717
+ const qs = params.toString();
3718
+ return this.client.request({
3719
+ path: `customer-portal/contracts${qs ? `?${qs}` : ""}`,
3720
+ responseSchema: customerContractListResponseSchema,
3721
+ headers: this.headers(),
3722
+ });
3723
+ }
3724
+
3725
+ /**
3726
+ * Every shipment across the customer's orders. `active: true` narrows to
3727
+ * parcels still moving.
3728
+ *
3729
+ * STORED status only — no live carrier poll. `trackShipment(id)` spends a
3730
+ * Shippo request because it is one parcel the customer opened; doing that
3731
+ * per row here would mean a carrier call for every parcel in their history
3732
+ * on every page load.
3733
+ */
3734
+ async listShipments(opts?: {
3735
+ limit?: number;
3736
+ active?: boolean;
3737
+ }): Promise<CustomerShipmentListResponse> {
3738
+ const params = new URLSearchParams();
3739
+ if (typeof opts?.limit === "number")
3740
+ params.set("limit", String(opts.limit));
3741
+ if (opts?.active) params.set("active", "1");
3742
+ const qs = params.toString();
3743
+ return this.client.request({
3744
+ path: `customer-portal/shipments${qs ? `?${qs}` : ""}`,
3745
+ responseSchema: customerShipmentListResponseSchema,
3746
+ headers: this.headers(),
3747
+ });
3748
+ }
3749
+
3228
3750
  /**
3229
3751
  * The signed-in customer's orders (newest first), each with a shipment
3230
3752
  * roll-up (`overallStatus`, `anyInTransit`, `latestEstimatedDelivery`) for
@@ -3320,6 +3842,281 @@ export class BiabDevCustomerPortalClient {
3320
3842
  });
3321
3843
  }
3322
3844
 
3845
+ /**
3846
+ * Ask for a replacement instead of a refund.
3847
+ *
3848
+ * Sugar over `submitReturnRequest` with `kind: "exchange"`, because an
3849
+ * exchange reads as its own action to the person writing the UI, and burying
3850
+ * it in an options bag is how it stays undiscovered. `variantId` is the one
3851
+ * they want instead — omit it when that still needs a conversation.
3852
+ *
3853
+ * @example
3854
+ * ```ts
3855
+ * await portal.submitExchangeRequest(orderId, {
3856
+ * exchangeForVariantId: largeVariantId,
3857
+ * reason: "wrong size",
3858
+ * });
3859
+ * ```
3860
+ */
3861
+ async submitExchangeRequest(
3862
+ orderId: string,
3863
+ input?: Omit<CustomerReturnRequestInput, "kind">,
3864
+ ): Promise<CustomerReturnRequestCreated> {
3865
+ return this.submitReturnRequest(orderId, {
3866
+ ...(input ?? {}),
3867
+ kind: "exchange",
3868
+ });
3869
+ }
3870
+
3871
+ /**
3872
+ * Report that a parcel never arrived.
3873
+ *
3874
+ * This does NOT change the shipment's carrier status, and deliberately so:
3875
+ * when the carrier says delivered and the customer says otherwise, both
3876
+ * facts matter — the carrier's scan is what the org files a claim against.
3877
+ * The result carries `carrierStatus` alongside the report so a UI can show
3878
+ * both: *"Carrier says delivered Aug 14 · You reported not received Aug 16."*
3879
+ *
3880
+ * `contested: true` means exactly that disagreement. Re-reporting an open
3881
+ * claim returns it with `alreadyOpen: true` rather than erroring — a
3882
+ * customer tapping twice is anxious, not hostile.
3883
+ *
3884
+ * @example
3885
+ * ```ts
3886
+ * const r = await portal.reportNotReceived(shipmentId, {
3887
+ * note: "Nothing on the porch, checked with neighbours.",
3888
+ * });
3889
+ * if (r.contested) showCarrierDisputeHelp();
3890
+ * ```
3891
+ */
3892
+ async reportNotReceived(
3893
+ shipmentId: string,
3894
+ input?: { note?: string },
3895
+ ): Promise<CustomerNotReceivedResult> {
3896
+ const body = input?.note ? { note: input.note } : {};
3897
+ return this.client.request({
3898
+ method: "POST",
3899
+ path: `customer-portal/shipments/${encodeURIComponent(shipmentId)}/not-received`,
3900
+ body,
3901
+ responseSchema: customerNotReceivedResultSchema,
3902
+ headers: this.headers(),
3903
+ });
3904
+ }
3905
+
3906
+ /**
3907
+ * The product reviews this customer has written for this org.
3908
+ *
3909
+ * Distinct from `listReviews()`, which is reviews of the ORG. Both exist
3910
+ * because "are these people good to deal with" and "is this thing any good"
3911
+ * are different questions, stored in different tables, and shown in
3912
+ * different places.
3913
+ *
3914
+ * `isApproved: false` means QUEUED, not rejected — render it as "awaiting
3915
+ * review" or the customer will assume it was lost and write it again.
3916
+ */
3917
+ /**
3918
+ * The customer's subscription with this org, and what the org offers.
3919
+ *
3920
+ * One subscription per org — `user_subscriptions` is unique on
3921
+ * (user, org) — so there is no id to pass. The org's live offerings come
3922
+ * back alongside, because a portal that reports "not subscribed" and
3923
+ * nothing else is a dead end.
3924
+ *
3925
+ * **Render entitlement from `subscription.hasAccess`, not from `status`.**
3926
+ * A lifetime purchase has no period to expire and a cancelled subscription
3927
+ * keeps access until the period already paid for ends; `hasAccess` is
3928
+ * computed by the same function the content gates use, so the portal and
3929
+ * the gate cannot disagree.
3930
+ */
3931
+ async getSubscription(): Promise<CustomerSubscriptionResponse> {
3932
+ return this.client.request({
3933
+ path: "customer-portal/subscription",
3934
+ responseSchema: customerSubscriptionResponseSchema,
3935
+ headers: this.headers(),
3936
+ });
3937
+ }
3938
+
3939
+ /**
3940
+ * Cancel the subscription at the end of the paid period.
3941
+ *
3942
+ * Cancelling ends the RENEWAL, not the access: the customer has paid for
3943
+ * the period they are in and keeps everything until `accessUntil`. Ending
3944
+ * access on the click would take back something they already bought, which
3945
+ * is the most common way a subscription flow feels like a trap.
3946
+ *
3947
+ * Read `accessUntil` back to them — "active until 14 March" is the true
3948
+ * statement, not "cancelled".
3949
+ */
3950
+ async cancelSubscription(): Promise<CustomerSubscriptionCancelResponse> {
3951
+ return this.client.request({
3952
+ method: "POST",
3953
+ path: "customer-portal/subscription/cancel",
3954
+ body: { resume: false },
3955
+ responseSchema: customerSubscriptionCancelResponseSchema,
3956
+ headers: this.headers(),
3957
+ });
3958
+ }
3959
+
3960
+ /**
3961
+ * Clear a pending cancellation.
3962
+ *
3963
+ * Between cancelling and the period ending nothing has actually been lost,
3964
+ * so changing your mind should cost one call rather than a re-purchase.
3965
+ * After the period ends there is nothing to resume and they subscribe again
3966
+ * like anyone else.
3967
+ */
3968
+ async resumeSubscription(): Promise<CustomerSubscriptionCancelResponse> {
3969
+ return this.client.request({
3970
+ method: "POST",
3971
+ path: "customer-portal/subscription/cancel",
3972
+ body: { resume: true },
3973
+ responseSchema: customerSubscriptionCancelResponseSchema,
3974
+ headers: this.headers(),
3975
+ });
3976
+ }
3977
+
3978
+ /**
3979
+ * The content this subscription entitles them to — the answer to "what am
3980
+ * I actually getting for this?", which is the question asked right before
3981
+ * someone cancels.
3982
+ *
3983
+ * When `entitled` is false these are LOCKED previews, not an empty
3984
+ * entitlement: titles, excerpts and images, no bodies. Show them beside the
3985
+ * offer. Rendering an empty list would hide the pitch at exactly the moment
3986
+ * it is most relevant.
3987
+ */
3988
+ async getSubscriberContent(opts?: {
3989
+ limit?: number;
3990
+ }): Promise<CustomerSubscriberContentResponse> {
3991
+ const params = new URLSearchParams();
3992
+ if (typeof opts?.limit === "number")
3993
+ params.set("limit", String(opts.limit));
3994
+ const qs = params.toString();
3995
+ return this.client.request({
3996
+ path: `customer-portal/subscription/content${qs ? `?${qs}` : ""}`,
3997
+ responseSchema: customerSubscriberContentResponseSchema,
3998
+ headers: this.headers(),
3999
+ });
4000
+ }
4001
+
4002
+ /**
4003
+ * Push config and the browsers this customer has registered.
4004
+ *
4005
+ * Returns `vapidPublicKey` too, because a page cannot subscribe without it.
4006
+ */
4007
+ async getPushConfig(): Promise<CustomerPushConfig> {
4008
+ return this.client.request({
4009
+ path: "customer-portal/push",
4010
+ responseSchema: customerPushConfigSchema,
4011
+ headers: this.headers(),
4012
+ });
4013
+ }
4014
+
4015
+ /**
4016
+ * Register this browser.
4017
+ *
4018
+ * Pair with `subscribeToPush()` from `@businessdash/sdk/push-core`, which
4019
+ * handles the service worker, the permission prompt and the key encoding
4020
+ * and hands back exactly this payload.
4021
+ *
4022
+ * Upserted on the endpoint, so re-subscribing from the same browser
4023
+ * refreshes the row rather than creating a second one.
4024
+ */
4025
+ async subscribePush(input: {
4026
+ endpoint: string;
4027
+ p256dh: string;
4028
+ auth: string;
4029
+ userAgent?: string | null;
4030
+ label?: string | null;
4031
+ categories?: string[] | null;
4032
+ }): Promise<{ id: string; created: boolean }> {
4033
+ return this.client.request({
4034
+ method: "POST",
4035
+ path: "customer-portal/push",
4036
+ body: input,
4037
+ responseSchema: z.object({ id: z.string(), created: z.boolean() }),
4038
+ headers: this.headers(),
4039
+ });
4040
+ }
4041
+
4042
+ /**
4043
+ * Rename a device, or narrow what it receives.
4044
+ *
4045
+ * `categories: []` means "nothing for now" and keeps the subscription
4046
+ * warm — browsers make re-granting a revoked permission deliberately
4047
+ * awkward, so an "off" that discards the subscription is hard to undo.
4048
+ */
4049
+ async updatePushDevice(input: {
4050
+ id: string;
4051
+ label?: string | null;
4052
+ categories?: string[] | null;
4053
+ }): Promise<{ ok: boolean }> {
4054
+ return this.client.request({
4055
+ method: "PATCH",
4056
+ path: "customer-portal/push",
4057
+ body: input,
4058
+ responseSchema: z.object({ ok: z.boolean() }),
4059
+ headers: this.headers(),
4060
+ });
4061
+ }
4062
+
4063
+ /**
4064
+ * Forget a device. Takes an id or the endpoint.
4065
+ *
4066
+ * A browser tearing its own subscription down knows its endpoint, not our
4067
+ * row id — so pass what `unsubscribeFromPush()` returns.
4068
+ */
4069
+ async unsubscribePush(input: {
4070
+ id?: string;
4071
+ endpoint?: string;
4072
+ }): Promise<{ ok: boolean; deletedId: string | null }> {
4073
+ return this.client.request({
4074
+ method: "DELETE",
4075
+ path: "customer-portal/push",
4076
+ body: input,
4077
+ responseSchema: z.object({
4078
+ ok: z.boolean(),
4079
+ deletedId: z.string().nullable(),
4080
+ }),
4081
+ headers: this.headers(),
4082
+ });
4083
+ }
4084
+
4085
+ async listProductReviews(): Promise<CustomerProductReviewList> {
4086
+ return this.client.request({
4087
+ path: "customer-portal/product-reviews",
4088
+ responseSchema: customerProductReviewListSchema,
4089
+ headers: this.headers(),
4090
+ });
4091
+ }
4092
+
4093
+ /**
4094
+ * Review a product this customer BOUGHT.
4095
+ *
4096
+ * The portal requires a matching order — it is standing inside the
4097
+ * customer's purchase history, which is the one thing the public storefront
4098
+ * does not know. That makes these verified-buyer reviews, and it stops a
4099
+ * competitor one-starring a catalogue they never bought.
4100
+ *
4101
+ * Rejects with `not_purchased` (403) when no order matches, and
4102
+ * `already_reviewed` (409) on a second review of the same product.
4103
+ * Moderation follows the ORG's policy, so the result may be
4104
+ * `awaitingModeration`.
4105
+ */
4106
+ async submitProductReview(input: {
4107
+ productId: string;
4108
+ rating: number;
4109
+ content: string;
4110
+ }): Promise<CustomerProductReviewCreated> {
4111
+ return this.client.request({
4112
+ method: "POST",
4113
+ path: "customer-portal/product-reviews",
4114
+ body: input,
4115
+ responseSchema: customerProductReviewCreatedSchema,
4116
+ headers: this.headers(),
4117
+ });
4118
+ }
4119
+
3323
4120
  /**
3324
4121
  * List the digital downloads the customer is entitled to for a paid order
3325
4122
  * (P10d). Each item is a ready-to-use URL — a short-lived presigned link for
@@ -3430,6 +4227,22 @@ export class BiabDevCustomerPortalClient {
3430
4227
  * dispatch travel-time recompute layer ships, at which point the
3431
4228
  * `eta` field switches sources without breaking this shape.
3432
4229
  */
4230
+ /**
4231
+ * Where the job and its technicians are in the dispatch lifecycle.
4232
+ *
4233
+ * Named for ETA historically; it is the DISPATCH STATUS surface, which is
4234
+ * why `getDispatchStatus` below is the same call under the name people
4235
+ * look for.
4236
+ *
4237
+ * Read `dispatchStatus` (job-level) for "is anyone on the way" and
4238
+ * `assignments[].dispatchStatus` for per-technician detail. They differ on
4239
+ * purpose: the job is `completed` only once the LAST assignee finishes.
4240
+ *
4241
+ * Nothing about the dispatch CASCADE is exposed — who was offered the job,
4242
+ * who declined, how many people were asked before someone accepted. That
4243
+ * is staff-internal, and a customer learning they were the fourth choice
4244
+ * helps nobody.
4245
+ */
3433
4246
  async getJobEta(jobId: string): Promise<CustomerJobEtaResponse> {
3434
4247
  return this.client.request({
3435
4248
  path: `customer-portal/jobs/${encodeURIComponent(jobId)}/eta`,
@@ -3438,6 +4251,17 @@ export class BiabDevCustomerPortalClient {
3438
4251
  });
3439
4252
  }
3440
4253
 
4254
+ /**
4255
+ * Dispatch status for a job — the same call as `getJobEta`, under the name
4256
+ * developers actually search for.
4257
+ *
4258
+ * An alias rather than a second route: two endpoints returning the same
4259
+ * thing is how they drift.
4260
+ */
4261
+ async getDispatchStatus(jobId: string): Promise<CustomerJobEtaResponse> {
4262
+ return this.getJobEta(jobId);
4263
+ }
4264
+
3441
4265
  /**
3442
4266
  * Unified job activity feed: structured events (status changes,
3443
4267
  * dispatch lifecycle, media uploads, quote/contract/invoice
@@ -3576,3 +4400,276 @@ export class BiabDevAuthClient {
3576
4400
  });
3577
4401
  }
3578
4402
  }
4403
+
4404
+ /**
4405
+ * Booking, rescheduling, cancelling — and the conference call attached to it.
4406
+ *
4407
+ * ## Both sides of every flow
4408
+ *
4409
+ * An invitee acts with a signed TOKEN from their confirmation email; staff act
4410
+ * with the API key. Both reach the same service, so the same notifications fire
4411
+ * to the invitee AND every host either way, and the booking records which of
4412
+ * them made the change.
4413
+ *
4414
+ * ## Three tokens, not one
4415
+ *
4416
+ * A confirmed booking yields `manageToken`, `rescheduleToken` and `cancelToken`
4417
+ * separately. A cancel link in an email must not also be able to reschedule,
4418
+ * and neither should be able to read the whole booking. One token for all three
4419
+ * would make every emailed link as powerful as the strongest thing it enables.
4420
+ *
4421
+ * ## The conference link is re-minted on reschedule
4422
+ *
4423
+ * Moving a booking can issue a new meeting URL. Read `conferenceLink` back from
4424
+ * the reschedule result rather than reusing the one from confirmation —
4425
+ * otherwise the calendar invite you send points at a dead room.
4426
+ */
4427
+ /**
4428
+ * Customer-portal invitations.
4429
+ *
4430
+ * For an org running its own admin tooling rather than the dashboard.
4431
+ */
4432
+ export class BiabDevCustomerInvitesClient {
4433
+ constructor(private readonly client: BiabDevClient) {}
4434
+
4435
+ /**
4436
+ * Hand an invite link out again.
4437
+ *
4438
+ * **Rotates the token** — the previous link stops working. That is the
4439
+ * point rather than a side effect: if the reason for resending was "it went
4440
+ * to the wrong address", rotating IS the fix, and reusing the token would
4441
+ * leave the wrong recipient holding a working invitation. The cost is that
4442
+ * someone who finds the original email later gets a dead link, which is a
4443
+ * support message rather than an account.
4444
+ *
4445
+ * Rate limited to one send a minute per invitation, answering **429** with
4446
+ * a retry hint. Resend mails an address the caller chose, so an unbounded
4447
+ * one is a mail-bombing tool. Staff clicking the button in the dashboard
4448
+ * skip the wait; an API key does not, because an API key is the automated
4449
+ * caller the limit exists for.
4450
+ *
4451
+ * Refuses a **revoked** invitation (resending would quietly un-revoke it)
4452
+ * and a **fully-redeemed** one (it did its job; a new invitation should be
4453
+ * created as one, so the list keeps showing what happened).
4454
+ */
4455
+ async resend(
4456
+ inviteId: string,
4457
+ opts?: { expiresInDays?: number | null },
4458
+ ): Promise<CustomerInviteResendResponse> {
4459
+ return this.client.request({
4460
+ method: "POST",
4461
+ path: `customer-invites/${encodeURIComponent(inviteId)}/resend`,
4462
+ body:
4463
+ opts?.expiresInDays === undefined
4464
+ ? {}
4465
+ : { expiresInDays: opts.expiresInDays },
4466
+ responseSchema: customerInviteResendResponseSchema,
4467
+ });
4468
+ }
4469
+ }
4470
+
4471
+ export class BiabDevSchedulingClient {
4472
+ constructor(
4473
+ private readonly client: BiabDevClient,
4474
+ private readonly siteId: string,
4475
+ ) {}
4476
+
4477
+ private path(suffix: string): string {
4478
+ return `sites/${encodeURIComponent(this.siteId)}/scheduling/${suffix}`;
4479
+ }
4480
+
4481
+ /** What the org offers to book. */
4482
+ async listEventTypes(): Promise<SchedulingEventTypeList> {
4483
+ return this.client.request({
4484
+ path: this.path("event-types"),
4485
+ responseSchema: schedulingEventTypeListSchema,
4486
+ });
4487
+ }
4488
+
4489
+ /** One event type, plus the questions asked when booking it. */
4490
+ async getEventType(slug: string): Promise<SchedulingEventTypeDetail> {
4491
+ return this.client.request({
4492
+ path: this.path(`event-types/${encodeURIComponent(slug)}`),
4493
+ responseSchema: schedulingEventTypeDetailSchema,
4494
+ });
4495
+ }
4496
+
4497
+ /**
4498
+ * Free slots in a window.
4499
+ *
4500
+ * Ask for the window you are about to render, not the whole month: slot
4501
+ * computation walks every host's calendar and busy times, so a wide range
4502
+ * is a slow one.
4503
+ */
4504
+ async getAvailableSlots(
4505
+ slug: string,
4506
+ params: { from: Date | string; to: Date | string },
4507
+ ): Promise<SchedulingSlotsResponse> {
4508
+ const query = new URLSearchParams({
4509
+ from:
4510
+ typeof params.from === "string"
4511
+ ? params.from
4512
+ : params.from.toISOString(),
4513
+ to: typeof params.to === "string" ? params.to : params.to.toISOString(),
4514
+ });
4515
+ return this.client.request({
4516
+ path: this.path(
4517
+ `event-types/${encodeURIComponent(slug)}/slots?${query.toString()}`,
4518
+ ),
4519
+ responseSchema: schedulingSlotsResponseSchema,
4520
+ });
4521
+ }
4522
+
4523
+ /**
4524
+ * Book a slot.
4525
+ *
4526
+ * Returns `pending` rather than `confirmed` when the event type requires
4527
+ * approval — say "requested" in the UI for that case, because telling
4528
+ * someone their meeting is booked when staff still have to accept it is the
4529
+ * kind of thing they plan a day around.
4530
+ */
4531
+ async confirmBooking(input: {
4532
+ eventTypeSlug: string;
4533
+ startAt: Date | string;
4534
+ invitee: {
4535
+ email: string;
4536
+ name: string;
4537
+ phone?: string | null;
4538
+ timezone: string;
4539
+ };
4540
+ answers?: Array<{ questionId: string; value: string }>;
4541
+ notes?: string | null;
4542
+ schedulingLinkId?: string;
4543
+ holderKey?: string | null;
4544
+ }): Promise<SchedulingBookingResult> {
4545
+ const body = {
4546
+ ...input,
4547
+ startAt:
4548
+ typeof input.startAt === "string"
4549
+ ? input.startAt
4550
+ : input.startAt.toISOString(),
4551
+ };
4552
+ return this.client.request({
4553
+ method: "POST",
4554
+ path: this.path("bookings"),
4555
+ body,
4556
+ responseSchema: schedulingBookingResultSchema,
4557
+ });
4558
+ }
4559
+
4560
+ /**
4561
+ * Read a booking with one of its signed tokens.
4562
+ *
4563
+ * `type` must match the token you hold — a cancel token will not open the
4564
+ * manage view. That is the point of issuing three.
4565
+ */
4566
+ async getBooking(
4567
+ token: string,
4568
+ type: "manage" | "cancel" | "reschedule" = "manage",
4569
+ ): Promise<SchedulingBookingView> {
4570
+ return this.client.request({
4571
+ path: this.path(
4572
+ `bookings/by-token/${encodeURIComponent(token)}?type=${encodeURIComponent(type)}`,
4573
+ ),
4574
+ responseSchema: schedulingBookingViewSchema,
4575
+ });
4576
+ }
4577
+
4578
+ /**
4579
+ * Move a booking — the INVITEE side, with their reschedule token.
4580
+ *
4581
+ * Notifies the invitee and every host, and cancels the reminder jobs queued
4582
+ * against the old time so nobody gets "your meeting starts in 15 minutes"
4583
+ * for a slot that moved.
4584
+ */
4585
+ async rescheduleBooking(input: {
4586
+ token: string;
4587
+ newStartAt: Date | string;
4588
+ reason?: string | null;
4589
+ }): Promise<SchedulingRescheduleResult> {
4590
+ return this.client.request({
4591
+ method: "POST",
4592
+ path: this.path(`bookings/by-token/${encodeURIComponent(input.token)}`),
4593
+ body: {
4594
+ action: "reschedule",
4595
+ newStartAt:
4596
+ typeof input.newStartAt === "string"
4597
+ ? input.newStartAt
4598
+ : input.newStartAt.toISOString(),
4599
+ ...(input.reason ? { reason: input.reason } : {}),
4600
+ },
4601
+ responseSchema: schedulingRescheduleResultSchema,
4602
+ });
4603
+ }
4604
+
4605
+ /**
4606
+ * Move a booking as STAFF, with the API key rather than an invitee token.
4607
+ *
4608
+ * Same service as the token path, so the invitee AND every host are
4609
+ * notified either way, and the reminder jobs for the old time are
4610
+ * cancelled either way. `actorUserId` records WHO moved it, which is what
4611
+ * lets a customer's history say "they rescheduled" rather than leaving an
4612
+ * unexplained change.
4613
+ */
4614
+ async rescheduleBookingAsStaff(input: {
4615
+ bookingId: string;
4616
+ newStartAt: Date | string;
4617
+ reason?: string | null;
4618
+ actorUserId?: string | null;
4619
+ }): Promise<SchedulingRescheduleResult> {
4620
+ return this.client.request({
4621
+ method: "POST",
4622
+ path: this.path(`bookings/${encodeURIComponent(input.bookingId)}/manage`),
4623
+ body: {
4624
+ action: "reschedule",
4625
+ newStartAt:
4626
+ typeof input.newStartAt === "string"
4627
+ ? input.newStartAt
4628
+ : input.newStartAt.toISOString(),
4629
+ ...(input.reason ? { reason: input.reason } : {}),
4630
+ ...(input.actorUserId ? { actorUserId: input.actorUserId } : {}),
4631
+ },
4632
+ responseSchema: schedulingRescheduleResultSchema,
4633
+ });
4634
+ }
4635
+
4636
+ /** Cancel a booking as STAFF. Notifies the invitee and every host. */
4637
+ async cancelBookingAsStaff(input: {
4638
+ bookingId: string;
4639
+ reason?: string | null;
4640
+ actorUserId?: string | null;
4641
+ }): Promise<SchedulingCancelResult> {
4642
+ return this.client.request({
4643
+ method: "POST",
4644
+ path: this.path(`bookings/${encodeURIComponent(input.bookingId)}/manage`),
4645
+ body: {
4646
+ action: "cancel",
4647
+ ...(input.reason ? { reason: input.reason } : {}),
4648
+ ...(input.actorUserId ? { actorUserId: input.actorUserId } : {}),
4649
+ },
4650
+ responseSchema: schedulingCancelResultSchema,
4651
+ });
4652
+ }
4653
+
4654
+ /**
4655
+ * Cancel a booking — the INVITEE side, with their cancel token.
4656
+ *
4657
+ * `reason` is passed to the hosts. Worth collecting: "something came up" and
4658
+ * "I booked the wrong service" lead to different follow-ups, and the second
4659
+ * is recoverable revenue.
4660
+ */
4661
+ async cancelBooking(input: {
4662
+ token: string;
4663
+ reason?: string | null;
4664
+ }): Promise<SchedulingCancelResult> {
4665
+ return this.client.request({
4666
+ method: "POST",
4667
+ path: this.path(`bookings/by-token/${encodeURIComponent(input.token)}`),
4668
+ body: {
4669
+ action: "cancel",
4670
+ ...(input.reason ? { reason: input.reason } : {}),
4671
+ },
4672
+ responseSchema: schedulingCancelResultSchema,
4673
+ });
4674
+ }
4675
+ }