@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.js CHANGED
@@ -73,6 +73,7 @@ __export(index_exports, {
73
73
  ESTIMATE_STATUSES: () => ESTIMATE_STATUSES,
74
74
  EnrollmentsResource: () => EnrollmentsResource,
75
75
  EstimateResource: () => EstimateResource,
76
+ EventAutomationsResource: () => EventAutomationsResource,
76
77
  FEATURE_KEYS: () => FEATURE_KEYS,
77
78
  GmailResource: () => GmailResource,
78
79
  InventoryResource: () => InventoryResource,
@@ -81,6 +82,8 @@ __export(index_exports, {
81
82
  InvoiceSettingsResource: () => InvoiceSettingsResource,
82
83
  JOB_SORT_KEYS: () => JOB_SORT_KEYS,
83
84
  JobResource: () => JobResource,
85
+ LeadSourcesResource: () => LeadSourcesResource,
86
+ ListsResource: () => ListsResource,
84
87
  MODULE_KEYS: () => MODULE_KEYS,
85
88
  MeResource: () => MeResource,
86
89
  MemoryTokenStorage: () => MemoryTokenStorage,
@@ -104,13 +107,20 @@ __export(index_exports, {
104
107
  RolesResource: () => RolesResource,
105
108
  ServerError: () => ServerError,
106
109
  ServiceSchedulesResource: () => ServiceSchedulesResource,
110
+ TASK_SORT_KEYS: () => TASK_SORT_KEYS,
107
111
  TagResource: () => TagResource,
108
112
  TaskResource: () => TaskResource,
113
+ TeamsResource: () => TeamsResource,
114
+ UsersResource: () => UsersResource,
109
115
  ValidationError: () => ValidationError,
110
116
  WEBHOOK_EVENT_TYPES: () => WEBHOOK_EVENT_TYPES,
117
+ WORKFLOW_EVENT_RULE_CATCH_UP_EVENT_TYPES: () => WORKFLOW_EVENT_RULE_CATCH_UP_EVENT_TYPES,
111
118
  WORKFLOW_EVENT_RULE_EVENT_TYPES: () => WORKFLOW_EVENT_RULE_EVENT_TYPES,
112
119
  WORKFLOW_EVENT_RULE_MATCH_ALL_EVENT_TYPES: () => WORKFLOW_EVENT_RULE_MATCH_ALL_EVENT_TYPES,
120
+ WebFormSubmissionsResource: () => WebFormSubmissionsResource,
121
+ WebFormsResource: () => WebFormsResource,
113
122
  WebhookRouter: () => WebhookRouter,
123
+ WebhookSubscriptionsResource: () => WebhookSubscriptionsResource,
114
124
  WebhookVerificationError: () => WebhookVerificationError,
115
125
  WorkflowEventRulesResource: () => WorkflowEventRulesResource,
116
126
  WorkflowsResource: () => WorkflowsResource,
@@ -292,6 +302,9 @@ var HttpClient = class {
292
302
  this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
293
303
  this.retryOnRateLimit = config.retryOnRateLimit !== false;
294
304
  this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
305
+ this.getAccessToken = config.getAccessToken;
306
+ this.onUnauthorized = config.onUnauthorized;
307
+ this.fetchImpl = config.fetch;
295
308
  if (config.clientId && config.clientSecret) {
296
309
  this.oauth = {
297
310
  clientId: config.clientId,
@@ -308,53 +321,68 @@ var HttpClient = class {
308
321
  let lastError;
309
322
  let didRefreshOn401 = false;
310
323
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
324
+ const authHeaders = await this.resolveAuthHeaders();
311
325
  const headers = {
312
326
  "Content-Type": "application/json",
313
- ...await this.resolveAuthHeaders(),
327
+ ...authHeaders,
314
328
  ...options.headers
315
329
  };
330
+ if (options.idempotencyKey) headers["Idempotency-Key"] = options.idempotencyKey;
331
+ const timer = timeoutSignal(this.timeout);
316
332
  const fetchOptions = {
317
333
  method: options.method,
318
334
  headers,
319
- signal: AbortSignal.timeout(this.timeout)
335
+ signal: timer.signal
320
336
  };
321
337
  if (bodyStr !== void 0) {
322
338
  fetchOptions.body = bodyStr;
323
339
  }
324
- const response = await fetch(url, fetchOptions);
325
- if (response.ok) {
326
- if (response.status === 204) {
327
- return void 0;
340
+ try {
341
+ const response = await (this.fetchImpl ?? fetch)(url, fetchOptions);
342
+ if (response.ok) {
343
+ if (response.status === 204) {
344
+ return void 0;
345
+ }
346
+ if (options.responseType === "arrayBuffer") {
347
+ return await response.arrayBuffer();
348
+ }
349
+ return await response.json();
328
350
  }
329
- if (options.responseType === "arrayBuffer") {
330
- return await response.arrayBuffer();
351
+ if (response.status === 429 && this.retryOnRateLimit && attempt < this.maxRetries) {
352
+ const retryAfter = this.parseRetryAfter(response);
353
+ await this.sleep(retryAfter * 1e3);
354
+ continue;
331
355
  }
332
- return await response.json();
333
- }
334
- if (response.status === 429 && this.retryOnRateLimit && attempt < this.maxRetries) {
335
- const retryAfter = this.parseRetryAfter(response);
336
- await this.sleep(retryAfter * 1e3);
337
- continue;
338
- }
339
- if (response.status === 401 && this.oauth && !didRefreshOn401) {
340
- didRefreshOn401 = true;
341
- await this.refreshTokens();
342
- attempt--;
343
- continue;
344
- }
345
- lastError = await this.mapError(response);
346
- if (response.status !== 429 || !this.retryOnRateLimit) {
347
- throw lastError;
356
+ if (response.status === 401 && this.oauth && !didRefreshOn401) {
357
+ didRefreshOn401 = true;
358
+ await this.refreshTokens();
359
+ attempt--;
360
+ continue;
361
+ }
362
+ if (response.status === 401 && this.onUnauthorized && !didRefreshOn401) {
363
+ didRefreshOn401 = true;
364
+ if (await this.recoverSession(authHeaders["Authorization"])) {
365
+ attempt--;
366
+ continue;
367
+ }
368
+ }
369
+ lastError = await this.mapError(response);
370
+ if (response.status !== 429 || !this.retryOnRateLimit) {
371
+ throw lastError;
372
+ }
373
+ } finally {
374
+ timer.clear();
348
375
  }
349
376
  }
350
377
  throw lastError ?? new RateLimitError();
351
378
  }
352
379
  getAuthHeaders() {
380
+ if (this.getAccessToken) return {};
353
381
  if (this.accessToken) {
354
382
  return { Authorization: `Bearer ${this.accessToken}` };
355
383
  }
356
384
  if (this.apiKey) {
357
- return { "X-API-Key": this.apiKey };
385
+ return { Authorization: `Bearer ${this.apiKey}` };
358
386
  }
359
387
  return {};
360
388
  }
@@ -365,6 +393,10 @@ var HttpClient = class {
365
393
  * the token is within the skew window of expiry.
366
394
  */
367
395
  async resolveAuthHeaders() {
396
+ if (this.getAccessToken) {
397
+ const token = await withTimeout(this.getAccessToken(), this.timeout, "getAccessToken");
398
+ return token ? { Authorization: `Bearer ${token}` } : {};
399
+ }
368
400
  if (!this.oauth) {
369
401
  return this.getAuthHeaders();
370
402
  }
@@ -428,6 +460,41 @@ var HttpClient = class {
428
460
  this.refreshInFlight = void 0;
429
461
  }
430
462
  }
463
+ /**
464
+ * Ask the app to refresh its session after a 401. Resolves `true` when the
465
+ * request should be retried.
466
+ * - Requests that got a 401 while a refresh is running share that one call.
467
+ * - A request whose 401 arrives after a refresh already finished (it was
468
+ * sent with the old token) doesn't start a second refresh: if the current
469
+ * token differs from the one it sent, it just retries with the new one.
470
+ * - A refresh that throws, or doesn't settle within the request timeout,
471
+ * counts as "still signed out": the original 401 surfaces.
472
+ */
473
+ async recoverSession(sentAuthorization) {
474
+ if (!this.onUnauthorized || !this.getAccessToken) return false;
475
+ if (!this.unauthorizedInFlight) {
476
+ try {
477
+ const current = await withTimeout(this.getAccessToken(), this.timeout, "getAccessToken");
478
+ if (current && `Bearer ${current}` !== sentAuthorization) return true;
479
+ } catch {
480
+ return false;
481
+ }
482
+ }
483
+ if (!this.unauthorizedInFlight) {
484
+ const call = this.onUnauthorized;
485
+ const inFlight = (async () => {
486
+ try {
487
+ return await withTimeout(call(), this.timeout, "onUnauthorized");
488
+ } catch {
489
+ return false;
490
+ }
491
+ })().finally(() => {
492
+ if (this.unauthorizedInFlight === inFlight) this.unauthorizedInFlight = void 0;
493
+ });
494
+ this.unauthorizedInFlight = inFlight;
495
+ }
496
+ return this.unauthorizedInFlight;
497
+ }
431
498
  buildUrl(path, params) {
432
499
  const url = new URL(`${this.baseUrl}${path}`);
433
500
  if (params) {
@@ -455,7 +522,8 @@ var HttpClient = class {
455
522
  body = await response.json();
456
523
  } catch {
457
524
  }
458
- const message = body["message"] ?? response.statusText ?? "Request failed";
525
+ const envelope = body["error"];
526
+ const message = (typeof body["message"] === "string" ? body["message"] : void 0) ?? (typeof envelope?.message === "string" ? envelope.message : void 0) ?? response.statusText ?? "Request failed";
459
527
  switch (response.status) {
460
528
  case 400: {
461
529
  const fieldErrors = body["errors"] ?? [];
@@ -482,6 +550,29 @@ var HttpClient = class {
482
550
  return new Promise((resolve) => setTimeout(resolve, ms));
483
551
  }
484
552
  };
553
+ function timeoutSignal(ms) {
554
+ if (typeof AbortSignal.timeout === "function") {
555
+ return { signal: AbortSignal.timeout(ms), clear: () => void 0 };
556
+ }
557
+ const controller = new AbortController();
558
+ const timer = setTimeout(() => controller.abort(), ms);
559
+ return { signal: controller.signal, clear: () => clearTimeout(timer) };
560
+ }
561
+ function withTimeout(promise, ms, what) {
562
+ return new Promise((resolve, reject) => {
563
+ const timer = setTimeout(() => reject(new Error(`${what} did not settle within ${ms}ms`)), ms);
564
+ Promise.resolve(promise).then(
565
+ (value) => {
566
+ clearTimeout(timer);
567
+ resolve(value);
568
+ },
569
+ (err) => {
570
+ clearTimeout(timer);
571
+ reject(err);
572
+ }
573
+ );
574
+ });
575
+ }
485
576
 
486
577
  // src/resources/base-resource.ts
487
578
  var ApiResource = class {
@@ -511,13 +602,16 @@ var BaseResource = class extends ApiResource {
511
602
  });
512
603
  }
513
604
  /**
514
- * Create a new entity.
605
+ * Create a new entity. Pass `idempotencyKey` (a UUID you keep for retries) to
606
+ * make a retry safe on routes that honor it (customers, opportunities, contacts,
607
+ * tasks): a repeat returns the first response and creates nothing.
515
608
  */
516
- async create(data) {
609
+ async create(data, options) {
517
610
  return this.http.request({
518
611
  method: "POST",
519
612
  path: this.basePath,
520
- body: data
613
+ body: data,
614
+ ...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}
521
615
  });
522
616
  }
523
617
  /**
@@ -547,6 +641,12 @@ function toQueryParams(params) {
547
641
  if (!params) return out;
548
642
  for (const [key, value] of Object.entries(params)) {
549
643
  if (value === void 0 || value === null) continue;
644
+ if (key === "customFields" && typeof value === "object" && !Array.isArray(value)) {
645
+ for (const [field, v] of Object.entries(value)) {
646
+ if (v !== void 0 && v !== null) out[`cf.${field}`] = String(v);
647
+ }
648
+ continue;
649
+ }
550
650
  if (Array.isArray(value)) {
551
651
  if (value.length === 0) continue;
552
652
  out[key] = value.map(String).join(",");
@@ -573,6 +673,16 @@ var OpportunityResource = class extends BaseResource {
573
673
  params: toQueryParams(params)
574
674
  });
575
675
  }
676
+ /**
677
+ * GET /opportunities/trade-options — the active trades a client can request
678
+ * (`requestedTradeIds` on create/update), sorted by name.
679
+ */
680
+ async tradeOptions() {
681
+ return this.http.request({
682
+ method: "GET",
683
+ path: `${this.basePath}/trade-options`
684
+ });
685
+ }
576
686
  /** PUT /opportunities/{id} — partial update; stage moves go through workflow transitions. */
577
687
  async update(id, data) {
578
688
  return this.http.request({
@@ -581,6 +691,21 @@ var OpportunityResource = class extends BaseResource {
581
691
  body: data
582
692
  });
583
693
  }
694
+ /** GET /opportunities/:id/transitions — the pipeline moves available from the current stage, for the caller's role. */
695
+ async listTransitions(id) {
696
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}/transitions` });
697
+ }
698
+ /**
699
+ * POST /opportunities/:id/transition — take one pipeline move, as the stage button does: required
700
+ * fields are checked, the move is logged, and the stage's automations run (they can send email).
701
+ */
702
+ async transition(id, data) {
703
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/transition`, body: data });
704
+ }
705
+ /** GET /opportunities/:id/workflow — the pipeline instance with its transition history (404 if none). */
706
+ async getWorkflow(id) {
707
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}/workflow` });
708
+ }
584
709
  };
585
710
 
586
711
  // src/resources/customers.ts
@@ -627,6 +752,19 @@ var CustomerResource = class extends BaseResource {
627
752
  path: `/customers/${winnerId}/merge/${loserId}`
628
753
  });
629
754
  }
755
+ /**
756
+ * POST /customers/{id}/convert-to-company — turn a person customer into a
757
+ * company. The person becomes the company's primary contact (their email and
758
+ * phone, with do-not-contact and marketing opt-outs carried over). A customer
759
+ * that's already a company is a 409 `ALREADY_COMPANY`.
760
+ */
761
+ async convertToCompany(id, data = {}) {
762
+ return this.http.request({
763
+ method: "POST",
764
+ path: `${this.basePath}/${id}/convert-to-company`,
765
+ body: data
766
+ });
767
+ }
630
768
  };
631
769
 
632
770
  // src/resources/properties.ts
@@ -665,6 +803,17 @@ var PropertyResource = class extends ApiResource {
665
803
  body: data
666
804
  });
667
805
  }
806
+ /** GET /customers/:customerId/properties/:id — one property, checked against its customer (404 if it isn't theirs). */
807
+ async getForCustomer(customerId, id) {
808
+ return this.http.request({
809
+ method: "GET",
810
+ path: `/customers/${customerId}/properties/${id}`
811
+ });
812
+ }
813
+ /** POST /properties/:id/geocode — queue a fresh geocode of the property's address. */
814
+ async geocode(id) {
815
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/geocode` });
816
+ }
668
817
  /** DELETE /customers/{customerId}/properties/{id} — soft delete (204). */
669
818
  async delete(customerId, id) {
670
819
  await this.http.request({
@@ -680,7 +829,11 @@ var CalendarEventResource = class extends BaseResource {
680
829
  super(http2, "/events");
681
830
  }
682
831
  async list(params) {
683
- return super.list(params);
832
+ return this.http.request({
833
+ method: "GET",
834
+ path: this.basePath,
835
+ params: params ?? {}
836
+ });
684
837
  }
685
838
  /**
686
839
  * PATCH /events/:id. For an occurrence of a recurring event, `editMode`
@@ -699,7 +852,8 @@ var CalendarEventResource = class extends BaseResource {
699
852
  * POST /events/:id/complete — marks the event completed and runs its "on
700
853
  * complete" transition, best-effort. The event is completed even when the
701
854
  * transition can't run; `workflow_trigger` says whether it fired, was
702
- * skipped (none set) or failed (with a reason).
855
+ * skipped (none set, or `handled_by_pipeline_rule`: the pipeline's rule
856
+ * moves the record instead) or failed (with a reason).
703
857
  */
704
858
  async complete(id, body = {}) {
705
859
  return this.http.request({
@@ -708,6 +862,34 @@ var CalendarEventResource = class extends BaseResource {
708
862
  body
709
863
  });
710
864
  }
865
+ /**
866
+ * POST /events/:id/cancel. For an occurrence of a recurring event, `editMode`
867
+ * picks which occurrences are cancelled (`this` when omitted). 409 if already
868
+ * cancelled; 422 for a completed event.
869
+ */
870
+ async cancel(id, options = {}) {
871
+ return this.http.request({
872
+ method: "POST",
873
+ path: `${this.basePath}/${id}/cancel`,
874
+ params: { recurrence_edit_mode: options.editMode }
875
+ });
876
+ }
877
+ /** POST /events/:id/participants — add a user, contact, crew or agent to the event. No invite is sent. */
878
+ async addParticipant(id, data) {
879
+ return this.http.request({
880
+ method: "POST",
881
+ path: `${this.basePath}/${id}/participants`,
882
+ body: data
883
+ });
884
+ }
885
+ /** PATCH /events/:id/participants/:participantId — set a participant's response. */
886
+ async updateParticipantStatus(id, participantId, status) {
887
+ return this.http.request({
888
+ method: "PATCH",
889
+ path: `${this.basePath}/${id}/participants/${participantId}`,
890
+ body: { status }
891
+ });
892
+ }
711
893
  };
712
894
 
713
895
  // src/resources/contracts.ts
@@ -715,6 +897,73 @@ var ContractResource = class extends BaseResource {
715
897
  constructor(http2) {
716
898
  super(http2, "/contracts");
717
899
  }
900
+ /** POST /contracts/for-subject — a bare agreement for a customer or a service schedule (no opportunity, no line groups). */
901
+ async createForSubject(data) {
902
+ return this.http.request({ method: "POST", path: `${this.basePath}/for-subject`, body: data });
903
+ }
904
+ /** POST /contracts/flat — a contract on an opportunity with a manually entered total and a contract template, no estimates. */
905
+ async createFlat(data) {
906
+ return this.http.request({ method: "POST", path: `${this.basePath}/flat`, body: data });
907
+ }
908
+ /** POST /contracts/{id}/change-orders — a change order on a signed or fulfilled contract (409 otherwise). */
909
+ async createChangeOrder(id, data) {
910
+ return this.http.request({
911
+ method: "POST",
912
+ path: `${this.basePath}/${id}/change-orders`,
913
+ body: data
914
+ });
915
+ }
916
+ /**
917
+ * GET /contracts/{id}/change-orders — the change orders written against a
918
+ * contract, oldest first, each with its line groups. A change order has none
919
+ * of its own, so asking for one returns an empty list.
920
+ */
921
+ async listChangeOrders(id) {
922
+ return this.http.request({
923
+ method: "GET",
924
+ path: `${this.basePath}/${id}/change-orders`
925
+ });
926
+ }
927
+ /** POST /contracts/{id}/submit — submit for approval; auto-approved when under the tenant's threshold. */
928
+ async submit(id) {
929
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/submit` });
930
+ }
931
+ /** POST /contracts/{id}/approve — approve a contract pending approval (needs the approve grant). */
932
+ async approve(id) {
933
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/approve` });
934
+ }
935
+ /** POST /contracts/{id}/cancel — cancel a contract in any state before it's signed. */
936
+ async cancel(id) {
937
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/cancel` });
938
+ }
939
+ /** POST /contracts/{id}/reopen — return a kicked-back contract to draft for review and re-send. */
940
+ async reopen(id) {
941
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/reopen` });
942
+ }
943
+ /**
944
+ * POST /contracts/{id}/send — **emails the signer a "Review & Sign" link.**
945
+ * Snapshots the clauses and creates the signing token. An empty body
946
+ * addresses the customer; `signerContactId` sends it to that contact instead.
947
+ * The first send of a contract uses one unit of the tenant's e-signature
948
+ * allowance; a re-send doesn't. Not safe to retry blindly after a timeout: a
949
+ * repeat emails again.
950
+ */
951
+ async send(id, data = {}) {
952
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/send`, body: data });
953
+ }
954
+ /**
955
+ * GET /contracts/{id}/pdf — the contract PDF bytes. `type: 'signed'` returns
956
+ * the stored signed copy (404 until it's signed); otherwise a preview renders.
957
+ */
958
+ async downloadPdf(id, options = {}) {
959
+ return this.http.request({
960
+ method: "GET",
961
+ path: `${this.basePath}/${id}/pdf`,
962
+ params: options.type ? { type: options.type } : {},
963
+ headers: { Accept: "application/pdf" },
964
+ responseType: "arrayBuffer"
965
+ });
966
+ }
718
967
  };
719
968
 
720
969
  // src/resources/jobs.ts
@@ -784,6 +1033,28 @@ var JobResource = class extends ApiResource {
784
1033
  path: "/jobs/trade-coverage"
785
1034
  });
786
1035
  }
1036
+ /** GET /jobs/{id}/workflow — the job's pipeline instance with its transition history (404 if none). */
1037
+ async getWorkflow(id) {
1038
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}/workflow` });
1039
+ }
1040
+ /**
1041
+ * POST /jobs/{id}/transition — take one pipeline move, as the stage button
1042
+ * does: required fields are checked, the move is logged, and **the
1043
+ * transition's actions run**. A tenant can attach actions that create or send
1044
+ * an invoice, charge a saved card, send a contract, or send email, SMS or a
1045
+ * webhook. Not safe to retry blindly after a timeout: check `getWorkflow()`
1046
+ * first, because a repeat runs the actions again.
1047
+ */
1048
+ async transition(id, data) {
1049
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/transition`, body: data });
1050
+ }
1051
+ /** GET /jobs/{id}/transitions — the pipeline moves available from the job's current stage, for the caller's role. */
1052
+ async listTransitions(id) {
1053
+ return this.http.request({
1054
+ method: "GET",
1055
+ path: `${this.basePath}/${id}/transitions`
1056
+ });
1057
+ }
787
1058
  };
788
1059
 
789
1060
  // src/resources/estimates.ts
@@ -841,6 +1112,74 @@ var EstimateResource = class extends ApiResource {
841
1112
  path: `/jobs/${jobId}/estimates/${id}`
842
1113
  });
843
1114
  }
1115
+ /** GET /estimates/{id}/workflow — the estimate's pipeline instance with its transition history (404 if none). */
1116
+ async getWorkflow(id) {
1117
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}/workflow` });
1118
+ }
1119
+ /**
1120
+ * POST /estimates/{id}/transition — take one pipeline move. **The
1121
+ * transition's actions run**, as for `jobs.transition()`: a tenant can attach
1122
+ * actions that bill, charge, send a contract, or send email, SMS or a webhook.
1123
+ * Not safe to retry blindly after a timeout: check `getWorkflow()` first.
1124
+ */
1125
+ async transition(id, data) {
1126
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/transition`, body: data });
1127
+ }
1128
+ /**
1129
+ * PUT /jobs/{jobId}/estimates/{id}/status — move the estimate's status (see
1130
+ * `UpdateEstimateStatusDto` for the allowed moves). The route itself sends
1131
+ * nothing and creates no contract or invoice; a tenant's webhooks or
1132
+ * automations on the status change can.
1133
+ */
1134
+ async updateStatus(jobId, id, data) {
1135
+ return this.http.request({
1136
+ method: "PUT",
1137
+ path: `/jobs/${jobId}/estimates/${id}/status`,
1138
+ body: data
1139
+ });
1140
+ }
1141
+ /** GET /estimates/{id}/transitions — the pipeline moves available from the estimate's current stage, for the caller's role. */
1142
+ async listTransitions(id) {
1143
+ return this.http.request({
1144
+ method: "GET",
1145
+ path: `${this.basePath}/${id}/transitions`
1146
+ });
1147
+ }
1148
+ /**
1149
+ * POST /jobs/{jobId}/estimates/{estimateId}/line-items — add a line item. With
1150
+ * `materialCatalogId` or `tradeLaborItemId`, the catalog fills in description,
1151
+ * unit and price. The estimate's subtotal and total are recalculated.
1152
+ */
1153
+ async createLineItem(jobId, estimateId, data) {
1154
+ return this.http.request({
1155
+ method: "POST",
1156
+ path: `/jobs/${jobId}/estimates/${estimateId}/line-items`,
1157
+ body: data
1158
+ });
1159
+ }
1160
+ /** PUT /jobs/{jobId}/estimates/{estimateId}/line-items/{id} — partial update; totals are recalculated. */
1161
+ async updateLineItem(jobId, estimateId, id, data) {
1162
+ return this.http.request({
1163
+ method: "PUT",
1164
+ path: `/jobs/${jobId}/estimates/${estimateId}/line-items/${id}`,
1165
+ body: data
1166
+ });
1167
+ }
1168
+ /** DELETE /jobs/{jobId}/estimates/{estimateId}/line-items/{id} — totals are recalculated. */
1169
+ async deleteLineItem(jobId, estimateId, id) {
1170
+ await this.http.request({
1171
+ method: "DELETE",
1172
+ path: `/jobs/${jobId}/estimates/${estimateId}/line-items/${id}`
1173
+ });
1174
+ }
1175
+ /** PUT /jobs/{jobId}/estimates/{estimateId}/line-items/reorder — set each item's `sortOrder` (and optionally `section`) in one call. */
1176
+ async reorderLineItems(jobId, estimateId, data) {
1177
+ return this.http.request({
1178
+ method: "PUT",
1179
+ path: `/jobs/${jobId}/estimates/${estimateId}/line-items/reorder`,
1180
+ body: data
1181
+ });
1182
+ }
844
1183
  };
845
1184
 
846
1185
  // src/resources/invoices.ts
@@ -903,6 +1242,24 @@ var InvoiceResource = class extends BaseResource {
903
1242
  params
904
1243
  });
905
1244
  }
1245
+ /** GET /admin/invoices/{invoiceId}/payments — every payment recorded against the invoice (Read Payment). */
1246
+ async listPayments(invoiceId) {
1247
+ return this.http.request({
1248
+ method: "GET",
1249
+ path: `${this.basePath}/${invoiceId}/payments`
1250
+ });
1251
+ }
1252
+ /**
1253
+ * GET /admin/payments/recording-settings — the tenant's resolved
1254
+ * payment-recording settings (whether manual recording is on, and which
1255
+ * methods). Read Invoice.
1256
+ */
1257
+ async getPaymentRecordingSettings() {
1258
+ return this.http.request({
1259
+ method: "GET",
1260
+ path: "/admin/payments/recording-settings"
1261
+ });
1262
+ }
906
1263
  /** GET /admin/invoices/overdue/summary — `{ count, total }` of every overdue invoice. Read Invoice. */
907
1264
  async overdueSummary() {
908
1265
  return this.http.request({
@@ -932,33 +1289,72 @@ var InventoryResource = class extends BaseResource {
932
1289
  body: data
933
1290
  });
934
1291
  }
1292
+ // ---- Material used on a job (E64-S9). The routes live under /jobs/{jobId}. ----
1293
+ /**
1294
+ * POST /jobs/{jobId}/consumption — record stock drawn from a location for a
1295
+ * job. Each line lowers that location's on-hand quantity.
1296
+ */
1297
+ async recordJobConsumption(jobId, data) {
1298
+ return this.http.request({
1299
+ method: "POST",
1300
+ path: `/jobs/${jobId}/consumption`,
1301
+ body: data
1302
+ });
1303
+ }
1304
+ /**
1305
+ * GET /jobs/{jobId}/consumption — one page of the job's consume and return
1306
+ * movements (newest first by default), the job's totals across every page,
1307
+ * and page metadata. The rows are under `items`, not `data`.
1308
+ */
1309
+ async listJobConsumption(jobId, params) {
1310
+ return this.http.request({
1311
+ method: "GET",
1312
+ path: `/jobs/${jobId}/consumption`,
1313
+ params: toQueryParams(params)
1314
+ });
1315
+ }
1316
+ /** POST /jobs/{jobId}/return — put unused stock from a job back into a location. */
1317
+ async returnFromJob(jobId, data) {
1318
+ return this.http.request({
1319
+ method: "POST",
1320
+ path: `/jobs/${jobId}/return`,
1321
+ body: data
1322
+ });
1323
+ }
1324
+ /** GET /jobs/{jobId}/material-variance — estimated vs actual material for the job. */
1325
+ async getJobMaterialVariance(jobId) {
1326
+ return this.http.request({
1327
+ method: "GET",
1328
+ path: `/jobs/${jobId}/material-variance`
1329
+ });
1330
+ }
935
1331
  };
936
1332
 
937
1333
  // src/resources/contacts.ts
938
1334
  var ContactChannelResource = class {
939
1335
  constructor(http2, contactId) {
940
1336
  this.http = http2;
941
- this.basePath = `/contacts/${contactId}/channels`;
1337
+ this.contactId = contactId;
942
1338
  }
943
1339
  /** Returns `{ channel, duplicateOf }` — `duplicateOf` is the dedup signal. */
944
1340
  async create(data) {
945
1341
  return this.http.request({
946
1342
  method: "POST",
947
- path: this.basePath,
1343
+ path: `/contacts/${this.contactId}/channels`,
948
1344
  body: data
949
1345
  });
950
1346
  }
951
1347
  async update(channelId, data) {
952
1348
  return this.http.request({
953
1349
  method: "PATCH",
954
- path: `${this.basePath}/${channelId}`,
1350
+ path: `/contacts/${this.contactId}/channels/${channelId}`,
955
1351
  body: data
956
1352
  });
957
1353
  }
958
1354
  async delete(channelId) {
959
1355
  await this.http.request({
960
1356
  method: "DELETE",
961
- path: `${this.basePath}/${channelId}`
1357
+ path: `/contacts/${this.contactId}/channels/${channelId}`
962
1358
  });
963
1359
  }
964
1360
  };
@@ -986,6 +1382,46 @@ var ContactResource = class extends BaseResource {
986
1382
  path: `/contacts/${winnerId}/merge/${loserId}`
987
1383
  });
988
1384
  }
1385
+ /** GET /contacts/roles — the distinct roles in use on active links, for a role picker. */
1386
+ async listRoles() {
1387
+ return this.http.request({ method: "GET", path: "/contacts/roles" });
1388
+ }
1389
+ /** GET /contacts/:id/links — every link, active and ended (history). `get()` returns active links only. */
1390
+ async listLinks(contactId) {
1391
+ return this.http.request({ method: "GET", path: `/contacts/${contactId}/links` });
1392
+ }
1393
+ /** POST /contacts/:id/links — link the contact to a customer or property. */
1394
+ async addLink(contactId, data) {
1395
+ return this.http.request({ method: "POST", path: `/contacts/${contactId}/links`, body: data });
1396
+ }
1397
+ /** PATCH /contacts/:id/links/:linkId — change role/primary, or end the link (`endedAt`). */
1398
+ async updateLink(contactId, linkId, data) {
1399
+ return this.http.request({
1400
+ method: "PATCH",
1401
+ path: `/contacts/${contactId}/links/${linkId}`,
1402
+ body: data
1403
+ });
1404
+ }
1405
+ /** POST /contacts/:id/workflow/start — opt the contact into the relationship pipeline (409 if started). */
1406
+ async startWorkflow(contactId) {
1407
+ return this.http.request({ method: "POST", path: `/contacts/${contactId}/workflow/start` });
1408
+ }
1409
+ /** GET /contacts/:id/workflow — current stage and the transitions available from it. */
1410
+ async getWorkflow(contactId) {
1411
+ return this.http.request({ method: "GET", path: `/contacts/${contactId}/workflow` });
1412
+ }
1413
+ /** POST /contacts/:id/workflow/transitions/:transitionId — 422 for an invalid move or unmet required fields. */
1414
+ async transition(contactId, transitionId) {
1415
+ return this.http.request({
1416
+ method: "POST",
1417
+ path: `/contacts/${contactId}/workflow/transitions/${transitionId}`,
1418
+ body: {}
1419
+ });
1420
+ }
1421
+ /** GET /contacts/:id/workflow/history */
1422
+ async getWorkflowHistory(contactId) {
1423
+ return this.http.request({ method: "GET", path: `/contacts/${contactId}/workflow/history` });
1424
+ }
989
1425
  channels(contactId) {
990
1426
  return new ContactChannelResource(this.http, contactId);
991
1427
  }
@@ -1232,6 +1668,14 @@ var TagResource = class extends ApiResource {
1232
1668
  async delete(id) {
1233
1669
  await this.http.request({ method: "DELETE", path: `${this.basePath}/${id}` });
1234
1670
  }
1671
+ /**
1672
+ * POST /tags/:winnerId/merge/:loserId — fold a duplicate tag into another: records
1673
+ * move to the winner (`retagged`), duplicates are dropped (`dropped`), and the
1674
+ * loser is deleted. Irreversible.
1675
+ */
1676
+ async merge(winnerId, loserId) {
1677
+ return this.http.request({ method: "POST", path: `${this.basePath}/${winnerId}/merge/${loserId}` });
1678
+ }
1235
1679
  /** `TagsService.setEntityTags()` returns the final tag set wrapped in `{ tags }`. */
1236
1680
  async setEntityTags(entityType, entityId, data) {
1237
1681
  return this.http.request({
@@ -1240,6 +1684,31 @@ var TagResource = class extends ApiResource {
1240
1684
  body: data
1241
1685
  });
1242
1686
  }
1687
+ /**
1688
+ * POST /entities/:type/:id/tags — add ONE tag, keeping the record's other tags.
1689
+ * Safe to call concurrently with other tag changes on the same record (unlike
1690
+ * read-modify-write through `setEntityTags()`). Adding a tag the record already
1691
+ * carries is a no-op. Returns the record's full tag set.
1692
+ */
1693
+ async addEntityTag(entityType, entityId, ref) {
1694
+ const body = ref.tagId !== void 0 ? { tag_id: ref.tagId } : { tag_name: ref.tagName };
1695
+ return this.http.request({
1696
+ method: "POST",
1697
+ path: `/entities/${entityType}/${entityId}/tags`,
1698
+ body
1699
+ });
1700
+ }
1701
+ /**
1702
+ * DELETE /entities/:type/:id/tags/:tagId — remove ONE tag, keeping the rest.
1703
+ * Removing a tag the record doesn't carry is a no-op. Returns the record's
1704
+ * full tag set.
1705
+ */
1706
+ async removeEntityTag(entityType, entityId, tagId) {
1707
+ return this.http.request({
1708
+ method: "DELETE",
1709
+ path: `/entities/${entityType}/${entityId}/tags/${tagId}`
1710
+ });
1711
+ }
1243
1712
  async getEntityTags(entityType, entityId) {
1244
1713
  return this.http.request({
1245
1714
  method: "GET",
@@ -1255,6 +1724,111 @@ var TagResource = class extends ApiResource {
1255
1724
  }
1256
1725
  };
1257
1726
 
1727
+ // src/resources/lists.ts
1728
+ var ListsResource = class extends ApiResource {
1729
+ constructor(http2) {
1730
+ super(http2, "/lists");
1731
+ }
1732
+ /** GET /lists — every list, newest first, each with `member_count`. A bare array. */
1733
+ async list() {
1734
+ return this.http.request({ method: "GET", path: this.basePath });
1735
+ }
1736
+ /**
1737
+ * GET /lists/filter-fields — the standard fields, their types and the operators
1738
+ * each accepts for a smart list of this entity type, and whether custom fields
1739
+ * are accepted (E66-S3). A save that breaks these rules is a 400.
1740
+ */
1741
+ async filterFields(entityType) {
1742
+ return this.http.request({
1743
+ method: "GET",
1744
+ path: `${this.basePath}/filter-fields`,
1745
+ params: { entity_type: entityType }
1746
+ });
1747
+ }
1748
+ /** GET /lists/:id — one list with an on-demand `member_count`. */
1749
+ async get(id) {
1750
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}` });
1751
+ }
1752
+ /** POST /lists */
1753
+ async create(data) {
1754
+ return this.http.request({ method: "POST", path: this.basePath, body: data });
1755
+ }
1756
+ /** PATCH /lists/:id — rename, or replace a smart list's definition. */
1757
+ async update(id, data) {
1758
+ return this.http.request({ method: "PATCH", path: `${this.basePath}/${id}`, body: data });
1759
+ }
1760
+ /** DELETE /lists/:id — soft delete; the records on the list are untouched. */
1761
+ async delete(id) {
1762
+ await this.http.request({ method: "DELETE", path: `${this.basePath}/${id}` });
1763
+ }
1764
+ /** POST /lists/:id/members — static lists only; idempotent on duplicates. */
1765
+ async addMembers(id, members) {
1766
+ return this.http.request({
1767
+ method: "POST",
1768
+ path: `${this.basePath}/${id}/members`,
1769
+ body: { members }
1770
+ });
1771
+ }
1772
+ /** DELETE /lists/:id/members — static lists only. */
1773
+ async removeMembers(id, members) {
1774
+ return this.http.request({
1775
+ method: "DELETE",
1776
+ path: `${this.basePath}/${id}/members`,
1777
+ body: { members }
1778
+ });
1779
+ }
1780
+ /** GET /lists/:id/recipients — people (audience, contact and customer lists), with consent. */
1781
+ async getRecipients(id, params) {
1782
+ return this.http.request({
1783
+ method: "GET",
1784
+ path: `${this.basePath}/${id}/recipients`,
1785
+ params
1786
+ });
1787
+ }
1788
+ /** POST /lists/:id/export — queues a CSV export; a notification links the file when it's ready. */
1789
+ async export(id, options) {
1790
+ return this.http.request({
1791
+ method: "POST",
1792
+ path: `${this.basePath}/${id}/export`,
1793
+ body: options ?? {}
1794
+ });
1795
+ }
1796
+ };
1797
+
1798
+ // src/resources/lead-sources.ts
1799
+ var LeadSourcesResource = class extends ApiResource {
1800
+ constructor(http2) {
1801
+ super(http2, "/lead-sources");
1802
+ }
1803
+ /** GET /lead-sources — active ones by default, in display order. */
1804
+ async list(options) {
1805
+ const res = await this.http.request({
1806
+ method: "GET",
1807
+ path: this.basePath,
1808
+ ...options?.includeInactive ? { params: { includeInactive: "true" } } : {}
1809
+ });
1810
+ return res.data;
1811
+ }
1812
+ /** POST /lead-sources — 409 on a duplicate key. */
1813
+ async create(data) {
1814
+ const res = await this.http.request({ method: "POST", path: this.basePath, body: data });
1815
+ return res.data;
1816
+ }
1817
+ /** PATCH /lead-sources/:id — label, active flag or order; the key can't change. */
1818
+ async update(id, data) {
1819
+ const res = await this.http.request({
1820
+ method: "PATCH",
1821
+ path: `${this.basePath}/${id}`,
1822
+ body: data
1823
+ });
1824
+ return res.data;
1825
+ }
1826
+ /** DELETE /lead-sources/:id — soft delete. */
1827
+ async delete(id) {
1828
+ await this.http.request({ method: "DELETE", path: `${this.basePath}/${id}` });
1829
+ }
1830
+ };
1831
+
1258
1832
  // src/resources/tasks.ts
1259
1833
  var TaskResource = class extends ApiResource {
1260
1834
  constructor(http2) {
@@ -1271,9 +1845,14 @@ var TaskResource = class extends ApiResource {
1271
1845
  async get(id) {
1272
1846
  return this.http.request({ method: "GET", path: `${this.basePath}/${id}` });
1273
1847
  }
1274
- /** POST /tasks */
1275
- async create(data) {
1276
- return this.http.request({ method: "POST", path: this.basePath, body: data });
1848
+ /** POST /tasks. Pass `idempotencyKey` to make a retry safe (E64-S6). */
1849
+ async create(data, options) {
1850
+ return this.http.request({
1851
+ method: "POST",
1852
+ path: this.basePath,
1853
+ body: data,
1854
+ ...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}
1855
+ });
1277
1856
  }
1278
1857
  /** PATCH /tasks/:id — non-terminal fields only; terminal status changes use the action methods. */
1279
1858
  async update(id, data) {
@@ -1468,70 +2047,220 @@ var InvoiceSettingsResource = class {
1468
2047
  path: `${this.basePath}/overdue-offsets`,
1469
2048
  body
1470
2049
  });
1471
- return res.offsets;
2050
+ return res.offsets;
2051
+ }
2052
+ /**
2053
+ * GET /admin/invoice-settings/attach-pdf — whether invoice emails carry the
2054
+ * invoice PDF (on unless an admin turned it off).
2055
+ */
2056
+ async getAttachPdf() {
2057
+ const res = await this.http.request({
2058
+ method: "GET",
2059
+ path: `${this.basePath}/attach-pdf`
2060
+ });
2061
+ return res.enabled;
2062
+ }
2063
+ /**
2064
+ * PUT /admin/invoice-settings/attach-pdf — turn the PDF attachment on
2065
+ * invoice emails on or off. Returns the stored value.
2066
+ */
2067
+ async setAttachPdf(enabled) {
2068
+ const body = { enabled };
2069
+ const res = await this.http.request({
2070
+ method: "PUT",
2071
+ path: `${this.basePath}/attach-pdf`,
2072
+ body
2073
+ });
2074
+ return res.enabled;
2075
+ }
2076
+ };
2077
+
2078
+ // src/resources/invoice-reminders.ts
2079
+ var InvoiceRemindersResource = class {
2080
+ constructor(http2) {
2081
+ this.http = http2;
2082
+ this.basePath = "/admin/invoice-reminders";
2083
+ }
2084
+ /** GET /admin/invoice-reminders — the rules, ascending by offset, and the overdue offsets. */
2085
+ async list() {
2086
+ return this.http.request({
2087
+ method: "GET",
2088
+ path: this.basePath
2089
+ });
2090
+ }
2091
+ /**
2092
+ * PUT /admin/invoice-reminders — replace the WHOLE rule set (a rule left out
2093
+ * is deleted): up to 5 rules at unique offsets. Templates may use only
2094
+ * `{{customerName}}`, `{{invoiceNumber}}`, `{{amountDue}}`, `{{total}}`,
2095
+ * `{{dueDate}}`, `{{daysOverdue}}` and `{{companyName}}`; any other is a 422
2096
+ * `REMINDER_TEMPLATE_INVALID` listing each one. Returns the stored set.
2097
+ */
2098
+ async update(rules) {
2099
+ return this.http.request({
2100
+ method: "PUT",
2101
+ path: this.basePath,
2102
+ body: { rules }
2103
+ });
2104
+ }
2105
+ /**
2106
+ * POST /admin/invoice-reminders/preview — render a subject and body exactly
2107
+ * as the reminder email would, against an invoice or a sample. Sends nothing.
2108
+ */
2109
+ async preview(params) {
2110
+ return this.http.request({
2111
+ method: "POST",
2112
+ path: `${this.basePath}/preview`,
2113
+ body: params
2114
+ });
2115
+ }
2116
+ };
2117
+
2118
+ // src/resources/event-automations.ts
2119
+ var EventAutomationsResource = class {
2120
+ constructor(http2) {
2121
+ this.http = http2;
2122
+ }
2123
+ // Each request spells its path out in full (no computed base path) so the
2124
+ // API's SDK route-parity audit (sdk-route-parity-audit.spec.ts) can read it.
2125
+ /** GET — enabled and disabled automations, oldest first, optionally by event type or owner. */
2126
+ async list(params = {}) {
2127
+ const query = {};
2128
+ if (params.owner_tag) query["owner_tag"] = params.owner_tag;
2129
+ if (params.event_type) query["event_type"] = params.event_type;
2130
+ const res = await this.http.request({
2131
+ method: "GET",
2132
+ path: "/admin/event-automations",
2133
+ params: query
2134
+ });
2135
+ return res.data;
2136
+ }
2137
+ /** GET /:id — 404 when it doesn't exist or was deleted. */
2138
+ async get(id) {
2139
+ return this.http.request({
2140
+ method: "GET",
2141
+ path: `/admin/event-automations/${encodeURIComponent(id)}`
2142
+ });
2143
+ }
2144
+ /**
2145
+ * POST — create (201). 400 `VALIDATION_FAILED` for a bad body, or a 400
2146
+ * naming the step whose `action_type` isn't allowed in automations.
2147
+ */
2148
+ async create(input) {
2149
+ return this.http.request({
2150
+ method: "POST",
2151
+ path: "/admin/event-automations",
2152
+ body: input
2153
+ });
1472
2154
  }
1473
2155
  /**
1474
- * GET /admin/invoice-settings/attach-pdf — whether invoice emails carry the
1475
- * invoice PDF (on unless an admin turned it off).
2156
+ * PATCH /:id — only the fields sent change. Sending `actions` replaces the
2157
+ * list: keep each existing step's `id` so a step that already ran isn't run
2158
+ * again. `{ enabled: false }` switches it off.
1476
2159
  */
1477
- async getAttachPdf() {
1478
- const res = await this.http.request({
1479
- method: "GET",
1480
- path: `${this.basePath}/attach-pdf`
2160
+ async update(id, input) {
2161
+ return this.http.request({
2162
+ method: "PATCH",
2163
+ path: `/admin/event-automations/${encodeURIComponent(id)}`,
2164
+ body: input
1481
2165
  });
1482
- return res.enabled;
1483
2166
  }
1484
- /**
1485
- * PUT /admin/invoice-settings/attach-pdf — turn the PDF attachment on
1486
- * invoice emails on or off. Returns the stored value.
1487
- */
1488
- async setAttachPdf(enabled) {
1489
- const body = { enabled };
1490
- const res = await this.http.request({
1491
- method: "PUT",
1492
- path: `${this.basePath}/attach-pdf`,
1493
- body
2167
+ /** DELETE /:id — soft delete; returns `{ id }` (200). */
2168
+ async delete(id) {
2169
+ return this.http.request({
2170
+ method: "DELETE",
2171
+ path: `/admin/event-automations/${encodeURIComponent(id)}`
1494
2172
  });
1495
- return res.enabled;
1496
2173
  }
1497
2174
  };
1498
2175
 
1499
- // src/resources/invoice-reminders.ts
1500
- var InvoiceRemindersResource = class {
2176
+ // src/resources/web-forms.ts
2177
+ var WebFormsResource = class {
1501
2178
  constructor(http2) {
1502
2179
  this.http = http2;
1503
- this.basePath = "/admin/invoice-reminders";
2180
+ this.submissions = new WebFormSubmissionsResource(http2);
1504
2181
  }
1505
- /** GET /admin/invoice-reminders — the rules, ascending by offset, and the overdue offsets. */
2182
+ // Each request spells its path out in full so the API's SDK route-parity
2183
+ // audit (sdk-route-parity-audit.spec.ts) can read it.
2184
+ /** GET — every form not archived, newest first. */
1506
2185
  async list() {
2186
+ const res = await this.http.request({ method: "GET", path: "/admin/web-forms" });
2187
+ return res.data;
2188
+ }
2189
+ /** GET /trade-options — the active trades a `trade` question offers, by current name. */
2190
+ async tradeOptions() {
2191
+ const res = await this.http.request({ method: "GET", path: "/admin/web-forms/trade-options" });
2192
+ return res.data;
2193
+ }
2194
+ /** GET /:id */
2195
+ async get(id) {
2196
+ return this.http.request({ method: "GET", path: `/admin/web-forms/${encodeURIComponent(id)}` });
2197
+ }
2198
+ /** POST — create (201) with its default routing. Pass `recipient_agent_ids`. */
2199
+ async create(input) {
2200
+ return this.http.request({ method: "POST", path: "/admin/web-forms", body: input });
2201
+ }
2202
+ /** PATCH /:id — only the fields sent change. `active: false` switches the form off. */
2203
+ async update(id, input) {
1507
2204
  return this.http.request({
1508
- method: "GET",
1509
- path: this.basePath
2205
+ method: "PATCH",
2206
+ path: `/admin/web-forms/${encodeURIComponent(id)}`,
2207
+ body: input
1510
2208
  });
1511
2209
  }
1512
- /**
1513
- * PUT /admin/invoice-reminders — replace the WHOLE rule set (a rule left out
1514
- * is deleted): up to 5 rules at unique offsets. Templates may use only
1515
- * `{{customerName}}`, `{{invoiceNumber}}`, `{{amountDue}}`, `{{total}}`,
1516
- * `{{dueDate}}`, `{{daysOverdue}}` and `{{companyName}}`; any other is a 422
1517
- * `REMINDER_TEMPLATE_INVALID` listing each one. Returns the stored set.
1518
- */
1519
- async update(rules) {
2210
+ /** DELETE /:id — archive: the public URL stops working and routing stops. Returns `{ id }`. */
2211
+ async archive(id) {
2212
+ return this.http.request({ method: "DELETE", path: `/admin/web-forms/${encodeURIComponent(id)}` });
2213
+ }
2214
+ /** POST /:id/rotate-key — a new public key; the old embed stops working at once. */
2215
+ async rotateKey(id) {
1520
2216
  return this.http.request({
1521
- method: "PUT",
1522
- path: this.basePath,
1523
- body: { rules }
2217
+ method: "POST",
2218
+ path: `/admin/web-forms/${encodeURIComponent(id)}/rotate-key`
1524
2219
  });
1525
2220
  }
2221
+ };
2222
+ var WebFormSubmissionsResource = class {
2223
+ constructor(http2) {
2224
+ this.http = http2;
2225
+ }
2226
+ /** GET /submissions — the first page, newest first, with routing state. Use `listPage` to read on. */
2227
+ async list(params = {}) {
2228
+ return (await this.listPage(params)).data;
2229
+ }
1526
2230
  /**
1527
- * POST /admin/invoice-reminders/preview — render a subject and body exactly
1528
- * as the reminder email would, against an invoice or a sample. Sends nothing.
2231
+ * GET /submissions — one page plus `next`, which is null on the last page.
2232
+ * Pass `next`'s `before` and `before_id` back in to read the following page.
1529
2233
  */
1530
- async preview(params) {
2234
+ async listPage(params = {}) {
2235
+ const query = {};
2236
+ for (const [k, v] of Object.entries(params)) if (v !== void 0) query[k] = v;
2237
+ const res = await this.http.request({
2238
+ method: "GET",
2239
+ path: "/admin/web-forms/submissions",
2240
+ params: query
2241
+ });
2242
+ return { data: res.data, next: res.next ?? null };
2243
+ }
2244
+ /** GET /submissions/:id — answers as seen, plus each routing step. */
2245
+ async get(id) {
2246
+ return this.http.request({
2247
+ method: "GET",
2248
+ path: `/admin/web-forms/submissions/${encodeURIComponent(id)}`
2249
+ });
2250
+ }
2251
+ /** PATCH /submissions/:id — set triage status. */
2252
+ async update(id, input) {
2253
+ return this.http.request({
2254
+ method: "PATCH",
2255
+ path: `/admin/web-forms/submissions/${encodeURIComponent(id)}`,
2256
+ body: input
2257
+ });
2258
+ }
2259
+ /** POST /submissions/:id/route — retry failed steps, run never-run ones (not email, text or webhook: `skipped`); safe to repeat. */
2260
+ async route(id) {
1531
2261
  return this.http.request({
1532
2262
  method: "POST",
1533
- path: `${this.basePath}/preview`,
1534
- body: params
2263
+ path: `/admin/web-forms/submissions/${encodeURIComponent(id)}/route`
1535
2264
  });
1536
2265
  }
1537
2266
  };
@@ -1703,23 +2432,119 @@ var WorkflowsResource = class {
1703
2432
  var ServiceSchedulesResource = class {
1704
2433
  constructor(http2) {
1705
2434
  this.http = http2;
1706
- this.basePath = "/service-schedules";
2435
+ }
2436
+ // Each request spells its path out in full (no computed base path) so the
2437
+ // API's SDK route-parity audit (sdk-route-parity-audit.spec.ts) can read it.
2438
+ /** GET — the schedules of one customer or opportunity. */
2439
+ async list(subject_type, subject_id) {
2440
+ return this.http.request({
2441
+ method: "GET",
2442
+ path: "/service-schedules",
2443
+ params: { subject_type, subject_id }
2444
+ });
2445
+ }
2446
+ /** GET /:id — 404 when it doesn't exist or was deleted. */
2447
+ async get(id) {
2448
+ return this.http.request({
2449
+ method: "GET",
2450
+ path: `/service-schedules/${encodeURIComponent(id)}`
2451
+ });
2452
+ }
2453
+ /**
2454
+ * POST — create (201). The horizon's visits are generated straight away.
2455
+ * 400 `INVALID_RECURRENCE_RULE`, `SERVICE_TRADE_REQUIRED` or a validation error.
2456
+ */
2457
+ async create(input) {
2458
+ return this.http.request({
2459
+ method: "POST",
2460
+ path: "/service-schedules",
2461
+ body: input
2462
+ });
2463
+ }
2464
+ /**
2465
+ * PATCH /:id — only the fields sent change. A cadence, time, crew, duration
2466
+ * or horizon change regenerates future visits from today (`effective_from`,
2467
+ * which must be today) and reports it in `regeneration`; a visit someone changed is never
2468
+ * touched and is listed under `regeneration.protected`. The edit and its
2469
+ * regeneration succeed or fail together. 400 `INVALID_EFFECTIVE_FROM` for any
2470
+ * date but today.
2471
+ */
2472
+ async update(id, input) {
2473
+ return this.http.request({
2474
+ method: "PATCH",
2475
+ path: `/service-schedules/${encodeURIComponent(id)}`,
2476
+ body: input
2477
+ });
2478
+ }
2479
+ /** POST /:id/preview-update — what `update` with the same input would do. Writes nothing. */
2480
+ async previewUpdate(id, input) {
2481
+ return this.http.request({
2482
+ method: "POST",
2483
+ path: `/service-schedules/${encodeURIComponent(id)}/preview-update`,
2484
+ body: input
2485
+ });
2486
+ }
2487
+ /** PATCH /:id/status — pause, resume (`active`) or end. Resume generates the horizon at once. */
2488
+ async setStatus(id, status) {
2489
+ return this.http.request({
2490
+ method: "PATCH",
2491
+ path: `/service-schedules/${encodeURIComponent(id)}/status`,
2492
+ body: { status }
2493
+ });
2494
+ }
2495
+ /** DELETE /:id — soft-delete (204). */
2496
+ async delete(id) {
2497
+ await this.http.request({
2498
+ method: "DELETE",
2499
+ path: `/service-schedules/${encodeURIComponent(id)}`
2500
+ });
1707
2501
  }
1708
2502
  /**
1709
- * POST /service-schedules/conflicts/preview — before booking people, see
1710
- * who is already on another event (`conflicts`) and who would be outside
1711
- * their working hours (`working_hours`). Read-only and warn-only: nothing
1712
- * is ever blocked. Give exactly one of `windows` (single booking) or
1713
- * `schedule` (a cadence). 400 on invalid input, INVALID_RECURRENCE_RULE,
1714
- * INVALID_TIMEZONE or TOO_MANY_WINDOWS (> 400).
2503
+ * POST /conflicts/preview — before booking people, see who is already on
2504
+ * another event (`conflicts`) and who would be outside their working hours
2505
+ * (`working_hours`). Read-only and warn-only: nothing is ever blocked. Give
2506
+ * exactly one of `windows` (single booking) or `schedule` (a cadence). 400 on
2507
+ * invalid input, INVALID_RECURRENCE_RULE, INVALID_TIMEZONE or
2508
+ * TOO_MANY_WINDOWS (> 400).
1715
2509
  */
1716
2510
  async previewConflicts(request) {
1717
2511
  return this.http.request({
1718
2512
  method: "POST",
1719
- path: `${this.basePath}/conflicts/preview`,
2513
+ path: "/service-schedules/conflicts/preview",
1720
2514
  body: request
1721
2515
  });
1722
2516
  }
2517
+ /** GET /settings/crew-roles — which agent roles count as assignable crew. */
2518
+ async getCrewRoles() {
2519
+ return this.http.request({
2520
+ method: "GET",
2521
+ path: "/service-schedules/settings/crew-roles"
2522
+ });
2523
+ }
2524
+ /** PUT /settings/crew-roles — set the assignable roles (an override of the default). */
2525
+ async setCrewRoles(roles) {
2526
+ return this.http.request({
2527
+ method: "PUT",
2528
+ path: "/service-schedules/settings/crew-roles",
2529
+ body: { roles }
2530
+ });
2531
+ }
2532
+ /** GET /settings/crew — assignable crew with their home-base status. */
2533
+ async listCrew() {
2534
+ const res = await this.http.request({
2535
+ method: "GET",
2536
+ path: "/service-schedules/settings/crew"
2537
+ });
2538
+ return res.crew;
2539
+ }
2540
+ /** PUT /settings/crew/:agentId/home-address — set and geocode a crew member's home base. */
2541
+ async setCrewHomeAddress(agentId, address) {
2542
+ return this.http.request({
2543
+ method: "PUT",
2544
+ path: `/service-schedules/settings/crew/${encodeURIComponent(agentId)}/home-address`,
2545
+ body: { address }
2546
+ });
2547
+ }
1723
2548
  };
1724
2549
 
1725
2550
  // src/resources/billing-schedules.ts
@@ -1924,6 +2749,52 @@ var CustomFieldsResource = class {
1924
2749
  params
1925
2750
  });
1926
2751
  }
2752
+ /** GET /admin/custom-fields — the same list through the admin route (Manage grant). */
2753
+ async listAll(params) {
2754
+ return this.http.request({
2755
+ method: "GET",
2756
+ path: "/admin/custom-fields",
2757
+ params
2758
+ });
2759
+ }
2760
+ /** GET /admin/custom-fields/:id */
2761
+ async get(id) {
2762
+ return this.http.request({
2763
+ method: "GET",
2764
+ path: `/admin/custom-fields/${id}`
2765
+ });
2766
+ }
2767
+ /**
2768
+ * POST /admin/custom-fields — define a new field. 400 for `job`, `vendor` or
2769
+ * `agent` (those records can't hold values yet, PRD D6). A `fieldKey` must be
2770
+ * unique per entity type: check `listAll({ entity_type, include_inactive: true })` first.
2771
+ */
2772
+ async create(data) {
2773
+ return this.http.request({
2774
+ method: "POST",
2775
+ path: "/admin/custom-fields",
2776
+ body: data
2777
+ });
2778
+ }
2779
+ /** PATCH /admin/custom-fields/:id — `fieldKey` and `entityType` can't change. */
2780
+ async update(id, data) {
2781
+ return this.http.request({
2782
+ method: "PATCH",
2783
+ path: `/admin/custom-fields/${id}`,
2784
+ body: data
2785
+ });
2786
+ }
2787
+ /**
2788
+ * DELETE /admin/custom-fields/:id — deactivates the definition (204). Records
2789
+ * keep their stored values; `list({ include_inactive: true })` still shows it,
2790
+ * and `update(id, { active: true })` brings it back.
2791
+ */
2792
+ async deactivate(id) {
2793
+ await this.http.request({
2794
+ method: "DELETE",
2795
+ path: `/admin/custom-fields/${id}`
2796
+ });
2797
+ }
1927
2798
  };
1928
2799
 
1929
2800
  // src/resources/ortto.ts
@@ -1999,6 +2870,121 @@ var OrttoResource = class {
1999
2870
  }
2000
2871
  };
2001
2872
 
2873
+ // src/resources/webhook-subscriptions.ts
2874
+ var WebhookSubscriptionsResource = class extends ApiResource {
2875
+ constructor(http2) {
2876
+ super(http2, "/admin/webhooks");
2877
+ }
2878
+ /** POST /admin/webhooks — the returned `secret` is shown only this once. */
2879
+ async create(data) {
2880
+ return this.http.request({ method: "POST", path: this.basePath, body: data });
2881
+ }
2882
+ /** GET /admin/webhooks */
2883
+ async list(params) {
2884
+ return this.http.request({
2885
+ method: "GET",
2886
+ path: this.basePath,
2887
+ params
2888
+ });
2889
+ }
2890
+ /** GET /admin/webhooks/:id */
2891
+ async get(id) {
2892
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}` });
2893
+ }
2894
+ /** PATCH /admin/webhooks/:id */
2895
+ async update(id, data) {
2896
+ return this.http.request({ method: "PATCH", path: `${this.basePath}/${id}`, body: data });
2897
+ }
2898
+ /** DELETE /admin/webhooks/:id */
2899
+ async delete(id) {
2900
+ return this.http.request({ method: "DELETE", path: `${this.basePath}/${id}` });
2901
+ }
2902
+ /** POST /admin/webhooks/:id/test — sends one signed test event to the URL now. */
2903
+ async test(id) {
2904
+ return this.http.request({ method: "POST", path: `${this.basePath}/${id}/test` });
2905
+ }
2906
+ /**
2907
+ * POST /admin/webhooks/:id/rotate-secret — replaces the signing secret and
2908
+ * returns the new one, shown only this once. New deliveries are signed with
2909
+ * it, but deliveries already queued (and their retries, for several minutes)
2910
+ * keep the old one. `verifyWebhookSignature` checks one secret, so for a few
2911
+ * minutes have your receiver try the new secret and fall back to the old one.
2912
+ */
2913
+ async rotateSecret(id) {
2914
+ return this.http.request({
2915
+ method: "POST",
2916
+ path: `${this.basePath}/${id}/rotate-secret`
2917
+ });
2918
+ }
2919
+ /** GET /admin/webhooks/:id/deliveries — newest first. */
2920
+ async listDeliveries(id, params) {
2921
+ return this.http.request({
2922
+ method: "GET",
2923
+ path: `${this.basePath}/${id}/deliveries`,
2924
+ params
2925
+ });
2926
+ }
2927
+ /**
2928
+ * POST /admin/webhooks/:id/deliveries/:deliveryId/retry — queues a new
2929
+ * delivery of that event (202). The receiver sees the event again, so make
2930
+ * it idempotent on the event id.
2931
+ */
2932
+ async retryDelivery(id, deliveryId) {
2933
+ return this.http.request({
2934
+ method: "POST",
2935
+ path: `${this.basePath}/${id}/deliveries/${deliveryId}/retry`
2936
+ });
2937
+ }
2938
+ };
2939
+
2940
+ // src/resources/users.ts
2941
+ var UsersResource = class extends ApiResource {
2942
+ constructor(http2) {
2943
+ super(http2, "/users");
2944
+ }
2945
+ /** GET /users — paged, with email, role, status and org fields (read grant on users). */
2946
+ async list(params) {
2947
+ return this.http.request({
2948
+ method: "GET",
2949
+ path: this.basePath,
2950
+ params
2951
+ });
2952
+ }
2953
+ /**
2954
+ * GET /users/directory — active users' ids, names and roles, for pickers and
2955
+ * for resolving an assignee. Every role can read it.
2956
+ */
2957
+ async directory(params) {
2958
+ return this.http.request({
2959
+ method: "GET",
2960
+ path: `${this.basePath}/directory`,
2961
+ params
2962
+ });
2963
+ }
2964
+ /**
2965
+ * GET /users/:id/working-hours — the user's own weekly hours and the tenant
2966
+ * default. A non-admin caller may read only their own (403 otherwise).
2967
+ */
2968
+ async getWorkingHours(id) {
2969
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}/working-hours` });
2970
+ }
2971
+ };
2972
+
2973
+ // src/resources/teams.ts
2974
+ var TeamsResource = class extends ApiResource {
2975
+ constructor(http2) {
2976
+ super(http2, "/teams");
2977
+ }
2978
+ /** GET /teams — every team, with `member_count`. */
2979
+ async list() {
2980
+ return this.http.request({ method: "GET", path: this.basePath });
2981
+ }
2982
+ /** GET /teams/:id — one team, with `members`. */
2983
+ async get(id) {
2984
+ return this.http.request({ method: "GET", path: `${this.basePath}/${id}` });
2985
+ }
2986
+ };
2987
+
2002
2988
  // src/webhooks.ts
2003
2989
  var import_node_crypto2 = require("crypto");
2004
2990
  var WEBHOOK_EVENT_TYPES = [
@@ -2034,7 +3020,8 @@ var WEBHOOK_EVENT_TYPES = [
2034
3020
  "integration_connection.deactivated",
2035
3021
  "customer_payment_method.invalidated",
2036
3022
  "invoice.autopay_succeeded",
2037
- "invoice.autopay_failed"
3023
+ "invoice.autopay_failed",
3024
+ "received"
2038
3025
  ];
2039
3026
  function verifyWebhookSignature(payload, signature, secret) {
2040
3027
  if (!signature || !secret || !payload) {
@@ -2236,6 +3223,18 @@ var PlanesClient = class {
2236
3223
  }
2237
3224
  return this._tags;
2238
3225
  }
3226
+ get lists() {
3227
+ if (!this._lists) {
3228
+ this._lists = new ListsResource(this.http);
3229
+ }
3230
+ return this._lists;
3231
+ }
3232
+ get leadSources() {
3233
+ if (!this._leadSources) {
3234
+ this._leadSources = new LeadSourcesResource(this.http);
3235
+ }
3236
+ return this._leadSources;
3237
+ }
2239
3238
  get tasks() {
2240
3239
  if (!this._tasks) {
2241
3240
  this._tasks = new TaskResource(this.http);
@@ -2291,6 +3290,20 @@ var PlanesClient = class {
2291
3290
  }
2292
3291
  return this._invoiceReminders;
2293
3292
  }
3293
+ /** Event automations, admin only — `eventAutomations.list/get/create/update/delete` (E60-S2). */
3294
+ get eventAutomations() {
3295
+ if (!this._eventAutomations) {
3296
+ this._eventAutomations = new EventAutomationsResource(this.http);
3297
+ }
3298
+ return this._eventAutomations;
3299
+ }
3300
+ /** Web forms and their submissions, admin only — `webForms.list/get/create/update/archive/rotateKey`, `webForms.submissions.list/get/update/route` (E60-S6). */
3301
+ get webForms() {
3302
+ if (!this._webForms) {
3303
+ this._webForms = new WebFormsResource(this.http);
3304
+ }
3305
+ return this._webForms;
3306
+ }
2294
3307
  /** Published sequences a contact can be enrolled in (E51-F2-S6). */
2295
3308
  get contactSequences() {
2296
3309
  if (!this._contactSequences) {
@@ -2341,6 +3354,30 @@ var PlanesClient = class {
2341
3354
  }
2342
3355
  return this._ortto;
2343
3356
  }
3357
+ /**
3358
+ * Webhook subscriptions — where entity events are delivered (E64-S10). Admin
3359
+ * grants and the `open_api` feature. Not `webhooks`: that is the event router.
3360
+ */
3361
+ get webhookSubscriptions() {
3362
+ if (!this._webhookSubscriptions) {
3363
+ this._webhookSubscriptions = new WebhookSubscriptionsResource(this.http);
3364
+ }
3365
+ return this._webhookSubscriptions;
3366
+ }
3367
+ /** The tenant's users, read-only: list and directory (E64-S10). */
3368
+ get users() {
3369
+ if (!this._users) {
3370
+ this._users = new UsersResource(this.http);
3371
+ }
3372
+ return this._users;
3373
+ }
3374
+ /** Teams, read-only (E64-S10). */
3375
+ get teams() {
3376
+ if (!this._teams) {
3377
+ this._teams = new TeamsResource(this.http);
3378
+ }
3379
+ return this._teams;
3380
+ }
2344
3381
  /**
2345
3382
  * Ergonomic webhook event router — `client.webhooks.on(pattern, handler)`
2346
3383
  * plus `client.webhooks.process({ body, signature, secret })`. Auth-independent
@@ -2380,14 +3417,14 @@ var BILLING_ADJUSTMENT_STATUSES = ["pending", "consumed", "cancelled"];
2380
3417
  var BILLING_ADJUSTMENT_TARGETS = ["next_invoice"];
2381
3418
 
2382
3419
  // src/types/contact-types.ts
2383
- var CONTACT_SORT_KEYS = ["name", "created_at", "recent"];
3420
+ var CONTACT_SORT_KEYS = ["name", "created_at", "recent", "updated_at"];
2384
3421
  var CONTACT_LIST_INCLUDES = ["channels", "links"];
2385
3422
 
2386
3423
  // src/types/customer-types.ts
2387
- var CUSTOMER_SORT_KEYS = ["created_at", "last_name", "first_name", "email", "phone", "source", "recent"];
3424
+ var CUSTOMER_SORT_KEYS = ["created_at", "last_name", "first_name", "email", "phone", "source", "recent", "updated_at"];
2388
3425
 
2389
3426
  // src/types/opportunity-types.ts
2390
- var OPPORTUNITY_SORT_KEYS = ["created_at", "total", "title", "customer_name", "stage", "priority", "recent"];
3427
+ var OPPORTUNITY_SORT_KEYS = ["created_at", "total", "title", "customer_name", "stage", "priority", "recent", "updated_at"];
2391
3428
  var OPPORTUNITY_STATES = ["open", "closed", "won", "lost"];
2392
3429
 
2393
3430
  // src/types/job-types.ts
@@ -2415,7 +3452,8 @@ var MODULE_KEYS = [
2415
3452
  "contacts",
2416
3453
  "tasks",
2417
3454
  "comments",
2418
- "conversations"
3455
+ "conversations",
3456
+ "web_forms"
2419
3457
  ];
2420
3458
 
2421
3459
  // src/types/plan-types.ts
@@ -2477,6 +3515,12 @@ var WORKFLOW_EVENT_RULE_EVENT_TYPES = [
2477
3515
  var WORKFLOW_EVENT_RULE_MATCH_ALL_EVENT_TYPES = [
2478
3516
  "calendar_event.created"
2479
3517
  ];
3518
+ var WORKFLOW_EVENT_RULE_CATCH_UP_EVENT_TYPES = [
3519
+ "calendar_event.created"
3520
+ ];
3521
+
3522
+ // src/types/task-types.ts
3523
+ var TASK_SORT_KEYS = ["due_at", "created_at", "updated_at"];
2480
3524
 
2481
3525
  // src/agent.ts
2482
3526
  var http = __toESM(require("http"));
@@ -2677,6 +3721,7 @@ function readBody(req) {
2677
3721
  ESTIMATE_STATUSES,
2678
3722
  EnrollmentsResource,
2679
3723
  EstimateResource,
3724
+ EventAutomationsResource,
2680
3725
  FEATURE_KEYS,
2681
3726
  GmailResource,
2682
3727
  InventoryResource,
@@ -2685,6 +3730,8 @@ function readBody(req) {
2685
3730
  InvoiceSettingsResource,
2686
3731
  JOB_SORT_KEYS,
2687
3732
  JobResource,
3733
+ LeadSourcesResource,
3734
+ ListsResource,
2688
3735
  MODULE_KEYS,
2689
3736
  MeResource,
2690
3737
  MemoryTokenStorage,
@@ -2708,13 +3755,20 @@ function readBody(req) {
2708
3755
  RolesResource,
2709
3756
  ServerError,
2710
3757
  ServiceSchedulesResource,
3758
+ TASK_SORT_KEYS,
2711
3759
  TagResource,
2712
3760
  TaskResource,
3761
+ TeamsResource,
3762
+ UsersResource,
2713
3763
  ValidationError,
2714
3764
  WEBHOOK_EVENT_TYPES,
3765
+ WORKFLOW_EVENT_RULE_CATCH_UP_EVENT_TYPES,
2715
3766
  WORKFLOW_EVENT_RULE_EVENT_TYPES,
2716
3767
  WORKFLOW_EVENT_RULE_MATCH_ALL_EVENT_TYPES,
3768
+ WebFormSubmissionsResource,
3769
+ WebFormsResource,
2717
3770
  WebhookRouter,
3771
+ WebhookSubscriptionsResource,
2718
3772
  WebhookVerificationError,
2719
3773
  WorkflowEventRulesResource,
2720
3774
  WorkflowsResource,