@unboundcx/sdk 4.6.0 → 4.6.2

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/README.md CHANGED
@@ -175,6 +175,41 @@ await api.objects.updateById('contacts', 'contact-123', { name: 'Jane' });
175
175
  await api.objects.deleteById('contacts', 'contact-123');
176
176
  await api.objects.describe('contacts'); // Get schema
177
177
  await api.objects.list(); // List all object types
178
+
179
+ // Skip trigger execution on a write (imports / bulk tools)
180
+ await api.objects.updateById({
181
+ object: 'people',
182
+ id: '013…',
183
+ update: { leadScore: 200 },
184
+ skipTriggers: true,
185
+ });
186
+ ```
187
+
188
+ #### Triggers (`api.triggers`)
189
+
190
+ ```javascript
191
+ await api.triggers.listObjects();
192
+ await api.triggers.list({ objectName: 'people', status: 'enabled' });
193
+ await api.triggers.create({
194
+ name: 'Hot lead',
195
+ objectName: 'people',
196
+ actions: ['update'],
197
+ recordFilter: { type: { op: 'eq', value: 'lead' } },
198
+ changeFilters: [
199
+ {
200
+ field: 'leadScore',
201
+ previous: { op: 'lt', value: 20 },
202
+ updated: { op: 'gt', value: 100 },
203
+ },
204
+ ],
205
+ actionType: 'workflow',
206
+ actionConfig: { workflowVersionId: '052…' },
207
+ });
208
+ await api.triggers.get('173…');
209
+ await api.triggers.update('173…', { status: 'paused' });
210
+ await api.triggers.setStatus('173…', 'enabled');
211
+ await api.triggers.listFires('173…', { limit: 20 });
212
+ await api.triggers.delete('173…');
178
213
  ```
179
214
 
180
215
  #### Live Queries (`api.objects.liveQuery`)
@@ -409,7 +444,6 @@ const fileUrl = api.storage.getFileUrl(files[0].storageId);
409
444
  await api.storage.deleteFile(files[0].storageId);
410
445
  ```
411
446
 
412
-
413
447
  #### Documents (`api.documents`)
414
448
 
415
449
  Generic templates → PDF. Implementation: `services/documents.js`.
@@ -419,7 +453,9 @@ Generic templates → PDF. Implementation: `services/documents.js`.
419
453
  const created = await api.documents.templates.create({
420
454
  name: 'Fax cover',
421
455
  engine: 'generative', // or 'overlay' + sourcePdfStorageId
456
+ uses: ['fax'], // omit / [] = every surface
422
457
  });
458
+ await api.documents.templates.list({ status: 'published', use: 'fax' });
423
459
  await api.documents.templates.update(created.id, { draftSchemaJson, draftLayoutJson });
424
460
  await api.documents.templates.publish(created.id);
425
461
 
package/index.js CHANGED
@@ -29,6 +29,7 @@ import { KnowledgeBaseService } from './services/knowledgeBase.js';
29
29
  import { FaxService } from './services/fax.js';
30
30
  import { DocumentsService } from './services/documents.js';
31
31
  import { PermissionsService } from './services/permissions.js';
32
+ import { TriggersService } from './services/triggers.js';
32
33
 
