@planescrm/sdk 0.55.0 → 0.72.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.
package/dist/index.mjs CHANGED
@@ -166,6 +166,9 @@ var HttpClient = class {
166
166
  this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
167
167
  this.retryOnRateLimit = config.retryOnRateLimit !== false;
168
168
  this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
169
+ this.getAccessToken = config.getAccessToken;
170
+ this.onUnauthorized = config.onUnauthorized;
171
+ this.fetchImpl = config.fetch;
169
172
  if (config.clientId && config.clientSecret) {
170
173
  this.oauth = {
171
174
  clientId: config.clientId,
@@ -182,53 +185,68 @@ var HttpClient = class {
182
185
  let lastError;
183
186
  let didRefreshOn401 = false;
184
187
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
188
+ const authHeaders = await this.resolveAuthHeaders();
185
189
  const headers = {
186
190
  "Content-Type": "application/json",
187
- ...await this.resolveAuthHeaders(),
191
+ ...authHeaders,
188
192
  ...options.headers
189
193
  };
194
+ if (options.idempotencyKey) headers["Idempotency-Key"] = options.idempotencyKey;
195
+ const timer = timeoutSignal(this.timeout);
190
196
  const fetchOptions = {
191
197
  method: options.method,
192
198
  headers,
193
- signal: AbortSignal.timeout(this.timeout)
199
+ signal: timer.signal
194
200
  };
195
201
  if (bodyStr !== void 0) {
196
202
  fetchOptions.body = bodyStr;
197
203
  }
198
- const response = await fetch(url, fetchOptions);
199
- if (response.ok) {
200
- if (response.status === 204) {
201
- return void 0;
204
+ try {
205
+ const response = await (this.fetchImpl ?? fetch)(url, fetchOptions);
206
+ if (response.ok) {
207
+ if (response.status === 204) {
208
+ return void 0;
209
+ }
210
+ if (options.responseType === "arrayBuffer") {
211
+ return await response.arrayBuffer();
212
+ }
213
+ return await response.json();
202
214
  }
203
- if (options.responseType === "arrayBuffer") {
204
- return await response.arrayBuffer();
215
+ if (response.status === 429 && this.retryOnRateLimit && attempt < this.maxRetries) {
216
+ const retryAfter = this.parseRetryAfter(response);
217
+ await this.sleep(retryAfter * 1e3);
218
+ continue;
205
219
  }
206
- return await response.json();
207
- }
208
- if (response.status === 429 && this.retryOnRateLimit && attempt < this.maxRetries) {
209
- const retryAfter = this.parseRetryAfter(response);
210
- await this.sleep(retryAfter * 1e3);
211
- continue;
212
- }
213
- if (response.status === 401 && this.oauth && !didRefreshOn401) {
214
- didRefreshOn401 = true;
215
- await this.refreshTokens();
216
- attempt--;
217
- continue;
218
- }
219
- lastError = await this.mapError(response);
220
- if (response.status !== 429 || !this.retryOnRateLimit) {
221
- throw lastError;
220
+ if (response.status === 401 && this.oauth && !didRefreshOn401) {
221
+ didRefreshOn401 = true;
222
+ await this.refreshTokens();
223
+ attempt--;
224
+ continue;
225
+ }
226
+ if (response.status === 401 && this.onUnauthorized && !didRefreshOn401) {
227
+ didRefreshOn401 = true;
228
+ if (await this.recoverSession(authHeaders["Authorization"])) {
229
+ attempt--;
230
+ continue;
231
+ }
232
+ }
233
+ lastError = await this.mapError(response);
234
+ if (response.status !== 429 || !this.retryOnRateLimit) {
235
+ throw lastError;
236
+ }
237
+ } finally {
238
+ timer.clear();
222
239
  }
223
240
  }
224
241
  throw lastError ?? new RateLimitError();
225
242
  }
226
243
  getAuthHeaders() {
244
+ if (this.getAccessToken) return {};
227
245
  if (this.accessToken) {
228
246
  return { Authorization: `Bearer ${this.accessToken}` };
229
247
  }
230
248
  if (this.apiKey) {
231
- return { "X-API-Key": this.apiKey };
249
+ return { Authorization: `Bearer ${this.apiKey}` };
232
250
  }
233
251
  return {};
234
252
  }
@@ -239,6 +257,10 @@ var HttpClient = class {
239
257
  * the token is within the skew window of expiry.
240
258
  */
241
259
  async resolveAuthHeaders() {
260
+ if (this.getAccessToken) {
261
+ const token = await withTimeout(this.getAccessToken(), this.timeout, "getAccessToken");
262
+ return token ? { Authorization: `Bearer ${token}` } : {};
263
+ }
242
264
  if (!this.oauth) {
243
265
  return this.getAuthHeaders();
244
266
  }
@@ -302,6 +324,41 @@ var HttpClient = class {
302
324
  this.refreshInFlight = void 0;
303
325
  }
304
326
  }
327
+ /**
328
+ * Ask the app to refresh its session after a 401. Resolves `true` when the
329
+ * request should be retried.
330
+ * - Requests that got a 401 while a refresh is running share that one call.
331
+ * - A request whose 401 arrives after a refresh already finished (it was
332
+ * sent with the old token) doesn't start a second refresh: if the current
333
+ * token differs from the one it sent, it just retries with the new one.
334
+ * - A refresh that throws, or doesn't settle within the request timeout,
335
+ * counts as "still signed out": the original 401 surfaces.
336
+ */
337
+ async recoverSession(sentAuthorization) {
338
+ if (!this.onUnauthorized || !this.getAccessToken) return false;
339
+ if (!this.unauthorizedInFlight) {
340
+ try {
341
+ const current = await withTimeout(this.getAccessToken(), this.timeout, "getAccessToken");
342
+ if (current && `Bearer ${current}` !== sentAuthorization) return true;
343
+ } catch {
344
+ return false;
345
+ }
346
+ }
347
+ if (!this.unauthorizedInFlight) {
348
+ const call = this.onUnauthorized;
349
+ const inFlight = (async () => {
350
+ try {
351
+ return await withTimeout(call(), this.timeout, "onUnauthorized");
352
+ } catch {
353
+ return false;
354
+ }
355
+ })().finally(() => {
356
+ if (this.unauthorizedInFlight === inFlight) this.unauthorizedInFlight = void 0;
357
+ });
358
+ this.unauthorizedInFlight = inFlight;
359
+ }
360
+ return this.unauthorizedInFlight;
361
+ }
305
362
  buildUrl(path, params) {
306
363
  const url = new URL(`${this.baseUrl}${path}`);
307
364
  if (params) {
@@ -329,7 +386,8 @@ var HttpClient = class {
329
386
  body = await response.json();
330
387
  } catch {
331
388
  }
332
- const message = body["message"] ?? response.statusText ?? "Request failed";
389
+ const envelope = body["error"];
390
+ const message = (typeof body["message"] === "string" ? body["message"] : void 0) ?? (typeof envelope?.message === "string" ? envelope.message : void 0) ?? response.statusText ?? "Request failed";
333
391
  switch (response.status) {
334
392
  case 400: {
335
393
  const fieldErrors = body["errors"] ?? [];
@@ -356,6 +414,29 @@ var HttpClient = class {
356
414
  return new Promise((resolve) => setTimeout(resolve, ms));
357
415
  }
358
416
  };
417
+ function timeoutSignal(ms) {
418
+ if (typeof AbortSignal.timeout === "function") {
419
+ return { signal: AbortSignal.timeout(ms), clear: () => void 0 };
420
+ }
421
+ const controller = new AbortController();
422
+ const timer = setTimeout(() => controller.abort(), ms);
423
+ return { signal: controller.signal, clear: () => clearTimeout(timer) };
424
+ }
425
+ function withTimeout(promise, ms, what) {
426
+ return new Promise((resolve, reject) => {
427
+ const timer = setTimeout(() => reject(new Error(`${what} did not settle within ${ms}ms`)), ms);
428
+ Promise.resolve(promise).then(
429
+ (value) => {
430
+ clearTimeout(timer);
431
+ resolve(value);
432
+ },
433
+ (err) => {
434
+ clearTimeout(timer);
435
+ reject(err);
436
+ }
437
+ );
438
+ });
439
+ }
359
440
 
360
441
  // src/resources/base-resource.ts
361
442
  var ApiResource = class {
@@ -385,13 +466,16 @@ var BaseResource = class extends ApiResource {
385
466
  });
386
467
  }
387
468
  /**
388
- * Create a new entity.
469
+ * Create a new entity. Pass `idempotencyKey` (a UUID you keep for retries) to
470
+ * make a retry safe on routes that honor it (customers, opportunities, contacts,
471
+ * tasks): a repeat returns the first response and creates nothing.
389
472
  */
390
- async create(data) {
473
+ async create(data, options) {
391
474
  return this.http.request({
392
475
  method: "POST",
393
476
  path: this.basePath,
394
- body: data
477
+ body: data,
478
+ ...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}
395
479
  });
396
480
  }
397
481
  /**
@@ -421,6 +505,12 @@ function toQueryParams(params) {
421
505
  if (!params) return out;
422
506
  for (const [key, value] of Object.entries(params)) {
423
507
  if (value === void 0 || value === null) continue;
508
+ if (key === "customFields" && typeof value === "object" && !Array.isArray(value)) {
509
+ for (const [field, v] of Object.entries(value)) {
510
+ if (v !== void 0 && v !== null) out[`cf.${field}`] = String(v);
511
+ }
512
+ continue;
513
+ }
424
514
  if (Array.isArray(value)) {
425
515
  if (value.length === 0) continue;
426
516
  out[key] = value.map(String).join(",");
@@ -447,6 +537,16 @@ var OpportunityResource = class extends BaseResource {
447
537
  params: toQueryParams(params)
448
538
  });
449
539
  }
540
+ /**
541
+ * GET /opportunities/trade-options — the active trades a client can request
542
+ * (`requestedTradeIds` on create/update), sorted by name.
543
+ */
544
+ async tradeOptions() {
545
+ return this.http.request({
546
+ method: "GET",
547
+ path: `${this.basePath}/trade-options`
548
+ });
549
+ }
450
550
  /** PUT /opportunities/{id} — partial update; stage moves go through workflow transitions. */
451
551
  async update(id, data) {
452
552
  return this.http.request({
@@ -455,6 +555,21 @@ var OpportunityResource = class extends BaseResource {
455
555
  body: data
456
556
  });
457
557
  }
558
+ /** GET /opportunities/:id/transitions — the pipeline moves available from the current stage, for the caller's role. */
559
+ async listTransitions(id) {
560
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}/transitions` });
561
+ }
562
+ /**
563
+ * POST /opportunities/:id/transition — take one pipeline move, as the stage button does: required
564
+ * fields are checked, the move is logged, and the stage's automations run (they can send email).
565
+ */
566
+ async transition(id, data) {
567
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/transition`, body: data });
568
+ }
569
+ /** GET /opportunities/:id/workflow — the pipeline instance with its transition history (404 if none). */
570
+ async getWorkflow(id) {
571
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}/workflow` });
572
+ }
458
573
  };
459
574
 
460
575
  // src/resources/customers.ts
@@ -501,6 +616,19 @@ var CustomerResource = class extends BaseResource {
501
616
  path: `/customers/${winnerId}/merge/${loserId}`
502
617
  });
503
618
  }
619
+ /**
620
+ * POST /customers/{id}/convert-to-company — turn a person customer into a
621
+ * company. The person becomes the company's primary contact (their email and
622
+ * phone, with do-not-contact and marketing opt-outs carried over). A customer
623
+ * that's already a company is a 409 `ALREADY_COMPANY`.
624
+ */
625
+ async convertToCompany(id, data = {}) {
626
+ return this.http.request({
627
+ method: "POST",
628
+ path: `${this.basePath}/${id}/convert-to-company`,
629
+ body: data
630
+ });
631
+ }
504
632
  };
505
633
 
506
634
  // src/resources/properties.ts
@@ -539,6 +667,17 @@ var PropertyResource = class extends ApiResource {
539
667
  body: data
540
668
  });
541
669
  }
670
+ /** GET /customers/:customerId/properties/:id — one property, checked against its customer (404 if it isn't theirs). */
671
+ async getForCustomer(customerId, id) {
672
+ return this.http.request({
673
+ method: "GET",
674
+ path: `/customers/${customerId}/properties/${id}`
675
+ });
676
+ }
677
+ /** POST /properties/:id/geocode — queue a fresh geocode of the property's address. */
678
+ async geocode(id) {
679
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/geocode` });
680
+ }
542
681
  /** DELETE /customers/{customerId}/properties/{id} — soft delete (204). */
543
682
  async delete(customerId, id) {
544
683
  await this.http.request({
@@ -554,7 +693,11 @@ var CalendarEventResource = class extends BaseResource {
554
693
  super(http2, "/events");
555
694
  }
556
695
  async list(params) {
557
- return super.list(params);
696
+ return this.http.request({
697
+ method: "GET",
698
+ path: this.basePath,
699
+ params: params ?? {}
700
+ });
558
701
  }
559
702
  /**
560
703
  * PATCH /events/:id. For an occurrence of a recurring event, `editMode`
@@ -573,7 +716,8 @@ var CalendarEventResource = class extends BaseResource {
573
716
  * POST /events/:id/complete — marks the event completed and runs its "on
574
717
  * complete" transition, best-effort. The event is completed even when the
575
718
  * transition can't run; `workflow_trigger` says whether it fired, was
576
- * skipped (none set) or failed (with a reason).
719
+ * skipped (none set, or `handled_by_pipeline_rule`: the pipeline's rule
720
+ * moves the record instead) or failed (with a reason).
577
721
  */
578
722
  async complete(id, body = {}) {
579
723
  return this.http.request({
@@ -582,6 +726,34 @@ var CalendarEventResource = class extends BaseResource {
582
726
  body
583
727
  });
584
728
  }
729
+ /**
730
+ * POST /events/:id/cancel. For an occurrence of a recurring event, `editMode`
731
+ * picks which occurrences are cancelled (`this` when omitted). 409 if already
732
+ * cancelled; 422 for a completed event.
733
+ */
734
+ async cancel(id, options = {}) {
735
+ return this.http.request({
736
+ method: "POST",
737
+ path: `${this.basePath}/${id}/cancel`,
738
+ params: { recurrence_edit_mode: options.editMode }
739
+ });
740
+ }
741
+ /** POST /events/:id/participants — add a user, contact, crew or agent to the event. No invite is sent. */
742
+ async addParticipant(id, data) {
743
+ return this.http.request({
744
+ method: "POST",
745
+ path: `${this.basePath}/${id}/participants`,
746
+ body: data
747
+ });
748
+ }
749
+ /** PATCH /events/:id/participants/:participantId — set a participant's response. */
750
+ async updateParticipantStatus(id, participantId, status) {
751
+ return this.http.request({
752
+ method: "PATCH",
753
+ path: `${this.basePath}/${id}/participants/${participantId}`,
754
+ body: { status }
755
+ });
756
+ }
585
757
  };
586
758
 
587
759
  // src/resources/contracts.ts
@@ -589,6 +761,73 @@ var ContractResource = class extends BaseResource {
589
761
  constructor(http2) {
590
762
  super(http2, "/contracts");
591
763
  }
764
+ /** POST /contracts/for-subject — a bare agreement for a customer or a service schedule (no opportunity, no line groups). */
765
+ async createForSubject(data) {
766
+ return this.http.request({ method: "POST", path: `${this.basePath}/for-subject`, body: data });
767
+ }
768
+ /** POST /contracts/flat — a contract on an opportunity with a manually entered total and a contract template, no estimates. */
769
+ async createFlat(data) {
770
+ return this.http.request({ method: "POST", path: `${this.basePath}/flat`, body: data });
771
+ }
772
+ /** POST /contracts/{id}/change-orders — a change order on a signed or fulfilled contract (409 otherwise). */
773
+ async createChangeOrder(id, data) {
774
+ return this.http.request({
775
+ method: "POST",
776
+ path: `${this.basePath}/${id}/change-orders`,
777
+ body: data
778
+ });
779
+ }
780
+ /**
781
+ * GET /contracts/{id}/change-orders — the change orders written against a
782
+ * contract, oldest first, each with its line groups. A change order has none
783
+ * of its own, so asking for one returns an empty list.
784
+ */
785
+ async listChangeOrders(id) {
786
+ return this.http.request({
787
+ method: "GET",
788
+ path: `${this.basePath}/${id}/change-orders`
789
+ });
790
+ }
791
+ /** POST /contracts/{id}/submit — submit for approval; auto-approved when under the tenant's threshold. */
792
+ async submit(id) {
793
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/submit` });
794
+ }
795
+ /** POST /contracts/{id}/approve — approve a contract pending approval (needs the approve grant). */
796
+ async approve(id) {
797
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/approve` });
798
+ }
799
+ /** POST /contracts/{id}/cancel — cancel a contract in any state before it's signed. */
800
+ async cancel(id) {
801
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/cancel` });
802
+ }
803
+ /** POST /contracts/{id}/reopen — return a kicked-back contract to draft for review and re-send. */
804
+ async reopen(id) {
805
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/reopen` });
806
+ }
807
+ /**
808
+ * POST /contracts/{id}/send — **emails the signer a "Review & Sign" link.**
809
+ * Snapshots the clauses and creates the signing token. An empty body
810
+ * addresses the customer; `signerContactId` sends it to that contact instead.
811
+ * The first send of a contract uses one unit of the tenant's e-signature
812
+ * allowance; a re-send doesn't. Not safe to retry blindly after a timeout: a
813
+ * repeat emails again.
814
+ */
815
+ async send(id, data = {}) {
816
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/send`, body: data });
817
+ }
818
+ /**
819
+ * GET /contracts/{id}/pdf — the contract PDF bytes. `type: 'signed'` returns
820
+ * the stored signed copy (404 until it's signed); otherwise a preview renders.
821
+ */
822
+ async downloadPdf(id, options = {}) {
823
+ return this.http.request({
824
+ method: "GET",
825
+ path: `${this.basePath}/${id}/pdf`,
826
+ params: options.type ? { type: options.type } : {},
827
+ headers: { Accept: "application/pdf" },
828
+ responseType: "arrayBuffer"
829
+ });
830
+ }
592
831
  };
593
832
 
594
833
  // src/resources/jobs.ts
@@ -658,6 +897,28 @@ var JobResource = class extends ApiResource {
658
897
  path: "/jobs/trade-coverage"
659
898
  });
660
899
  }
900
+ /** GET /jobs/{id}/workflow — the job's pipeline instance with its transition history (404 if none). */
901
+ async getWorkflow(id) {
902
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}/workflow` });
903
+ }
904
+ /**
905
+ * POST /jobs/{id}/transition — take one pipeline move, as the stage button
906
+ * does: required fields are checked, the move is logged, and **the
907
+ * transition's actions run**. A tenant can attach actions that create or send
908
+ * an invoice, charge a saved card, send a contract, or send email, SMS or a
909
+ * webhook. Not safe to retry blindly after a timeout: check `getWorkflow()`
910
+ * first, because a repeat runs the actions again.
911
+ */
912
+ async transition(id, data) {
913
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/transition`, body: data });
914
+ }
915
+ /** GET /jobs/{id}/transitions — the pipeline moves available from the job's current stage, for the caller's role. */
916
+ async listTransitions(id) {
917
+ return this.http.request({
918
+ method: "GET",
919
+ path: `${this.basePath}/${id}/transitions`
920
+ });
921
+ }
661
922
  };
662
923
 
663
924
  // src/resources/estimates.ts
@@ -715,6 +976,74 @@ var EstimateResource = class extends ApiResource {
715
976
  path: `/jobs/${jobId}/estimates/${id}`
716
977
  });
717
978
  }
979
+ /** GET /estimates/{id}/workflow — the estimate's pipeline instance with its transition history (404 if none). */
980
+ async getWorkflow(id) {
981
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}/workflow` });
982
+ }
983
+ /**
984
+ * POST /estimates/{id}/transition — take one pipeline move. **The
985
+ * transition's actions run**, as for `jobs.transition()`: a tenant can attach
986
+ * actions that bill, charge, send a contract, or send email, SMS or a webhook.
987
+ * Not safe to retry blindly after a timeout: check `getWorkflow()` first.
988
+ */
989
+ async transition(id, data) {
990
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/transition`, body: data });
991
+ }
992
+ /**
993
+ * PUT /jobs/{jobId}/estimates/{id}/status — move the estimate's status (see
994
+ * `UpdateEstimateStatusDto` for the allowed moves). The route itself sends
995
+ * nothing and creates no contract or invoice; a tenant's webhooks or
996
+ * automations on the status change can.
997
+ */
998
+ async updateStatus(jobId, id, data) {
999
+ return this.http.request({
1000
+ method: "PUT",
1001
+ path: `/jobs/${jobId}/estimates/${id}/status`,
1002
+ body: data
1003
+ });
1004
+ }
1005
+ /** GET /estimates/{id}/transitions — the pipeline moves available from the estimate's current stage, for the caller's role. */
1006
+ async listTransitions(id) {
1007
+ return this.http.request({
1008
+ method: "GET",
1009
+ path: `${this.basePath}/${id}/transitions`
1010
+ });
1011
+ }
1012
+ /**
1013
+ * POST /jobs/{jobId}/estimates/{estimateId}/line-items — add a line item. With
1014
+ * `materialCatalogId` or `tradeLaborItemId`, the catalog fills in description,
1015
+ * unit and price. The estimate's subtotal and total are recalculated.
1016
+ */
1017
+ async createLineItem(jobId, estimateId, data) {
1018
+ return this.http.request({
1019
+ method: "POST",
1020
+ path: `/jobs/${jobId}/estimates/${estimateId}/line-items`,
1021
+ body: data
1022
+ });
1023
+ }
1024
+ /** PUT /jobs/{jobId}/estimates/{estimateId}/line-items/{id} — partial update; totals are recalculated. */
1025
+ async updateLineItem(jobId, estimateId, id, data) {
1026
+ return this.http.request({
1027
+ method: "PUT",
1028
+ path: `/jobs/${jobId}/estimates/${estimateId}/line-items/${id}`,
1029
+ body: data
1030
+ });
1031
+ }
1032
+ /** DELETE /jobs/{jobId}/estimates/{estimateId}/line-items/{id} — totals are recalculated. */
1033
+ async deleteLineItem(jobId, estimateId, id) {
1034
+ await this.http.request({
1035
+ method: "DELETE",
1036
+ path: `/jobs/${jobId}/estimates/${estimateId}/line-items/${id}`
1037
+ });
1038
+ }
1039
+ /** PUT /jobs/{jobId}/estimates/{estimateId}/line-items/reorder — set each item's `sortOrder` (and optionally `section`) in one call. */
1040
+ async reorderLineItems(jobId, estimateId, data) {
1041
+ return this.http.request({
1042
+ method: "PUT",
1043
+ path: `/jobs/${jobId}/estimates/${estimateId}/line-items/reorder`,
1044
+ body: data
1045
+ });
1046
+ }
718
1047
  };
719
1048
 
720
1049
  // src/resources/invoices.ts
@@ -777,6 +1106,24 @@ var InvoiceResource = class extends BaseResource {
777
1106
  params
778
1107
  });
779
1108
  }
1109
+ /** GET /admin/invoices/{invoiceId}/payments — every payment recorded against the invoice (Read Payment). */
1110
+ async listPayments(invoiceId) {
1111
+ return this.http.request({
1112
+ method: "GET",
1113
+ path: `${this.basePath}/${invoiceId}/payments`
1114
+ });
1115
+ }
1116
+ /**
1117
+ * GET /admin/payments/recording-settings — the tenant's resolved
1118
+ * payment-recording settings (whether manual recording is on, and which
1119
+ * methods). Read Invoice.
1120
+ */
1121
+ async getPaymentRecordingSettings() {
1122
+ return this.http.request({
1123
+ method: "GET",
1124
+ path: "/admin/payments/recording-settings"
1125
+ });
1126
+ }
780
1127
  /** GET /admin/invoices/overdue/summary — `{ count, total }` of every overdue invoice. Read Invoice. */
781
1128
  async overdueSummary() {
782
1129
  return this.http.request({
@@ -806,33 +1153,72 @@ var InventoryResource = class extends BaseResource {
806
1153
  body: data
807
1154
  });
808
1155
  }
1156
+ // ---- Material used on a job (E64-S9). The routes live under /jobs/{jobId}. ----
1157
+ /**
1158
+ * POST /jobs/{jobId}/consumption — record stock drawn from a location for a
1159
+ * job. Each line lowers that location's on-hand quantity.
1160
+ */
1161
+ async recordJobConsumption(jobId, data) {
1162
+ return this.http.request({
1163
+ method: "POST",
1164
+ path: `/jobs/${jobId}/consumption`,
1165
+ body: data
1166
+ });
1167
+ }
1168
+ /**
1169
+ * GET /jobs/{jobId}/consumption — one page of the job's consume and return
1170
+ * movements (newest first by default), the job's totals across every page,
1171
+ * and page metadata. The rows are under `items`, not `data`.
1172
+ */
1173
+ async listJobConsumption(jobId, params) {
1174
+ return this.http.request({
1175
+ method: "GET",
1176
+ path: `/jobs/${jobId}/consumption`,
1177
+ params: toQueryParams(params)
1178
+ });
1179
+ }
1180
+ /** POST /jobs/{jobId}/return — put unused stock from a job back into a location. */
1181
+ async returnFromJob(jobId, data) {
1182
+ return this.http.request({
1183
+ method: "POST",
1184
+ path: `/jobs/${jobId}/return`,
1185
+ body: data
1186
+ });
1187
+ }
1188
+ /** GET /jobs/{jobId}/material-variance — estimated vs actual material for the job. */
1189
+ async getJobMaterialVariance(jobId) {
1190
+ return this.http.request({
1191
+ method: "GET",
1192
+ path: `/jobs/${jobId}/material-variance`
1193
+ });
1194
+ }
809
1195
  };
810
1196
 
811
1197
  // src/resources/contacts.ts
812
1198
  var ContactChannelResource = class {
813
1199
  constructor(http2, contactId) {
814
1200
  this.http = http2;
815
- this.basePath = `/contacts/${contactId}/channels`;
1201
+ this.contactId = contactId;
816
1202
  }
817
1203
  /** Returns `{ channel, duplicateOf }` — `duplicateOf` is the dedup signal. */
818
1204
  async create(data) {
819
1205
  return this.http.request({
820
1206
  method: "POST",
821
- path: this.basePath,
1207
+ path: `/contacts/${this.contactId}/channels`,
822
1208
  body: data
823
1209
  });
824
1210
  }
825
1211
  async update(channelId, data) {
826
1212
  return this.http.request({
827
1213
  method: "PATCH",
828
- path: `${this.basePath}/${channelId}`,
1214
+ path: `/contacts/${this.contactId}/channels/${channelId}`,
829
1215
  body: data
830
1216
  });
831
1217
  }
832
1218
  async delete(channelId) {
833
1219
  await this.http.request({
834
1220
  method: "DELETE",
835
- path: `${this.basePath}/${channelId}`
1221
+ path: `/contacts/${this.contactId}/channels/${channelId}`
836
1222
  });
837
1223
  }
838
1224
  };
@@ -860,6 +1246,46 @@ var ContactResource = class extends BaseResource {
860
1246
  path: `/contacts/${winnerId}/merge/${loserId}`
861
1247
  });
862
1248
  }
1249
+ /** GET /contacts/roles — the distinct roles in use on active links, for a role picker. */
1250
+ async listRoles() {
1251
+ return this.http.request({ method: "GET", path: "/contacts/roles" });
1252
+ }
1253
+ /** GET /contacts/:id/links — every link, active and ended (history). `get()` returns active links only. */
1254
+ async listLinks(contactId) {
1255
+ return this.http.request({ method: "GET", path: `/contacts/${contactId}/links` });
1256
+ }
1257
+ /** POST /contacts/:id/links — link the contact to a customer or property. */
1258
+ async addLink(contactId, data) {
1259
+ return this.http.request({ method: "POST", path: `/contacts/${contactId}/links`, body: data });
1260
+ }
1261
+ /** PATCH /contacts/:id/links/:linkId — change role/primary, or end the link (`endedAt`). */
1262
+ async updateLink(contactId, linkId, data) {
1263
+ return this.http.request({
1264
+ method: "PATCH",
1265
+ path: `/contacts/${contactId}/links/${linkId}`,
1266
+ body: data
1267
+ });
1268
+ }
1269
+ /** POST /contacts/:id/workflow/start — opt the contact into the relationship pipeline (409 if started). */
1270
+ async startWorkflow(contactId) {
1271
+ return this.http.request({ method: "POST", path: `/contacts/${contactId}/workflow/start` });
1272
+ }
1273
+ /** GET /contacts/:id/workflow — current stage and the transitions available from it. */
1274
+ async getWorkflow(contactId) {
1275
+ return this.http.request({ method: "GET", path: `/contacts/${contactId}/workflow` });
1276
+ }
1277
+ /** POST /contacts/:id/workflow/transitions/:transitionId — 422 for an invalid move or unmet required fields. */
1278
+ async transition(contactId, transitionId) {
1279
+ return this.http.request({
1280
+ method: "POST",
1281
+ path: `/contacts/${contactId}/workflow/transitions/${transitionId}`,
1282
+ body: {}
1283
+ });
1284
+ }
1285
+ /** GET /contacts/:id/workflow/history */
1286
+ async getWorkflowHistory(contactId) {
1287
+ return this.http.request({ method: "GET", path: `/contacts/${contactId}/workflow/history` });
1288
+ }
863
1289
  channels(contactId) {
864
1290
  return new ContactChannelResource(this.http, contactId);
865
1291
  }
@@ -1106,6 +1532,14 @@ var TagResource = class extends ApiResource {
1106
1532
  async delete(id) {
1107
1533
  await this.http.request({ method: "DELETE", path: `${this.basePath}/${id}` });
1108
1534
  }
1535
+ /**
1536
+ * POST /tags/:winnerId/merge/:loserId — fold a duplicate tag into another: records
1537
+ * move to the winner (`retagged`), duplicates are dropped (`dropped`), and the
1538
+ * loser is deleted. Irreversible.
1539
+ */
1540
+ async merge(winnerId, loserId) {
1541
+ return this.http.request({ method: "POST", path: `${this.basePath}/${winnerId}/merge/${loserId}` });
1542
+ }
1109
1543
  /** `TagsService.setEntityTags()` returns the final tag set wrapped in `{ tags }`. */
1110
1544
  async setEntityTags(entityType, entityId, data) {
1111
1545
  return this.http.request({
@@ -1114,6 +1548,31 @@ var TagResource = class extends ApiResource {
1114
1548
  body: data
1115
1549
  });
1116
1550
  }
1551
+ /**
1552
+ * POST /entities/:type/:id/tags — add ONE tag, keeping the record's other tags.
1553
+ * Safe to call concurrently with other tag changes on the same record (unlike
1554
+ * read-modify-write through `setEntityTags()`). Adding a tag the record already
1555
+ * carries is a no-op. Returns the record's full tag set.
1556
+ */
1557
+ async addEntityTag(entityType, entityId, ref) {
1558
+ const body = ref.tagId !== void 0 ? { tag_id: ref.tagId } : { tag_name: ref.tagName };
1559
+ return this.http.request({
1560
+ method: "POST",
1561
+ path: `/entities/${entityType}/${entityId}/tags`,
1562
+ body
1563
+ });
1564
+ }
1565
+ /**
1566
+ * DELETE /entities/:type/:id/tags/:tagId — remove ONE tag, keeping the rest.
1567
+ * Removing a tag the record doesn't carry is a no-op. Returns the record's
1568
+ * full tag set.
1569
+ */
1570
+ async removeEntityTag(entityType, entityId, tagId) {
1571
+ return this.http.request({
1572
+ method: "DELETE",
1573
+ path: `/entities/${entityType}/${entityId}/tags/${tagId}`
1574
+ });
1575
+ }
1117
1576
  async getEntityTags(entityType, entityId) {
1118
1577
  return this.http.request({
1119
1578
  method: "GET",
@@ -1129,6 +1588,111 @@ var TagResource = class extends ApiResource {
1129
1588
  }
1130
1589
  };
1131
1590
 
1591
+ // src/resources/lists.ts
1592
+ var ListsResource = class extends ApiResource {
1593
+ constructor(http2) {
1594
+ super(http2, "/lists");
1595
+ }
1596
+ /** GET /lists — every list, newest first, each with `member_count`. A bare array. */
1597
+ async list() {
1598
+ return this.http.request({ method: "GET", path: this.basePath });
1599
+ }
1600
+ /**
1601
+ * GET /lists/filter-fields — the standard fields, their types and the operators
1602
+ * each accepts for a smart list of this entity type, and whether custom fields
1603
+ * are accepted (E66-S3). A save that breaks these rules is a 400.
1604
+ */
1605
+ async filterFields(entityType) {
1606
+ return this.http.request({
1607
+ method: "GET",
1608
+ path: `${this.basePath}/filter-fields`,
1609
+ params: { entity_type: entityType }
1610
+ });
1611
+ }
1612
+ /** GET /lists/:id — one list with an on-demand `member_count`. */
1613
+ async get(id) {
1614
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}` });
1615
+ }
1616
+ /** POST /lists */
1617
+ async create(data) {
1618
+ return this.http.request({ method: "POST", path: this.basePath, body: data });
1619
+ }
1620
+ /** PATCH /lists/:id — rename, or replace a smart list's definition. */
1621
+ async update(id, data) {
1622
+ return this.http.request({ method: "PATCH", path: `${this.basePath}/${id}`, body: data });
1623
+ }
1624
+ /** DELETE /lists/:id — soft delete; the records on the list are untouched. */
1625
+ async delete(id) {
1626
+ await this.http.request({ method: "DELETE", path: `${this.basePath}/${id}` });
1627
+ }
1628
+ /** POST /lists/:id/members — static lists only; idempotent on duplicates. */
1629
+ async addMembers(id, members) {
1630
+ return this.http.request({
1631
+ method: "POST",
1632
+ path: `${this.basePath}/${id}/members`,
1633
+ body: { members }
1634
+ });
1635
+ }
1636
+ /** DELETE /lists/:id/members — static lists only. */
1637
+ async removeMembers(id, members) {
1638
+ return this.http.request({
1639
+ method: "DELETE",
1640
+ path: `${this.basePath}/${id}/members`,
1641
+ body: { members }
1642
+ });
1643
+ }
1644
+ /** GET /lists/:id/recipients — people (audience, contact and customer lists), with consent. */
1645
+ async getRecipients(id, params) {
1646
+ return this.http.request({
1647
+ method: "GET",
1648
+ path: `${this.basePath}/${id}/recipients`,
1649
+ params
1650
+ });
1651
+ }
1652
+ /** POST /lists/:id/export — queues a CSV export; a notification links the file when it's ready. */
1653
+ async export(id, options) {
1654
+ return this.http.request({
1655
+ method: "POST",
1656
+ path: `${this.basePath}/${id}/export`,
1657
+ body: options ?? {}
1658
+ });
1659
+ }
1660
+ };
1661
+
1662
+ // src/resources/lead-sources.ts
1663
+ var LeadSourcesResource = class extends ApiResource {
1664
+ constructor(http2) {
1665
+ super(http2, "/lead-sources");
1666
+ }
1667
+ /** GET /lead-sources — active ones by default, in display order. */
1668
+ async list(options) {
1669
+ const res = await this.http.request({
1670
+ method: "GET",
1671
+ path: this.basePath,
1672
+ ...options?.includeInactive ? { params: { includeInactive: "true" } } : {}
1673
+ });
1674
+ return res.data;
1675
+ }
1676
+ /** POST /lead-sources — 409 on a duplicate key. */
1677
+ async create(data) {
1678
+ const res = await this.http.request({ method: "POST", path: this.basePath, body: data });
1679
+ return res.data;
1680
+ }
1681
+ /** PATCH /lead-sources/:id — label, active flag or order; the key can't change. */
1682
+ async update(id, data) {
1683
+ const res = await this.http.request({
1684
+ method: "PATCH",
1685
+ path: `${this.basePath}/${id}`,
1686
+ body: data
1687
+ });
1688
+ return res.data;
1689
+ }
1690
+ /** DELETE /lead-sources/:id — soft delete. */
1691
+ async delete(id) {
1692
+ await this.http.request({ method: "DELETE", path: `${this.basePath}/${id}` });
1693
+ }
1694
+ };
1695
+
1132
1696
  // src/resources/tasks.ts