33
34
  class UnboundSDK extends BaseSDK {
34
35
  constructor(options = {}) {
@@ -99,6 +100,7 @@ class UnboundSDK extends BaseSDK {
99
100
  this.fax = new FaxService(this);
100
101
  this.documents = new DocumentsService(this);
101
102
  this.permissions = new PermissionsService(this);
103
+ this.triggers = new TriggersService(this);
102
104
 
103
105
  // Add additional services that might be missing
104
106
  this._initializeAdditionalServices();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboundcx/sdk",
3
- "version": "4.6.0",
3
+ "version": "4.6.2",
4
4
  "description": "Official JavaScript SDK for the Unbound API - A comprehensive toolkit for integrating with Unbound's communication, AI, and data management services",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -9,6 +9,7 @@ export class DocumentTemplatesService {
9
9
  * @param {string} params.name - Template name (required)
10
10
  * @param {string} [params.description]
11
11
  * @param {string} [params.tag]
12
+ * @param {string[]} [params.uses] - Consumer surfaces (`fax`). Empty = all.
12
13
  * @param {'generative'|'overlay'} [params.engine='generative']
13
14
  * @param {string} [params.sourcePdfStorageId] - Required when engine is overlay
14
15
  * @param {Object} [params.draftSchemaJson]
@@ -21,6 +22,7 @@ export class DocumentTemplatesService {
21
22
  name,
22
23
  description,
23
24
  tag,
25
+ uses,
24
26
  engine,
25
27
  sourcePdfStorageId,
26
28
  draftSchemaJson,
@@ -34,6 +36,7 @@ export class DocumentTemplatesService {
34
36
  name: { type: 'string', required: true },
35
37
  description: { type: 'string', required: false },
36
38
  tag: { type: 'string', required: false },
39
+ uses: { type: 'object', required: false },
37
40
  engine: { type: 'string', required: false },
38
41
  sourcePdfStorageId: { type: 'string', required: false },
39
42
  draftSchemaJson: { type: 'object', required: false },
@@ -46,6 +49,7 @@ export class DocumentTemplatesService {
46
49
  const body = { name };
47
50
  if (description !== undefined) body.description = description;
48
51
  if (tag !== undefined) body.tag = tag;
52
+ if (uses !== undefined) body.uses = uses;
49
53
  if (engine !== undefined) body.engine = engine;
50
54
  if (sourcePdfStorageId !== undefined) {
51
55
  body.sourcePdfStorageId = sourcePdfStorageId;
@@ -63,13 +67,15 @@ export class DocumentTemplatesService {
63
67
  * @param {Object} [params]
64
68
  * @param {string} [params.tag]
65
69
  * @param {string} [params.status]
70
+ * @param {string} [params.use] - Consumer surface (`fax`). Empty uses match all.
66
71
  * @param {number} [params.limit]
67
72
  * @returns {Promise<{results: Object[]}>}
68
73
  */
69
- async list({ tag, status, limit } = {}) {
74
+ async list({ tag, status, use, limit } = {}) {
70
75
  const query = {};
71
76
  if (tag) query.tag = tag;
72
77
  if (status) query.status = status;
78
+ if (use) query.use = use;
73
79
  if (limit) query.limit = limit;
74
80
  return this.sdk._fetch('/documents/templates', 'GET', { query });
75
81
  }
@@ -91,6 +97,7 @@ export class DocumentTemplatesService {
91
97
  * @param {string} [params.name]
92
98
  * @param {string} [params.description]
93
99
  * @param {string} [params.tag]
100
+ * @param {string[]} [params.uses]
94
101
  * @param {Object} [params.draftSchemaJson]
95
102
  * @param {Object} [params.draftLayoutJson]
96
103
  * @param {Object} [params.draftPageJson]
@@ -103,6 +110,8 @@ export class DocumentTemplatesService {
103
110
  name,
104
111
  description,
105
112
  tag,
113
+ uses,
114
+ recordTypeId,
106
115
  draftSchemaJson,
107
116
  draftLayoutJson,
108
117
  draftPageJson,
@@ -116,6 +125,8 @@ export class DocumentTemplatesService {
116
125
  name: { type: 'string', required: false },
117
126
  description: { type: 'string', required: false },
118
127
  tag: { type: 'string', required: false },
128
+ uses: { type: 'object', required: false },
129
+ recordTypeId: { type: 'string', required: false },
119
130
  draftSchemaJson: { type: 'object', required: false },
120
131
  draftLayoutJson: { type: 'object', required: false },
121
132
  draftPageJson: { type: 'object', required: false },
@@ -127,6 +138,8 @@ export class DocumentTemplatesService {
127
138
  if (name !== undefined) body.name = name;
128
139
  if (description !== undefined) body.description = description;
129
140
  if (tag !== undefined) body.tag = tag;
141
+ if (uses !== undefined) body.uses = uses;
142
+ if (recordTypeId !== undefined) body.recordTypeId = recordTypeId;
130
143
  if (draftSchemaJson !== undefined) body.draftSchemaJson = draftSchemaJson;
131
144
  if (draftLayoutJson !== undefined) body.draftLayoutJson = draftLayoutJson;
132
145
  if (draftPageJson !== undefined) body.draftPageJson = draftPageJson;
@@ -408,7 +408,7 @@ export class PermissionsService {
408
408
  return this.sdk._fetch(
409
409
  `/permissions/groups/${groupId}/settings/${settingKey}`,
410
410
  'PUT',
411
- { value },
411
+ { body: { value } },
412
412
  );
413
413
  }
414
414
 
@@ -436,7 +436,7 @@ export class PermissionsService {
436
436
  async setGroupPriority(order) {
437
437
  this.sdk.validateParams({ order }, { order: { type: 'array', required: true } });
438
438
  return this.sdk._fetch('/permissions/groups/priority', 'PUT', {
439
- order: order.map(String),
439
+ body: { order: order.map(String) },
440
440
  });
441
441
  }
442
442
 
@@ -465,7 +465,7 @@ export class PermissionsService {
465
465
  return this.sdk._fetch(
466
466
  `/permissions/users/${userId}/settings/${settingKey}`,
467
467
  'PUT',
468
- { value },
468
+ { body: { value } },
469
469
  );
470
470
  }
471
471
 
@@ -485,4 +485,132 @@ export class PermissionsService {
485
485
  'DELETE',
486
486
  );
487
487
  }
488
+
489
+ // ---- Group-assigned skills and queues (§9.5) --------------------------
490
+ // Set-shaped, not scalar: they union across every group a user belongs to
491
+ // and never consult group priority. Writes fan out to each member's
492
+ // materialized userSkills/queueUsers rows and re-push their live worker.
493
+ //
494
+ // NOTE for anyone adding a method here: `_fetch`'s third argument is the
495
+ // params envelope — a request body MUST be passed as `{ body: {...} }`.
496
+ // Passing the payload bare silently sends no body at all.
497
+
498
+ /** Skill + queue vocabulary for the group assignment pickers. */
499
+ async getTaskRoutingCatalog() {
500
+ return this.sdk._fetch('/permissions/task-routing/catalog', 'GET');
501
+ }
502
+
503
+ /** Skills this group grants. @returns {Promise<{results: Array<{skillId}>}>} */
504
+ async listGroupSkills(groupId) {
505
+ groupId = String(groupId);
506
+ this.sdk.validateParams(
507
+ { groupId },
508
+ { groupId: { type: 'string', required: true } },
509
+ );
510
+ return this.sdk._fetch(`/permissions/groups/${groupId}/skills`, 'GET');
511
+ }
512
+
513
+ /** Replace the group's full skill list. */
514
+ async setGroupSkills(groupId, skillIds) {
515
+ groupId = String(groupId);
516
+ this.sdk.validateParams(
517
+ { groupId, skillIds },
518
+ {
519
+ groupId: { type: 'string', required: true },
520
+ skillIds: { type: 'array', required: true },
521
+ },
522
+ );
523
+ return this.sdk._fetch(`/permissions/groups/${groupId}/skills`, 'PUT', {
524
+ body: { skillIds: skillIds.map(String) },
525
+ });
526
+ }
527
+
528
+ /** Queues this group grants. @returns {Promise<{results: Array<{queueId, access, autoLogin}>}>} */
529
+ async listGroupQueues(groupId) {
530
+ groupId = String(groupId);
531
+ this.sdk.validateParams(
532
+ { groupId },
533
+ { groupId: { type: 'string', required: true } },
534
+ );
535
+ return this.sdk._fetch(`/permissions/groups/${groupId}/queues`, 'GET');
536
+ }
537
+
538
+ /**
539
+ * Replace the group's full queue list.
540
+ * @param {Array<{queueId: string, access?: boolean, autoLogin?: boolean}>} queues
541
+ * `autoLogin` seeds the derived membership row and takes effect at the
542
+ * agent's next availability transition — it never logs anyone in
543
+ * mid-session.
544
+ */
545
+ async setGroupQueues(groupId, queues) {
546
+ groupId = String(groupId);
547
+ this.sdk.validateParams(
548
+ { groupId, queues },
549
+ {
550
+ groupId: { type: 'string', required: true },
551
+ queues: { type: 'array', required: true },
552
+ },
553
+ );
554
+ return this.sdk._fetch(`/permissions/groups/${groupId}/queues`, 'PUT', {
555
+ body: {
556
+ queues: queues.map((q) => ({
557
+ queueId: String(q.queueId),
558
+ access: q.access !== false,
559
+ autoLogin: Boolean(q.autoLogin),
560
+ })),
561
+ },
562
+ });
563
+ }
564
+
565
+ /**
566
+ * A user's effective skills/queues with the source of each row, so the UI
567
+ * can show "granted by group X" instead of offering a control that would
568
+ * silently no-op.
569
+ */
570
+ async getUserTaskRouting(userId) {
571
+ userId = String(userId);
572
+ this.sdk.validateParams(
573
+ { userId },
574
+ { userId: { type: 'string', required: true } },
575
+ );
576
+ return this.sdk._fetch(`/permissions/users/${userId}/task-routing`, 'GET');
577
+ }
578
+
579
+ /** Exclude one group-granted skill/queue from this user ("in Sales but not queue X"). */
580
+ async addTaskRoutingExclusion(userId, settingKey, value) {
581
+ userId = String(userId);
582
+ settingKey = String(settingKey);
583
+ value = String(value);
584
+ this.sdk.validateParams(
585
+ { userId, settingKey, value },
586
+ {
587
+ userId: { type: 'string', required: true },
588
+ settingKey: { type: 'string', required: true },
589
+ value: { type: 'string', required: true },
590
+ },
591
+ );
592
+ return this.sdk._fetch(
593
+ `/permissions/users/${userId}/task-routing/exclusions/${settingKey}/${value}`,
594
+ 'PUT',
595
+ );
596
+ }
597
+
598
+ /** Drop an exclusion, letting the group grant apply again. */
599
+ async removeTaskRoutingExclusion(userId, settingKey, value) {
600
+ userId = String(userId);
601
+ settingKey = String(settingKey);
602
+ value = String(value);
603
+ this.sdk.validateParams(
604
+ { userId, settingKey, value },
605
+ {
606
+ userId: { type: 'string', required: true },
607
+ settingKey: { type: 'string', required: true },
608
+ value: { type: 'string', required: true },
609
+ },
610
+ );
611
+ return this.sdk._fetch(
612
+ `/permissions/users/${userId}/task-routing/exclusions/${settingKey}/${value}`,
613
+ 'DELETE',
614
+ );
615
+ }
488
616
  }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Triggers Service — object-change automations (workflow or outbound webhook).
3
+ *
4
+ * Watch create/update/delete on a trigger-enabled object, filter on the
5
+ * current row and/or the field that changed, then run a workflow session
6
+ * or POST to a URL.
7
+ *
8
+ * @example
9
+ * const { results } = await sdk.triggers.list({ objectName: 'people' });
10
+ *
11
+ * @example
12
+ * await sdk.triggers.create({
13
+ * name: 'Hot lead',
14
+ * objectName: 'people',
15
+ * actions: ['update'],
16
+ * recordFilter: { type: { op: 'eq', value: 'lead' } },
17
+ * changeFilters: [{
18
+ * field: 'leadScore',
19
+ * previous: { op: 'lt', value: 20 },
20
+ * updated: { op: 'gt', value: 100 },
21
+ * }],
22
+ * actionType: 'workflow',
23
+ * actionConfig: { workflowVersionId: '052…' },
24
+ * });
25
+ */
26
+ export class TriggersService {
27
+ constructor(sdk) {
28
+ this.sdk = sdk;
29
+ }
30
+
31
+ /**
32
+ * List objects that may have triggers (`objectMetaData.triggersEnabled`).
33
+ *
34
+ * @returns {Promise<{results: Array<{id: string, name: string}>}>}
35
+ *
36
+ * @example
37
+ * const { results } = await sdk.triggers.listObjects();
38
+ */
39
+ async listObjects() {
40
+ return this.sdk._fetch('/triggers/objects', 'GET', {});
41
+ }
42
+
43
+ /**
44
+ * List triggers for the account.
45
+ *
46
+ * @param {object} [args]
47
+ * @param {string} [args.objectName] - Filter to one object (e.g. `'people'`)
48
+ * @param {('enabled'|'paused'|'disabled')} [args.status]
49
+ * @param {number} [args.limit=200]
50
+ * @returns {Promise<{results: object[]}>}
51
+ *
52
+ * @example
53
+ * await sdk.triggers.list({ objectName: 'people', status: 'enabled' });
54
+ */
55
+ async list({ objectName, status, limit } = {}) {
56
+ const query = {};
57
+ if (objectName) query.objectName = objectName;
58
+ if (status) query.status = status;
59
+ if (limit) query.limit = limit;
60
+ return this.sdk._fetch('/triggers/', 'GET', { query });
61
+ }
62
+
63
+ /**
64
+ * Get a trigger by id.
65
+ *
66
+ * @param {string} id
67
+ * @returns {Promise<object>}
68
+ *
69
+ * @example
70
+ * const trigger = await sdk.triggers.get('173…');
71
+ */
72
+ async get(id) {
73
+ this.sdk.validateParams({ id }, { id: { type: 'string', required: true } });
74
+ return this.sdk._fetch(`/triggers/${id}`, 'GET', {});
75
+ }
76
+
77
+ /**
78
+ * Create a trigger.
79
+ *
80
+ * @param {object} args
81
+ * @param {string} args.name
82
+ * @param {string} [args.description]
83
+ * @param {string} args.objectName - Must have `triggersEnabled` (people, company, opportunities, projects, or custom `__c`)
84
+ * @param {Array<'create'|'update'|'delete'>} args.actions
85
+ * @param {('enabled'|'paused'|'disabled')} [args.status='enabled']
86
+ * @param {Object<string, {op: string, value: *}|*>} [args.recordFilter] - Current-row match (new row on create/update, old on delete)
87
+ * @param {Array<{field: string, previous?: {op: string, value: *}, updated?: {op: string, value: *}}>} [args.changeFilters] - Field must be in `changedFields`; optional previous/updated checks
88
+ * @param {('workflow'|'webhook')} args.actionType
89
+ * @param {object} args.actionConfig
90
+ * @param {string} [args.actionConfig.workflowVersionId] - Required when `actionType` is `'workflow'`
91
+ * @param {string} [args.actionConfig.workflowId]
92
+ * @param {string[]} [args.actionConfig.includeValues] - Field values to include (previous + updated). Empty = names only. `['*']` = every non-encrypted field. Encrypted columns are never sent.
93
+ * @param {string} [args.actionConfig.primaryUrl] - Required when `actionType` is `'webhook'`; must be `https://`
94
+ * @param {string} [args.actionConfig.secondaryUrl] - Failover URL; must be `https://` if set
95
+ * @param {string} [args.actionConfig.credentialId] - Stored webhook authorization id
96
+ * @param {number} [args.timeoutMinutes=15] - Queued-fire TTL, 1–1440
97
+ * @param {number} [args.retries=3] - Webhook retries, 0–5
98
+ * @returns {Promise<object>} Created trigger
99
+ *
100
+ * @example
101
+ * await sdk.triggers.create({
102
+ * name: 'Notify CRM',
103
+ * objectName: 'people',
104
+ * actions: ['update'],
105
+ * actionType: 'webhook',
106
+ * actionConfig: {
107
+ * primaryUrl: 'https://example.com/hooks/lead',
108
+ * secondaryUrl: 'https://backup.example.com/hooks/lead',
109
+ * includeValues: ['leadScore', 'email'],
110
+ * },
111
+ * timeoutMinutes: 15,
112
+ * retries: 3,
113
+ * });
114
+ */
115
+ async create({
116
+ name,
117
+ description,
118
+ objectName,
119
+ actions,
120
+ status,
121
+ recordFilter,
122
+ changeFilters,
123
+ actionType,
124
+ actionConfig,
125
+ timeoutMinutes,
126
+ retries,
127
+ recordTypeId,
128
+ }) {
129
+ const body = {
130
+ name,
131
+ description,
132
+ objectName,
133
+ actions,
134
+ status,
135
+ recordFilter,
136
+ changeFilters,
137
+ actionType,
138
+ actionConfig,
139
+ timeoutMinutes,
140
+ retries,
141
+ recordTypeId,
142
+ };
143
+
144
+ this.sdk.validateParams(
145
+ { name, objectName, actions, actionType, actionConfig },
146
+ {
147
+ name: { type: 'string', required: true },
148
+ objectName: { type: 'string', required: true },
149
+ actions: { type: 'object', required: true },
150
+ actionType: { type: 'string', required: true },
151
+ actionConfig: { type: 'object', required: true },
152
+ },
153
+ );
154
+
155
+ return this.sdk._fetch('/triggers/', 'POST', { body });
156
+ }
157
+
158
+ /**
159
+ * Update a trigger. Only provided fields are changed.
160
+ *
161
+ * @param {string} id
162
+ * @param {object} args - Same shape as {@link TriggersService#create}; all keys optional
163
+ * @returns {Promise<object>} Updated trigger
164
+ *
165
+ * @example
166
+ * await sdk.triggers.update('173…', { status: 'paused' });
167
+ */
168
+ async update(id, args = {}) {
169
+ this.sdk.validateParams(
170
+ { id, args },
171
+ {
172
+ id: { type: 'string', required: true },
173
+ args: { type: 'object', required: true },
174
+ },
175
+ );
176
+ return this.sdk._fetch(`/triggers/${id}`, 'PUT', { body: args });
177
+ }
178
+
179
+ /**
180
+ * Soft-delete a trigger.
181
+ *
182
+ * @param {string} id
183
+ * @returns {Promise<{id: string, deleted: boolean}>}
184
+ *
185
+ * @example
186
+ * await sdk.triggers.delete('173…');
187
+ */
188
+ async delete(id) {
189
+ this.sdk.validateParams({ id }, { id: { type: 'string', required: true } });
190
+ return this.sdk._fetch(`/triggers/${id}`, 'DELETE', {});
191
+ }
192
+
193
+ /**
194
+ * Alias of {@link TriggersService#delete}.
195
+ *
196
+ * @param {string} id
197
+ * @returns {Promise<{id: string, deleted: boolean}>}
198
+ */
199
+ async remove(id) {
200
+ return this.delete(id);
201
+ }
202
+
203
+ /**
204
+ * Set trigger status without a full update.
205
+ *
206
+ * @param {string} id
207
+ * @param {('enabled'|'paused'|'disabled')} status
208
+ * @param {object} [opts]
209
+ * @param {string} [opts.pausedReason] - Stored when `status` is `'paused'`
210
+ * @returns {Promise<object>} Updated trigger
211
+ *
212
+ * @example
213
+ * await sdk.triggers.setStatus('173…', 'paused', { pausedReason: 'rate limit' });
214
+ */
215
+ async setStatus(id, status, { pausedReason } = {}) {
216
+ this.sdk.validateParams(
217
+ { id, status },
218
+ {
219
+ id: { type: 'string', required: true },
220
+ status: { type: 'string', required: true },
221
+ },
222
+ );
223
+ return this.sdk._fetch(`/triggers/${id}/status`, 'POST', {
224
+ body: { status, pausedReason },
225
+ });
226
+ }
227
+
228
+ /**
229
+ * Recent execution log for a trigger (queued / fired / dropped / rejected / timeout).
230
+ *
231
+ * @param {string} id
232
+ * @param {object} [opts]
233
+ * @param {number} [opts.limit=50]
234
+ * @returns {Promise<{results: object[]}>}
235
+ *
236
+ * @example
237
+ * const { results } = await sdk.triggers.listFires('173…', { limit: 20 });
238
+ */
239
+ async listFires(id, { limit } = {}) {
240
+ this.sdk.validateParams({ id }, { id: { type: 'string', required: true } });
241
+ const query = {};
242
+ if (limit) query.limit = limit;
243
+ return this.sdk._fetch(`/triggers/${id}/fires`, 'GET', { query });
244
+ }
245
+ }
@@ -41,7 +41,7 @@ async function testPublicSDKCompleteness() {
41
41
  'phoneNumbers',
42
42
  'recordTypes',
43
43
  'generateId',
44
- 'documents',
44
+ 'triggers',
45
45
  ];
46
46
 
47
47
  console.log(`📊 Checking ${publicServices.length} public services...`);