1133
1697
  var TaskResource = class extends ApiResource {
1134
1698
  constructor(http2) {
@@ -1145,9 +1709,14 @@ var TaskResource = class extends ApiResource {
1145
1709
  async get(id) {
1146
1710
  return this.http.request({ method: "GET", path: `${this.basePath}/${id}` });
1147
1711
  }
1148
- /** POST /tasks */
1149
- async create(data) {
1150
- return this.http.request({ method: "POST", path: this.basePath, body: data });
1712
+ /** POST /tasks. Pass `idempotencyKey` to make a retry safe (E64-S6). */
1713
+ async create(data, options) {
1714
+ return this.http.request({
1715
+ method: "POST",
1716
+ path: this.basePath,
1717
+ body: data,
1718
+ ...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}
1719
+ });
1151
1720
  }
1152
1721
  /** PATCH /tasks/:id — non-terminal fields only; terminal status changes use the action methods. */
1153
1722
  async update(id, data) {
@@ -1342,70 +1911,220 @@ var InvoiceSettingsResource = class {
1342
1911
  path: `${this.basePath}/overdue-offsets`,
1343
1912
  body
1344
1913
  });
1345
- return res.offsets;
1914
+ return res.offsets;
1915
+ }
1916
+ /**
1917
+ * GET /admin/invoice-settings/attach-pdf — whether invoice emails carry the
1918
+ * invoice PDF (on unless an admin turned it off).
1919
+ */
1920
+ async getAttachPdf() {
1921
+ const res = await this.http.request({
1922
+ method: "GET",
1923
+ path: `${this.basePath}/attach-pdf`
1924
+ });
1925
+ return res.enabled;
1926
+ }
1927
+ /**
1928
+ * PUT /admin/invoice-settings/attach-pdf — turn the PDF attachment on
1929
+ * invoice emails on or off. Returns the stored value.
1930
+ */
1931
+ async setAttachPdf(enabled) {
1932
+ const body = { enabled };
1933
+ const res = await this.http.request({
1934
+ method: "PUT",
1935
+ path: `${this.basePath}/attach-pdf`,
1936
+ body
1937
+ });
1938
+ return res.enabled;
1939
+ }
1940
+ };
1941
+
1942
+ // src/resources/invoice-reminders.ts
1943
+ var InvoiceRemindersResource = class {
1944
+ constructor(http2) {
1945
+ this.http = http2;
1946
+ this.basePath = "/admin/invoice-reminders";
1947
+ }
1948
+ /** GET /admin/invoice-reminders — the rules, ascending by offset, and the overdue offsets. */
1949
+ async list() {
1950
+ return this.http.request({
1951
+ method: "GET",
1952
+ path: this.basePath
1953
+ });
1954
+ }
1955
+ /**
1956
+ * PUT /admin/invoice-reminders — replace the WHOLE rule set (a rule left out
1957
+ * is deleted): up to 5 rules at unique offsets. Templates may use only
1958
+ * `{{customerName}}`, `{{invoiceNumber}}`, `{{amountDue}}`, `{{total}}`,
1959
+ * `{{dueDate}}`, `{{daysOverdue}}` and `{{companyName}}`; any other is a 422
1960
+ * `REMINDER_TEMPLATE_INVALID` listing each one. Returns the stored set.
1961
+ */
1962
+ async update(rules) {
1963
+ return this.http.request({
1964
+ method: "PUT",
1965
+ path: this.basePath,
1966
+ body: { rules }
1967
+ });
1968
+ }
1969
+ /**
1970
+ * POST /admin/invoice-reminders/preview — render a subject and body exactly
1971
+ * as the reminder email would, against an invoice or a sample. Sends nothing.
1972
+ */
1973
+ async preview(params) {
1974
+ return this.http.request({
1975
+ method: "POST",
1976
+ path: `${this.basePath}/preview`,
1977
+ body: params
1978
+ });
1979
+ }
1980
+ };
1981
+
1982
+ // src/resources/event-automations.ts
1983
+ var EventAutomationsResource = class {
1984
+ constructor(http2) {
1985
+ this.http = http2;
1986
+ }
1987
+ // Each request spells its path out in full (no computed base path) so the
1988
+ // API's SDK route-parity audit (sdk-route-parity-audit.spec.ts) can read it.
1989
+ /** GET — enabled and disabled automations, oldest first, optionally by event type or owner. */
1990
+ async list(params = {}) {
1991
+ const query = {};
1992
+ if (params.owner_tag) query["owner_tag"] = params.owner_tag;
1993
+ if (params.event_type) query["event_type"] = params.event_type;
1994
+ const res = await this.http.request({
1995
+ method: "GET",
1996
+ path: "/admin/event-automations",
1997
+ params: query
1998
+ });
1999
+ return res.data;
2000
+ }
2001
+ /** GET /:id — 404 when it doesn't exist or was deleted. */
2002
+ async get(id) {
2003
+ return this.http.request({
2004
+ method: "GET",
2005
+ path: `/admin/event-automations/${encodeURIComponent(id)}`
2006
+ });
2007
+ }
2008
+ /**
2009
+ * POST — create (201). 400 `VALIDATION_FAILED` for a bad body, or a 400
2010
+ * naming the step whose `action_type` isn't allowed in automations.
2011
+ */
2012
+ async create(input) {
2013
+ return this.http.request({
2014
+ method: "POST",
2015
+ path: "/admin/event-automations",
2016
+ body: input
2017
+ });
1346
2018
  }
1347
2019
  /**
1348
- * GET /admin/invoice-settings/attach-pdf — whether invoice emails carry the
1349
- * invoice PDF (on unless an admin turned it off).
2020
+ * PATCH /:id — only the fields sent change. Sending `actions` replaces the
2021
+ * list: keep each existing step's `id` so a step that already ran isn't run
2022
+ * again. `{ enabled: false }` switches it off.
1350
2023
  */
1351
- async getAttachPdf() {
1352
- const res = await this.http.request({
1353
- method: "GET",
1354
- path: `${this.basePath}/attach-pdf`
2024
+ async update(id, input) {
2025
+ return this.http.request({
2026
+ method: "PATCH",
2027
+ path: `/admin/event-automations/${encodeURIComponent(id)}`,
2028
+ body: input
1355
2029
  });
1356
- return res.enabled;
1357
2030
  }
1358
- /**
1359
- * PUT /admin/invoice-settings/attach-pdf — turn the PDF attachment on
1360
- * invoice emails on or off. Returns the stored value.
1361
- */
1362
- async setAttachPdf(enabled) {
1363
- const body = { enabled };
1364
- const res = await this.http.request({
1365
- method: "PUT",
1366
- path: `${this.basePath}/attach-pdf`,
1367
- body
2031
+ /** DELETE /:id — soft delete; returns `{ id }` (200). */
2032
+ async delete(id) {
2033
+ return this.http.request({
2034
+ method: "DELETE",
2035
+ path: `/admin/event-automations/${encodeURIComponent(id)}`
1368
2036
  });
1369
- return res.enabled;
1370
2037
  }
1371
2038
  };
1372
2039
 
1373
- // src/resources/invoice-reminders.ts
1374
- var InvoiceRemindersResource = class {
2040
+ // src/resources/web-forms.ts
2041
+ var WebFormsResource = class {
1375
2042
  constructor(http2) {
1376
2043
  this.http = http2;
1377
- this.basePath = "/admin/invoice-reminders";
2044
+ this.submissions = new WebFormSubmissionsResource(http2);
1378
2045
  }
1379
- /** GET /admin/invoice-reminders — the rules, ascending by offset, and the overdue offsets. */
2046
+ // Each request spells its path out in full so the API's SDK route-parity
2047
+ // audit (sdk-route-parity-audit.spec.ts) can read it.
2048
+ /** GET — every form not archived, newest first. */
1380
2049
  async list() {
2050
+ const res = await this.http.request({ method: "GET", path: "/admin/web-forms" });
2051
+ return res.data;
2052
+ }
2053
+ /** GET /trade-options — the active trades a `trade` question offers, by current name. */
2054
+ async tradeOptions() {
2055
+ const res = await this.http.request({ method: "GET", path: "/admin/web-forms/trade-options" });
2056
+ return res.data;
2057
+ }
2058
+ /** GET /:id */
2059
+ async get(id) {
2060
+ return this.http.request({ method: "GET", path: `/admin/web-forms/${encodeURIComponent(id)}` });
2061
+ }
2062
+ /** POST — create (201) with its default routing. Pass `recipient_agent_ids`. */
2063
+ async create(input) {
2064
+ return this.http.request({ method: "POST", path: "/admin/web-forms", body: input });
2065
+ }
2066
+ /** PATCH /:id — only the fields sent change. `active: false` switches the form off. */
2067
+ async update(id, input) {
1381
2068
  return this.http.request({
1382
- method: "GET",
1383
- path: this.basePath
2069
+ method: "PATCH",
2070
+ path: `/admin/web-forms/${encodeURIComponent(id)}`,
2071
+ body: input
1384
2072
  });
1385
2073
  }
1386
- /**
1387
- * PUT /admin/invoice-reminders — replace the WHOLE rule set (a rule left out
1388
- * is deleted): up to 5 rules at unique offsets. Templates may use only
1389
- * `{{customerName}}`, `{{invoiceNumber}}`, `{{amountDue}}`, `{{total}}`,
1390
- * `{{dueDate}}`, `{{daysOverdue}}` and `{{companyName}}`; any other is a 422
1391
- * `REMINDER_TEMPLATE_INVALID` listing each one. Returns the stored set.
1392
- */
1393
- async update(rules) {
2074
+ /** DELETE /:id — archive: the public URL stops working and routing stops. Returns `{ id }`. */
2075
+ async archive(id) {
2076
+ return this.http.request({ method: "DELETE", path: `/admin/web-forms/${encodeURIComponent(id)}` });
2077
+ }
2078
+ /** POST /:id/rotate-key — a new public key; the old embed stops working at once. */
2079
+ async rotateKey(id) {
1394
2080
  return this.http.request({
1395
- method: "PUT",
1396
- path: this.basePath,
1397
- body: { rules }
2081
+ method: "POST",
2082
+ path: `/admin/web-forms/${encodeURIComponent(id)}/rotate-key`
1398
2083
  });
1399
2084
  }
2085
+ };
2086
+ var WebFormSubmissionsResource = class {
2087
+ constructor(http2) {
2088
+ this.http = http2;
2089
+ }
2090
+ /** GET /submissions — the first page, newest first, with routing state. Use `listPage` to read on. */
2091
+ async list(params = {}) {
2092
+ return (await this.listPage(params)).data;
2093
+ }
1400
2094
  /**
1401
- * POST /admin/invoice-reminders/preview — render a subject and body exactly
1402
- * as the reminder email would, against an invoice or a sample. Sends nothing.
2095
+ * GET /submissions — one page plus `next`, which is null on the last page.
2096
+ * Pass `next`'s `before` and `before_id` back in to read the following page.
1403
2097
  */
1404
- async preview(params) {
2098
+ async listPage(params = {}) {
2099
+ const query = {};
2100
+ for (const [k, v] of Object.entries(params)) if (v !== void 0) query[k] = v;
2101
+ const res = await this.http.request({
2102
+ method: "GET",
2103
+ path: "/admin/web-forms/submissions",
2104
+ params: query
2105
+ });
2106
+ return { data: res.data, next: res.next ?? null };
2107
+ }
2108
+ /** GET /submissions/:id — answers as seen, plus each routing step. */
2109
+ async get(id) {
2110
+ return this.http.request({
2111
+ method: "GET",
2112
+ path: `/admin/web-forms/submissions/${encodeURIComponent(id)}`
2113
+ });
2114
+ }
2115
+ /** PATCH /submissions/:id — set triage status. */
2116
+ async update(id, input) {
2117
+ return this.http.request({
2118
+ method: "PATCH",
2119
+ path: `/admin/web-forms/submissions/${encodeURIComponent(id)}`,
2120
+ body: input
2121
+ });
2122
+ }
2123
+ /** POST /submissions/:id/route — retry failed steps, run never-run ones (not email, text or webhook: `skipped`); safe to repeat. */
2124
+ async route(id) {
1405
2125
  return this.http.request({
1406
2126
  method: "POST",
1407
- path: `${this.basePath}/preview`,
1408
- body: params
2127
+ path: `/admin/web-forms/submissions/${encodeURIComponent(id)}/route`
1409
2128
  });
1410
2129
  }
1411
2130
  };
@@ -1577,23 +2296,119 @@ var WorkflowsResource = class {
1577
2296
  var ServiceSchedulesResource = class {
1578
2297
  constructor(http2) {
1579
2298
  this.http = http2;
1580
- this.basePath = "/service-schedules";
2299
+ }
2300
+ // Each request spells its path out in full (no computed base path) so the
2301
+ // API's SDK route-parity audit (sdk-route-parity-audit.spec.ts) can read it.
2302
+ /** GET — the schedules of one customer or opportunity. */
2303
+ async list(subject_type, subject_id) {
2304
+ return this.http.request({
2305
+ method: "GET",
2306
+ path: "/service-schedules",
2307
+ params: { subject_type, subject_id }
2308
+ });
2309
+ }
2310
+ /** GET /:id — 404 when it doesn't exist or was deleted. */
2311
+ async get(id) {
2312
+ return this.http.request({
2313
+ method: "GET",
2314
+ path: `/service-schedules/${encodeURIComponent(id)}`
2315
+ });
2316
+ }
2317
+ /**
2318
+ * POST — create (201). The horizon's visits are generated straight away.
2319
+ * 400 `INVALID_RECURRENCE_RULE`, `SERVICE_TRADE_REQUIRED` or a validation error.
2320
+ */
2321
+ async create(input) {
2322
+ return this.http.request({
2323
+ method: "POST",
2324
+ path: "/service-schedules",
2325
+ body: input
2326
+ });
2327
+ }
2328
+ /**
2329
+ * PATCH /:id — only the fields sent change. A cadence, time, crew, duration
2330
+ * or horizon change regenerates future visits from today (`effective_from`,
2331
+ * which must be today) and reports it in `regeneration`; a visit someone changed is never
2332
+ * touched and is listed under `regeneration.protected`. The edit and its
2333
+ * regeneration succeed or fail together. 400 `INVALID_EFFECTIVE_FROM` for any
2334
+ * date but today.
2335
+ */
2336
+ async update(id, input) {
2337
+ return this.http.request({
2338
+ method: "PATCH",
2339
+ path: `/service-schedules/${encodeURIComponent(id)}`,
2340
+ body: input
2341
+ });
2342
+ }
2343
+ /** POST /:id/preview-update — what `update` with the same input would do. Writes nothing. */
2344
+ async previewUpdate(id, input) {
2345
+ return this.http.request({
2346
+ method: "POST",
2347
+ path: `/service-schedules/${encodeURIComponent(id)}/preview-update`,
2348
+ body: input
2349
+ });
2350
+ }
2351
+ /** PATCH /:id/status — pause, resume (`active`) or end. Resume generates the horizon at once. */
2352
+ async setStatus(id, status) {
2353
+ return this.http.request({
2354
+ method: "PATCH",
2355
+ path: `/service-schedules/${encodeURIComponent(id)}/status`,
2356
+ body: { status }
2357
+ });
2358
+ }
2359
+ /** DELETE /:id — soft-delete (204). */
2360
+ async delete(id) {
2361
+ await this.http.request({
2362
+ method: "DELETE",
2363
+ path: `/service-schedules/${encodeURIComponent(id)}`
2364
+ });
1581
2365
  }
1582
2366
  /**
1583
- * POST /service-schedules/conflicts/preview — before booking people, see
1584
- * who is already on another event (`conflicts`) and who would be outside
1585
- * their working hours (`working_hours`). Read-only and warn-only: nothing
1586
- * is ever blocked. Give exactly one of `windows` (single booking) or
1587
- * `schedule` (a cadence). 400 on invalid input, INVALID_RECURRENCE_RULE,
1588
- * INVALID_TIMEZONE or TOO_MANY_WINDOWS (> 400).
2367
+ * POST /conflicts/preview — before booking people, see who is already on
2368
+ * another event (`conflicts`) and who would be outside their working hours
2369
+ * (`working_hours`). Read-only and warn-only: nothing is ever blocked. Give
2370
+ * exactly one of `windows` (single booking) or `schedule` (a cadence). 400 on
2371
+ * invalid input, INVALID_RECURRENCE_RULE, INVALID_TIMEZONE or
2372
+ * TOO_MANY_WINDOWS (> 400).
1589
2373
  */
1590
2374
  async previewConflicts(request) {
1591
2375
  return this.http.request({
1592
2376
  method: "POST",
1593
- path: `${this.basePath}/conflicts/preview`,
2377
+ path: "/service-schedules/conflicts/preview",
1594
2378
  body: request
1595
2379
  });
1596
2380
  }
2381
+ /** GET /settings/crew-roles — which agent roles count as assignable crew. */
2382
+ async getCrewRoles() {
2383
+ return this.http.request({
2384
+ method: "GET",
2385
+ path: "/service-schedules/settings/crew-roles"
2386
+ });
2387
+ }
2388
+ /** PUT /settings/crew-roles — set the assignable roles (an override of the default). */
2389
+ async setCrewRoles(roles) {
2390
+ return this.http.request({
2391
+ method: "PUT",
2392
+ path: "/service-schedules/settings/crew-roles",
2393
+ body: { roles }
2394
+ });
2395
+ }
2396
+ /** GET /settings/crew — assignable crew with their home-base status. */
2397
+ async listCrew() {
2398
+ const res = await this.http.request({
2399
+ method: "GET",
2400
+ path: "/service-schedules/settings/crew"
2401
+ });
2402
+ return res.crew;
2403
+ }
2404
+ /** PUT /settings/crew/:agentId/home-address — set and geocode a crew member's home base. */
2405
+ async setCrewHomeAddress(agentId, address) {
2406
+ return this.http.request({
2407
+ method: "PUT",
2408
+ path: `/service-schedules/settings/crew/${encodeURIComponent(agentId)}/home-address`,
2409
+ body: { address }
2410
+ });
2411
+ }
1597
2412
  };
1598
2413
 
1599
2414
  // src/resources/billing-schedules.ts
@@ -1798,6 +2613,52 @@ var CustomFieldsResource = class {
1798
2613
  params
1799
2614
  });
1800
2615
  }
2616
+ /** GET /admin/custom-fields — the same list through the admin route (Manage grant). */
2617
+ async listAll(params) {
2618
+ return this.http.request({
2619
+ method: "GET",
2620
+ path: "/admin/custom-fields",
2621
+ params
2622
+ });
2623
+ }
2624
+ /** GET /admin/custom-fields/:id */
2625
+ async get(id) {
2626
+ return this.http.request({
2627
+ method: "GET",
2628
+ path: `/admin/custom-fields/${id}`
2629
+ });
2630
+ }
2631
+ /**
2632
+ * POST /admin/custom-fields — define a new field. 400 for `job`, `vendor` or
2633
+ * `agent` (those records can't hold values yet, PRD D6). A `fieldKey` must be
2634
+ * unique per entity type: check `listAll({ entity_type, include_inactive: true })` first.
2635
+ */
2636
+ async create(data) {
2637
+ return this.http.request({
2638
+ method: "POST",
2639
+ path: "/admin/custom-fields",
2640
+ body: data
2641
+ });
2642
+ }
2643
+ /** PATCH /admin/custom-fields/:id — `fieldKey` and `entityType` can't change. */
2644
+ async update(id, data) {
2645
+ return this.http.request({
2646
+ method: "PATCH",
2647
+ path: `/admin/custom-fields/${id}`,
2648
+ body: data
2649
+ });
2650
+ }
2651
+ /**
2652
+ * DELETE /admin/custom-fields/:id — deactivates the definition (204). Records
2653
+ * keep their stored values; `list({ include_inactive: true })` still shows it,
2654
+ * and `update(id, { active: true })` brings it back.
2655
+ */
2656
+ async deactivate(id) {
2657
+ await this.http.request({
2658
+ method: "DELETE",
2659
+ path: `/admin/custom-fields/${id}`
2660
+ });
2661
+ }
1801
2662
  };
1802
2663
 
1803
2664
  // src/resources/ortto.ts
@@ -1873,6 +2734,121 @@ var OrttoResource = class {
1873
2734
  }
1874
2735
  };
1875
2736
 
2737
+ // src/resources/webhook-subscriptions.ts
2738
+ var WebhookSubscriptionsResource = class extends ApiResource {
2739
+ constructor(http2) {
2740
+ super(http2, "/admin/webhooks");
2741
+ }
2742
+ /** POST /admin/webhooks — the returned `secret` is shown only this once. */
2743
+ async create(data) {
2744
+ return this.http.request({ method: "POST", path: this.basePath, body: data });
2745
+ }
2746
+ /** GET /admin/webhooks */
2747
+ async list(params) {
2748
+ return this.http.request({
2749
+ method: "GET",
2750
+ path: this.basePath,
2751
+ params
2752
+ });
2753
+ }
2754
+ /** GET /admin/webhooks/:id */
2755
+ async get(id) {
2756
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}` });
2757
+ }
2758
+ /** PATCH /admin/webhooks/:id */
2759
+ async update(id, data) {
2760
+ return this.http.request({ method: "PATCH", path: `${this.basePath}/${id}`, body: data });
2761
+ }
2762
+ /** DELETE /admin/webhooks/:id */
2763
+ async delete(id) {
2764
+ return this.http.request({ method: "DELETE", path: `${this.basePath}/${id}` });
2765
+ }
2766
+ /** POST /admin/webhooks/:id/test — sends one signed test event to the URL now. */
2767
+ async test(id) {
2768
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/test` });
2769
+ }
2770
+ /**
2771
+ * POST /admin/webhooks/:id/rotate-secret — replaces the signing secret and
2772
+ * returns the new one, shown only this once. New deliveries are signed with
2773
+ * it, but deliveries already queued (and their retries, for several minutes)
2774
+ * keep the old one. `verifyWebhookSignature` checks one secret, so for a few
2775
+ * minutes have your receiver try the new secret and fall back to the old one.
2776
+ */
2777
+ async rotateSecret(id) {
2778
+ return this.http.request({
2779
+ method: "POST",
2780
+ path: `${this.basePath}/${id}/rotate-secret`
2781
+ });
2782
+ }
2783
+ /** GET /admin/webhooks/:id/deliveries — newest first. */
2784
+ async listDeliveries(id, params) {
2785
+ return this.http.request({
2786
+ method: "GET",
2787
+ path: `${this.basePath}/${id}/deliveries`,
2788
+ params
2789
+ });
2790
+ }
2791
+ /**
2792
+ * POST /admin/webhooks/:id/deliveries/:deliveryId/retry — queues a new
2793
+ * delivery of that event (202). The receiver sees the event again, so make
2794
+ * it idempotent on the event id.
2795
+ */
2796
+ async retryDelivery(id, deliveryId) {
2797
+ return this.http.request({
2798
+ method: "POST",
2799
+ path: `${this.basePath}/${id}/deliveries/${deliveryId}/retry`
2800
+ });
2801
+ }
2802
+ };
2803
+
2804
+ // src/resources/users.ts
2805
+ var UsersResource = class extends ApiResource {
2806
+ constructor(http2) {
2807
+ super(http2, "/users");
2808
+ }
2809
+ /** GET /users — paged, with email, role, status and org fields (read grant on users). */
2810
+ async list(params) {
2811
+ return this.http.request({
2812
+ method: "GET",
2813
+ path: this.basePath,
2814
+ params
2815
+ });
2816
+ }
2817
+ /**
2818
+ * GET /users/directory — active users' ids, names and roles, for pickers and
2819
+ * for resolving an assignee. Every role can read it.
2820
+ */
2821
+ async directory(params) {
2822
+ return this.http.request({
2823
+ method: "GET",
2824
+ path: `${this.basePath}/directory`,
2825
+ params
2826
+ });
2827
+ }
2828
+ /**
2829
+ * GET /users/:id/working-hours — the user's own weekly hours and the tenant
2830
+ * default. A non-admin caller may read only their own (403 otherwise).
2831
+ */
2832
+ async getWorkingHours(id) {
2833
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}/working-hours` });
2834
+ }
2835
+ };
2836
+
2837
+ // src/resources/teams.ts
2838
+ var TeamsResource = class extends ApiResource {
2839
+ constructor(http2) {
2840
+ super(http2, "/teams");
2841
+ }
2842
+ /** GET /teams — every team, with `member_count`. */
2843
+ async list() {
2844
+ return this.http.request({ method: "GET", path: this.basePath });
2845
+ }
2846
+ /** GET /teams/:id — one team, with `members`. */
2847
+ async get(id) {
2848
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}` });
2849
+ }
2850
+ };
2851
+
1876
2852
  // src/webhooks.ts
1877
2853
  import { createHmac, timingSafeEqual } from "crypto";
1878
2854
  var WEBHOOK_EVENT_TYPES = [
@@ -1908,7 +2884,8 @@ var WEBHOOK_EVENT_TYPES = [
1908
2884
  "integration_connection.deactivated",
1909
2885
  "customer_payment_method.invalidated",
1910
2886
  "invoice.autopay_succeeded",
1911
- "invoice.autopay_failed"
2887
+ "invoice.autopay_failed",
2888
+ "received"
1912
2889
  ];
1913
2890
  function verifyWebhookSignature(payload, signature, secret) {
1914
2891
  if (!signature || !secret || !payload) {
@@ -2110,6 +3087,18 @@ var PlanesClient = class {
2110
3087
  }
2111
3088
  return this._tags;
2112
3089
  }
3090
+ get lists() {
3091
+ if (!this._lists) {
3092
+ this._lists = new ListsResource(this.http);
3093
+ }
3094
+ return this._lists;
3095
+ }
3096
+ get leadSources() {
3097
+ if (!this._leadSources) {
3098
+ this._leadSources = new LeadSourcesResource(this.http);
3099
+ }
3100
+ return this._leadSources;
3101
+ }
2113
3102
  get tasks() {
2114
3103
  if (!this._tasks) {
2115
3104
  this._tasks = new TaskResource(this.http);
@@ -2165,6 +3154,20 @@ var PlanesClient = class {
2165
3154
  }
2166
3155
  return this._invoiceReminders;
2167
3156
  }
3157
+ /** Event automations, admin only — `eventAutomations.list/get/create/update/delete` (E60-S2). */
3158
+ get eventAutomations() {
3159
+ if (!this._eventAutomations) {
3160
+ this._eventAutomations = new EventAutomationsResource(this.http);
3161
+ }
3162
+ return this._eventAutomations;
3163
+ }
3164
+ /** Web forms and their submissions, admin only — `webForms.list/get/create/update/archive/rotateKey`, `webForms.submissions.list/get/update/route` (E60-S6). */
3165
+ get webForms() {
3166
+ if (!this._webForms) {
3167
+ this._webForms = new WebFormsResource(this.http);
3168
+ }
3169
+ return this._webForms;
3170
+ }
2168
3171
  /** Published sequences a contact can be enrolled in (E51-F2-S6). */
2169
3172
  get contactSequences() {
2170
3173
  if (!this._contactSequences) {
@@ -2215,6 +3218,30 @@ var PlanesClient = class {
2215
3218
  }
2216
3219
  return this._ortto;
2217
3220
  }
3221
+ /**
3222
+ * Webhook subscriptions — where entity events are delivered (E64-S10). Admin
3223
+ * grants and the `open_api` feature. Not `webhooks`: that is the event router.
3224
+ */
3225
+ get webhookSubscriptions() {
3226
+ if (!this._webhookSubscriptions) {
3227
+ this._webhookSubscriptions = new WebhookSubscriptionsResource(this.http);
3228
+ }
3229
+ return this._webhookSubscriptions;
3230
+ }
3231
+ /** The tenant's users, read-only: list and directory (E64-S10). */
3232
+ get users() {
3233
+ if (!this._users) {
3234
+ this._users = new UsersResource(this.http);
3235
+ }
3236
+ return this._users;
3237
+ }
3238
+ /** Teams, read-only (E64-S10). */
3239
+ get teams() {
3240
+ if (!this._teams) {
3241
+ this._teams = new TeamsResource(this.http);
3242
+ }
3243
+ return this._teams;
3244
+ }
2218
3245
  /**
2219
3246
  * Ergonomic webhook event router — `client.webhooks.on(pattern, handler)`
2220
3247
  * plus `client.webhooks.process({ body, signature, secret })`. Auth-independent
@@ -2254,14 +3281,14 @@ var BILLING_ADJUSTMENT_STATUSES = ["pending", "consumed", "cancelled"];
2254
3281
  var BILLING_ADJUSTMENT_TARGETS = ["next_invoice"];
2255
3282
 
2256
3283
  // src/types/contact-types.ts
2257
- var CONTACT_SORT_KEYS = ["name", "created_at", "recent"];
3284
+ var CONTACT_SORT_KEYS = ["name", "created_at", "recent", "updated_at"];
2258
3285
  var CONTACT_LIST_INCLUDES = ["channels", "links"];
2259
3286
 
2260
3287
  // src/types/customer-types.ts
2261
- var CUSTOMER_SORT_KEYS = ["created_at", "last_name", "first_name", "email", "phone", "source", "recent"];
3288
+ var CUSTOMER_SORT_KEYS = ["created_at", "last_name", "first_name", "email", "phone", "source", "recent", "updated_at"];
2262
3289
 
2263
3290
  // src/types/opportunity-types.ts
2264
- var OPPORTUNITY_SORT_KEYS = ["created_at", "total", "title", "customer_name", "stage", "priority", "recent"];
3291
+ var OPPORTUNITY_SORT_KEYS = ["created_at", "total", "title", "customer_name", "stage", "priority", "recent", "updated_at"];
2265
3292
  var OPPORTUNITY_STATES = ["open", "closed", "won", "lost"];
2266
3293
 
2267
3294
  // src/types/job-types.ts
@@ -2289,7 +3316,8 @@ var MODULE_KEYS = [
2289
3316
  "contacts",
2290
3317
  "tasks",
2291
3318
  "comments",
2292
- "conversations"
3319
+ "conversations",
3320
+ "web_forms"
2293
3321
  ];
2294
3322
 
2295
3323
  // src/types/plan-types.ts
@@ -2351,6 +3379,12 @@ var WORKFLOW_EVENT_RULE_EVENT_TYPES = [
2351
3379
  var WORKFLOW_EVENT_RULE_MATCH_ALL_EVENT_TYPES = [
2352
3380
  "calendar_event.created"
2353
3381
  ];
3382
+ var WORKFLOW_EVENT_RULE_CATCH_UP_EVENT_TYPES = [
3383
+ "calendar_event.created"
3384
+ ];
3385
+
3386
+ // src/types/task-types.ts
3387
+ var TASK_SORT_KEYS = ["due_at", "created_at", "updated_at"];
2354
3388
 
2355
3389
  // src/agent.ts
2356
3390
  import * as http from "http";
@@ -2550,6 +3584,7 @@ export {
2550
3584
  ESTIMATE_STATUSES,
2551
3585
  EnrollmentsResource,
2552
3586
  EstimateResource,
3587
+ EventAutomationsResource,
2553
3588
  FEATURE_KEYS,
2554
3589
  GmailResource,
2555
3590
  InventoryResource,
@@ -2558,6 +3593,8 @@ export {
2558
3593
  InvoiceSettingsResource,
2559
3594
  JOB_SORT_KEYS,
2560
3595
  JobResource,
3596
+ LeadSourcesResource,
3597
+ ListsResource,
2561
3598
  MODULE_KEYS,
2562
3599
  MeResource,
2563
3600
  MemoryTokenStorage,
@@ -2581,13 +3618,20 @@ export {
2581
3618
  RolesResource,
2582
3619
  ServerError,
2583
3620
  ServiceSchedulesResource,
3621
+ TASK_SORT_KEYS,
2584
3622
  TagResource,
2585
3623
  TaskResource,
3624
+ TeamsResource,
3625
+ UsersResource,
2586
3626
  ValidationError,
2587
3627
  WEBHOOK_EVENT_TYPES,
3628
+ WORKFLOW_EVENT_RULE_CATCH_UP_EVENT_TYPES,
2588
3629
  WORKFLOW_EVENT_RULE_EVENT_TYPES,
2589
3630
  WORKFLOW_EVENT_RULE_MATCH_ALL_EVENT_TYPES,
3631
+ WebFormSubmissionsResource,
3632
+ WebFormsResource,
2590
3633
  WebhookRouter,
3634
+ WebhookSubscriptionsResource,
2591
3635
  WebhookVerificationError,
2592
3636
  WorkflowEventRulesResource,
2593
3637
  WorkflowsResource,