@signalhousellc/sdk 1.4.2 → 1.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signalhousellc/sdk",
3
- "version": "1.4.2",
3
+ "version": "1.5.0",
4
4
  "description": "Signal House SDK for use with the Signal House platform",
5
5
  "type": "module",
6
6
  "main": "src/SignalHouseSDK.js",
@@ -32,6 +32,7 @@ import { Campaigns } from "./domains/Campaigns.js";
32
32
  import { Groups } from "./domains/Groups.js";
33
33
  import { Landings } from "./domains/Landings.js";
34
34
  import { Messages } from "./domains/Messages.js";
35
+ import { Templates } from "./domains/Templates.js";
35
36
  import { Numbers } from "./domains/Numbers.js";
36
37
  import { Registrations } from "./domains/Registrations.js";
37
38
  import { Onboarding } from "./domains/Onboarding.js";
@@ -78,6 +79,7 @@ export class SignalHouseSDK {
78
79
  this.groups = new Groups(client, this.enableAdmin);
79
80
  this.landings = new Landings(client, multipartClient, this.enableAdmin);
80
81
  this.messages = new Messages(client, multipartClient, this.enableAdmin);
82
+ this.templates = new Templates(client, multipartClient, this.enableAdmin);
81
83
  this.numbers = new Numbers(client, multipartClient, this.enableAdmin);
82
84
  this.registrations = new Registrations(client, this.enableAdmin);
83
85
  this.onboarding = new Onboarding(client, this.enableAdmin);
@@ -80,6 +80,29 @@
80
80
  * @property {string} [businessWebsite] - The customer's real website, sent to Infobip for brand verification (optional, valid URL, at most 255 characters; distinct from the top-level website / landing URL)
81
81
  */
82
82
 
83
+ /**
84
+ * @typedef {Object} BusinessVerificationDetails
85
+ * @property {string} businessName - The legal business name (required, at most 255 characters)
86
+ * @property {string} businessType - The business type (required, one of CO_OPERATIVE, CORPORATION, LIMITED_LIABILITY_CORPORATION, NON_PROFIT_CORPORATION, PARTNERSHIP)
87
+ * @property {string} industry - The business industry (required, one of AGRICULTURE, AUTOMOTIVE, BANKING, CONSTRUCTION, CONSUMER, EDUCATION, ELECTRONICS, ENGINEERING, ENERGY, FAST_MOVING_CONSUMER_GOODS, FINANCIAL, FINTECH, FOOD_AND_BEVERAGE, GOVERNMENT, HEALTHCARE, HOSPITALITY, INSURANCE, JEWELRY, LEGAL, MANUFACTURING, MEDIA, NOT_FOR_PROFIT, OIL_AND_GAS, ONLINE, PROFESSIONAL_SERVICES, RAW_MATERIALS, REAL_ESTATE, RELIGION, RETAIL, TECHNOLOGY, TELECOMMUNICATIONS, TRANSPORTATION, TRAVEL)
88
+ * @property {string} registrationType - The business registration type (required, one of EIN, CBN, NEQ, PROVINCIAL_NUMBER, CRN, VAT, ACN, ABN, BRN, SIREN, SIRET, NZBN, UST_IDNR, CIF, NIF, CNPJ, UID, OTHER)
89
+ * @property {string} registrationNumber - The business registration number, e.g. the EIN (required, at most 50 characters)
90
+ * @property {string} website - The business website (required, valid URL, at most 255 characters)
91
+ * @property {string} street - The business street address (required, at most 255 characters)
92
+ * @property {string} [street2] - The second line of the street address (optional, at most 255 characters)
93
+ * @property {string} city - The business city (required, at most 100 characters)
94
+ * @property {string} state - The business state or province (required, 2-letter code)
95
+ * @property {string} postalCode - The business postal code (required, at most 10 characters)
96
+ * @property {string} country - The business country (required, "US" or "CA")
97
+ * @property {Object} representative - The business's authorized representative (required)
98
+ * @property {string} representative.firstName - The representative's first name (required, at most 100 characters)
99
+ * @property {string} representative.lastName - The representative's last name (required, at most 100 characters)
100
+ * @property {string} representative.email - The representative's email address (required, valid email, at most 100 characters)
101
+ * @property {string} representative.phone - The representative's phone number (required, E.164 with an optional leading "+" and no spaces, parentheses or hyphens, e.g. "+17025550100")
102
+ * @property {string} representative.jobTitle - The representative's job title (required, at most 100 characters)
103
+ * @property {string} representative.jobLevel - The representative's job level (required, one of DIRECTOR, GM, VP, CEO, CFO, GENERAL_COUNSEL, OTHER)
104
+ */
105
+
83
106
  /**
84
107
  * @typedef {Object} CreateTollFreeBrandData
85
108
  * @property {string} subgroupId - The ID of the subgroup to create the brand under (required, exactly 8 characters)
@@ -140,6 +163,7 @@ export class Brands {
140
163
  * @param {number} [params.limit] - The number of items per page
141
164
  * @param {string} [params.status] - The status of the brand to filter by (PENDING_CREATION, PENDING_APPROVAL, UNVERIFIED, VERIFIED, VETTED_VERIFIED, PENDING_DELETE, DELETED)
142
165
  * @param {string} [params.registrationType] - Filter by registration type ("TEN_DLC", "TOLL_FREE", or "SHORT_CODE")
166
+ * @param {boolean} [params.currentTollFreeCarrier] - With subgroupId or groupId: when true, return only toll-free brands that a new toll-free campaign in that group can use (those on the group's current toll-free carrier)
143
167
  * @param {string} [params.search] - Staff-only (no id/subgroupId/groupId): case-insensitive substring over brandId, names, EIN, email, group, subgroup and reference ID
144
168
  * @param {string} [params.sortBy] - Staff-only: createdAt, brandId, displayName, groupId, subgroupId, status, entityType or country
145
169
  * @param {"asc"|"desc"} [params.sortOrder] - Staff-only: sort direction (default asc)
@@ -153,7 +177,7 @@ export class Brands {
153
177
  * @param {boolean} [params.excludeDeleted] - Staff-only: hide DELETED brands when no status is given
154
178
  * @param {boolean} [params.includeTotal] - Staff-only: with page and limit, return `{ data, total, page, limit, totalPages }` instead of the array
155
179
  * @param {RequestOptions} [params.options] - Additional options for the request
156
- * @returns {Promise<SdkResponse<Array<{ _id: string, brandId: string|null, status: string, registrationType: string, [key: string]: any }>|{ data: Array<Object>, total: number, page: number, limit: number, totalPages: number }>>} Brand records. For 10DLC brands in PENDING_CREATION (or rejected before carrier assignment), brandId is null — poll by _id until status advances and brandId is assigned. The staff-only list (no id/subgroupId/groupId) returns the `{ data, total, page, limit, totalPages }` envelope only when includeTotal is sent with page and limit.
180
+ * @returns {Promise<SdkResponse<Array<{ _id: string, brandId: string|null, status: string, registrationType: string, tollFree?: { isBrandReady?: boolean, maxNumbersPerCampaign?: number, maxOptInMethods?: number, requiresPrivacyPolicy?: boolean, requiresTermsAndConditions?: boolean, maxAppealReasonLength?: number, labelsSamples?: boolean, supportsMms?: boolean, usesBusinessVerification?: boolean, [key: string]: any }, [key: string]: any }>|{ data: Array<Object>, total: number, page: number, limit: number, totalPages: number }>>} Brand records. For 10DLC brands in PENDING_CREATION (or rejected before carrier assignment), brandId is null — poll by _id until status advances and brandId is assigned. Toll-free brands carry `tollFree.isBrandReady` (whether the brand can host campaigns), `tollFree.maxNumbersPerCampaign` (the most numbers one of its campaigns can hold), `tollFree.maxOptInMethods` (the most opt-in methods one of its campaigns can declare), `tollFree.requiresPrivacyPolicy` (whether its campaigns need a privacy policy URL), `tollFree.requiresTermsAndConditions` (whether its campaigns need a terms and conditions URL), `tollFree.maxAppealReasonLength` (the longest appeal reason its campaigns may give), `tollFree.labelsSamples` (whether its campaigns' sample messages each start with their use case's name), `tollFree.supportsMms` (whether its numbers can send MMS and group messages) and `tollFree.usesBusinessVerification` (whether it waits on the group's business verification). The staff-only list (no id/subgroupId/groupId) returns the `{ data, total, page, limit, totalPages }` envelope only when includeTotal is sent with page and limit.
157
181
  */
158
182
  async getBrands({
159
183
  id,
@@ -163,6 +187,7 @@ export class Brands {
163
187
  limit,
164
188
  status,
165
189
  registrationType,
190
+ currentTollFreeCarrier,
166
191
  search,
167
192
  sortBy,
168
193
  sortOrder,
@@ -185,6 +210,7 @@ export class Brands {
185
210
  limit,
186
211
  status,
187
212
  registrationType,
213
+ currentTollFreeCarrier,
188
214
  search,
189
215
  sortBy,
190
216
  sortOrder,
@@ -248,6 +274,38 @@ export class Brands {
248
274
  return this.client(`/brand/toll-free`, { method: 'POST', body: brandData, ...options });
249
275
  }
250
276
 
277
+ /**
278
+ * Get a group's business verification: whether its toll-free brands need one, its status and the submitted details.
279
+ * @async
280
+ * @roles api, admin, developer, billing, user
281
+ * @param {Object} params - The parameters for getting the business verification
282
+ * @param {string} params.groupId - The ID of the group whose business verification to get
283
+ * @param {RequestOptions} [params.options] - Additional options for the request
284
+ * @throws {Error} Throws an error if the groupId parameter is missing
285
+ * @returns {Promise<SdkResponse<{ groupId: string, required: boolean, status: string, details: BusinessVerificationDetails|null, submittedAt: string|null, reviewedAt: string|null, rejectionReason: string|null }>>} The group's business verification. `required` says whether the group's new toll-free brands need one; `status` is NOT_STARTED, SUBMITTING, IN_REVIEW, VERIFIED or NEEDS_CHANGES; `rejectionReason` says why it needs changes, or is null. When `required` is true and `status` is NOT_STARTED, submit it before creating the group's first toll-free brand.
286
+ */
287
+ async getBusinessVerification({ groupId, options = {} }) {
288
+ this.client._require({ groupId });
289
+ return this.client(`/brand/business-verification/${encodeURIComponent(groupId)}`, { method: 'GET', ...options });
290
+ }
291
+
292
+ /**
293
+ * Submit or update a group's business verification. Allowed when it is not started or needs changes. Review usually
294
+ * takes 1–2 business days; the group's toll-free brands stay pending until it is verified.
295
+ * @async
296
+ * @roles api, admin, developer, billing, user
297
+ * @param {Object} params - The parameters for submitting the business verification
298
+ * @param {string} params.groupId - The ID of the group whose business verification to submit (must be the caller's active group)
299
+ * @param {BusinessVerificationDetails} params.details - The business verification details (see BusinessVerificationDetails typedef for details)
300
+ * @param {RequestOptions} [params.options] - Additional options for the request
301
+ * @throws {Error} Throws an error if the groupId or details parameter is missing
302
+ * @returns {Promise<SdkResponse<{ groupId: string, required: boolean, status: string, details: BusinessVerificationDetails|null, submittedAt: string|null, reviewedAt: string|null, rejectionReason: string|null }>>} A 202 with the group's business verification, now SUBMITTING (the same shape as getBusinessVerification).
303
+ */
304
+ async submitBusinessVerification({ groupId, details, options = {} }) {
305
+ this.client._require({ groupId, details });
306
+ return this.client(`/brand/business-verification/${encodeURIComponent(groupId)}`, { method: 'PUT', body: details, ...options });
307
+ }
308
+
251
309
  /**
252
310
  * Create a local Short Code brand for manual Signal House review.
253
311
  * @async
@@ -96,7 +96,7 @@
96
96
  * @property {TollFreeOptIn} [optIns.keyword] - Keyword opt-in details
97
97
  * @property {TollFreeOptIn} [optIns.interactiveVoiceResponse] - IVR opt-in details
98
98
  * @property {string} [multiNumberReason] - Internally-stored reason the customer requested more than one Toll-Free number on the campaign (optional, at most 500 characters). Required when more than one phone number is assigned; captured for Signal House review only and never sent to the carrier.
99
- * @property {string[]} [useCases] - The sender's real use-case picks (optional). `useCase` above carries the single value the carrier accepts (MIXED when there is more than one); these are what a Signal House landing page renders a consent checkbox per. Never forwarded to the carrier.
99
+ * @property {string[]} [useCases] - The sender's real use-case picks (optional). `useCase` above carries the single value the carrier accepts (MIXED when there is more than one); these are what a Signal House landing page renders a consent checkbox per. Carriers that need specific use cases rather than MIXED take them from here when present; other carriers never receive them.
100
100
  * @property {string[]} [channels] - The channels consent covers ("SMS", "MMS", "VOICE") (optional). Never forwarded to the carrier.
101
101
  * @property {string} [landingId] - The Signal House landing page whose consent boxes this campaign's call-to-action describes (optional). Must be an ACTIVE page owned by the same brand and group.
102
102
  */
@@ -105,11 +105,11 @@
105
105
  * @typedef {Object} CreateTollFreeCampaignData
106
106
  * @property {string} brandId - The ID of the Toll-Free brand associated with the campaign (required, at least 7 characters)
107
107
  * @property {TollFreeCampaignDetails} tollFree - The Toll-Free campaign details (required)
108
- * @property {string} [privacyPolicyLink] - A URL linking to the privacy policy (optional, at most 2048 characters)
109
- * @property {string} [termsAndConditionsLink] - A URL linking to the terms and conditions (optional, at most 2048 characters)
108
+ * @property {string} [privacyPolicyLink] - A URL linking to the privacy policy (at most 2048 characters). Required when the brand's `tollFree.requiresPrivacyPolicy` is true and the brand has no `privacyPolicyLink` of its own; optional otherwise.
109
+ * @property {string} [termsAndConditionsLink] - A URL linking to the terms and conditions (at most 2048 characters). Required when the brand's `tollFree.requiresTermsAndConditions` is true and the brand has no `termsAndConditionsLink` of its own; optional otherwise.
110
110
  * @property {string} [tag] - A tag for the campaign (optional, at most 255 characters)
111
111
  * @property {boolean} [autoRenewal=true] - Whether the campaign should auto-renew (optional, defaults to true)
112
- * @property {Array<string>} phoneNumbers - The Toll-Free numbers to assign to the campaign (required, at least 1 and at most 5, each at least 10 digits and digits only). Numbers are locked to the campaign once assigned and cannot be moved or added afterward.
112
+ * @property {Array<string>} phoneNumbers - The Toll-Free numbers to assign to the campaign (required, 1 up to the brand's tollFree.maxNumbersPerCampaign and at most 5, each at least 10 digits and digits only). The numbers must be on the brand's toll-free carrier, or the campaign is refused. Numbers are locked to the campaign once assigned and cannot be moved or added afterward.
113
113
  */
114
114
 
115
115
  /**
@@ -108,8 +108,10 @@ export class Messages {
108
108
 
109
109
  /**
110
110
  * Fetch the latest upstream carrier delivery report for a set of messages by ID (SHGHL-1856).
111
- * Useful for reconciling messages stuck in a non-terminal state. SMS and MMS messages are looked up
112
- * against the carrier; other message types are returned with their stored status.
111
+ * Useful for reconciling messages stuck in a non-terminal state. Outbound SMS and MMS messages sent from numbers on
112
+ * the carrier whose delivery logs we read are looked up there (the logs are kept about 48 hours); every other message,
113
+ * including P2P messages and messages sent from numbers on other carriers, is returned with its stored status and
114
+ * upstreamReportAvailable false. The request fails with a 500 when the sending numbers can't be read.
113
115
  * @async
114
116
  * @roles api, admin, developer, billing, user
115
117
  * @param {Object} params - The parameters for fetching delivery reports
@@ -81,6 +81,7 @@ export class Numbers {
81
81
  * @param {string} [params.brandId] - Filter by brand ID
82
82
  * @param {string} [params.subgroupId] - Filter by subgroup ID
83
83
  * @param {string} [params.groupId] - Filter by group ID
84
+ * @param {boolean} [params.currentTollFreeCarrier] - With subgroupId or groupId: when true, return only toll-free numbers that a new toll-free campaign in that group can use (those on the group's current toll-free carrier)
84
85
  * @param {number} [params.page] - The page number for pagination (default: 1)
85
86
  * @param {number} [params.limit] - The number of items per page for pagination (default: 20)
86
87
  * @param {RequestOptions} [params.options] - Additional options for the request
@@ -103,8 +104,8 @@ export class Numbers {
103
104
  * or GET /campaign/health.
104
105
  * @returns {Promise<SdkResponse<any>>} A promise that resolves to the list of phone numbers
105
106
  */
106
- async getPhoneNumbers({ phoneNumber, campaignId, registrationId, brandId, subgroupId, groupId, page, limit, options = {} }) {
107
- const filters = { phoneNumber, campaignId, registrationId, brandId, subgroupId, groupId, page, limit };
107
+ async getPhoneNumbers({ phoneNumber, campaignId, registrationId, brandId, subgroupId, groupId, currentTollFreeCarrier, page, limit, options = {} }) {
108
+ const filters = { phoneNumber, campaignId, registrationId, brandId, subgroupId, groupId, currentTollFreeCarrier, page, limit };
108
109
  const queryString = this.client._getQueryString(filters);
109
110
  return this.client(`/number${queryString}`, { method: 'GET', ...options });
110
111
  }
@@ -261,14 +262,18 @@ export class Numbers {
261
262
  * @roles api, admin, developer, billing, user
262
263
  * @param {Object} params - The parameters for reading the order status
263
264
  * @param {string} params.orderId - The order id returned by `purchaseTollFreeNumbers`
265
+ * @param {string} [params.groupId] - Staff and service callers only: the order's group, whose carrier the read follows.
266
+ * Their tokens carry no active group; every other caller's order is read by its active group, so a well-formed
267
+ * groupId is ignored and a malformed one gets a 400.
264
268
  * @param {RequestOptions} [params.options] - Additional options for the request
265
269
  * @throws {Error} Throws an error if the orderId parameter is missing
266
270
  * @returns {Promise<SdkResponse<any>>} A promise that resolves to `{ orderId, counts: { ready, provisioning, failed }, failures: [{ reason }], numbers: string[] }`.
267
271
  */
268
- async getTollFreeOrderStatus({ orderId, options = {} }) {
272
+ async getTollFreeOrderStatus({ orderId, groupId, options = {} }) {
269
273
  this.client._require({ orderId });
270
274
  const safeOrderId = encodeURIComponent(orderId);
271
- return this.client(`/number/toll-free/order/${safeOrderId}`, { method: 'GET', ...options });
275
+ const queryString = this.client._getQueryString({ groupId });
276
+ return this.client(`/number/toll-free/order/${safeOrderId}${queryString}`, { method: 'GET', ...options });
272
277
  }
273
278
 
274
279
  /**
@@ -298,6 +303,60 @@ export class Numbers {
298
303
  });
299
304
  }
300
305
 
306
+ /**
307
+ * Get a phone number's outbound caller name (CNAM) - the name US carriers show for calls from it
308
+ * @async
309
+ * @roles api, admin, developer, billing, user
310
+ * @param {Object} params - The parameters for the request
311
+ * @param {string} params.phoneNumber - The phone number (E.164, leading + optional)
312
+ * @param {RequestOptions} [params.options] - Additional options for the request
313
+ * @throws {Error} Throws an error if the phoneNumber parameter is missing
314
+ * @returns {Promise<SdkResponse<any>>} A promise that resolves to `{ phoneNumber, cnam, autoApply, eligible, ineligibleReason }`
315
+ */
316
+ async getCallerName({ phoneNumber, options = {} }) {
317
+ this.client._require({ phoneNumber });
318
+ const safePhoneNumber = encodeURIComponent(phoneNumber);
319
+ return this.client(`/number/${safePhoneNumber}/cnam`, { method: 'GET', ...options });
320
+ }
321
+
322
+ /**
323
+ * Set a phone number's outbound caller name (CNAM). The name may only remove characters from the
324
+ * registered name of the number's brand (its campaign's brand, else `brandId`, else the subgroup's
325
+ * default caller-ID brand), and must be 1-15 letters, digits, spaces, `-` or `_`.
326
+ * @async
327
+ * @roles api, admin, developer
328
+ * @param {Object} params - The parameters for the request
329
+ * @param {string} params.phoneNumber - The phone number (E.164, leading + optional)
330
+ * @param {string} params.name - The caller name to show
331
+ * @param {string} [params.brandId] - The verified brand the name comes from; only for numbers with no campaign
332
+ * @param {RequestOptions} [params.options] - Additional options for the request
333
+ * @throws {Error} Throws an error if the phoneNumber or name parameter is missing
334
+ * @returns {Promise<SdkResponse<any>>} A promise that resolves to `{ phoneNumber, cnam, autoApply, eligible, ineligibleReason }`
335
+ */
336
+ async setCallerName({ phoneNumber, name, brandId, options = {} }) {
337
+ this.client._require({ phoneNumber, name });
338
+ const safePhoneNumber = encodeURIComponent(phoneNumber);
339
+ const body = brandId ? { name, brandId } : { name };
340
+ return this.client(`/number/${safePhoneNumber}/cnam`, { method: 'PUT', body, ...options });
341
+ }
342
+
343
+ /**
344
+ * Remove a phone number's own caller name (CNAM). If its subgroup has caller name auto-apply on, the
345
+ * number goes back to the automatic name.
346
+ * @async
347
+ * @roles api, admin, developer
348
+ * @param {Object} params - The parameters for the request
349
+ * @param {string} params.phoneNumber - The phone number (E.164, leading + optional)
350
+ * @param {RequestOptions} [params.options] - Additional options for the request
351
+ * @throws {Error} Throws an error if the phoneNumber parameter is missing
352
+ * @returns {Promise<SdkResponse<any>>} A promise that resolves to `{ phoneNumber, cnam, autoApply, eligible, ineligibleReason }`
353
+ */
354
+ async deleteCallerName({ phoneNumber, options = {} }) {
355
+ this.client._require({ phoneNumber });
356
+ const safePhoneNumber = encodeURIComponent(phoneNumber);
357
+ return this.client(`/number/${safePhoneNumber}/cnam`, { method: 'DELETE', ...options });
358
+ }
359
+
301
360
  /**
302
361
  * Submit a port-in request for one or more phone numbers from another provider
303
362
  * @async
@@ -371,7 +430,9 @@ export class Numbers {
371
430
  * Numbers that are not found, already RELEASED, currently being released (transient PENDING_RELEASE
372
431
  * status), already assigned to a different campaign, on a mismatched DCA, or (for a 10DLC campaign
373
432
  * without a number pool) beyond the remaining 49-number-per-campaign limit are not queued - each is
374
- * reported back individually in `skipped` instead of failing the whole request.
433
+ * reported back individually in `skipped` instead of failing the whole request. A Toll-Free campaign
434
+ * whose brand reports `tollFree.maxNumbersPerCampaign` as 1 holds one number: a request that would give
435
+ * it a second is refused with a 400 and nothing is queued.
375
436
  * @async
376
437
  * @roles api, admin, developer, billing, user
377
438
  * @param {Object} params - The parameters for assigning phone numbers to a campaign
@@ -40,6 +40,24 @@ export class Onboarding {
40
40
  return this.client(`/group/onboarding/${safeGroupId}`, { method: "GET", ...options });
41
41
  },
42
42
 
43
+ /**
44
+ * Set where a group's new toll-free brands and numbers go (`infobip` or `twilio`). Staff-only. Existing brands,
45
+ * numbers and campaigns stay where they are.
46
+ * @async
47
+ * @roles signalhouse
48
+ * @param {Object} params
49
+ * @param {string} params.groupId - The group ID whose toll-free provider to set
50
+ * @param {string} params.tollFreeProvider - The toll-free provider for the group's new toll-free brands and numbers ("infobip" or "twilio")
51
+ * @param {RequestOptions} [params.options] - Additional options for the request
52
+ * @throws {Error} Throws an error if the groupId or tollFreeProvider parameter is missing
53
+ * @returns {Promise<SdkResponse<any>>} - The updated onboarding record
54
+ */
55
+ setTollFreeProvider: async ({ groupId, tollFreeProvider, options = {} }) => {
56
+ this.client._require({ groupId, tollFreeProvider });
57
+ const safeGroupId = encodeURIComponent(groupId);
58
+ return this.client(`/group/onboarding/${safeGroupId}/toll-free-provider`, { method: "PUT", body: { tollFreeProvider }, ...options });
59
+ },
60
+
43
61
  /**
44
62
  * Add an internal comment/note to a group's admin Pricing editor. Staff-only.
45
63
  * @async
@@ -40,6 +40,8 @@
40
40
  * @property {string} [status] - The status of the subgroup (e.g., "active", "inactive")
41
41
  * @property {string} [carrierIdFamily] - Optional agency-managed carrier ID selection ("Default", "ATT", "TMobile", "Verizon", "USCellular", "GoogleVoice", "ClearSky", "Interop"). Setting this field requires signalhouse_api, signalhouse_admin, signalhouse_user, api, admin, developer, or billing role.
42
42
  * @property {string|null} [carrierIdRegion] - Optional ClearSky region ("PRClaro", "GuamDocomo", "GuamGTA", "GuamITE") or null; only valid when carrierIdFamily is "ClearSky". Setting this field requires signalhouse_api, signalhouse_admin, signalhouse_user, api, admin, developer, or billing role.
43
+ * @property {boolean} [cnamAutoApply] - Turn caller name (CNAM) auto-apply on or off: eligible numbers get their brand's registered name as the name shown on outbound calls. Requires signalhouse_api, signalhouse_admin, signalhouse_user, api, admin or developer role.
44
+ * @property {string|null} [cnamBrandId] - Default caller-ID brand for numbers with no campaign (e.g. voice-only). Must be a verified brand in this subgroup; required when turning auto-apply on in a subgroup with more than one verified brand.
43
45
  */
44
46
 
45
47
  export class Subgroups {
@@ -0,0 +1,403 @@
1
+ /** @typedef {import('../SignalHouseSDK.js').RequestOptions} RequestOptions */
2
+ /**
3
+ * @template T
4
+ * @typedef {import('../SignalHouseSDK.js').SdkResponse<T>} SdkResponse
5
+ */
6
+
7
+ /**
8
+ * A version selector: a positive version number, "draft" or "published". Omitted means the published
9
+ * version when one exists, otherwise the draft; an explicit selector never falls back (404 when missing).
10
+ * @typedef {number|"draft"|"published"} VersionSelector
11
+ */
12
+
13
+ /**
14
+ * @typedef {Object} RcsVariable
15
+ * @property {string} key - Placeholder key used as {{key}} (/^[a-z][a-z0-9_]{0,63}$/)
16
+ * @property {string} [label] - Display label
17
+ * @property {"text"|"number"|"dateTime"|"phoneNumber"|"url"} type - Value type
18
+ * @property {boolean} required - Whether a send must supply a value
19
+ * @property {string} [default] - Value used when none is supplied
20
+ * @property {string} [sample] - Example value for previews
21
+ */
22
+
23
+ /**
24
+ * The raw RCS template body. Unknown keys are rejected at every level; see the API docs for the block,
25
+ * suggestion and fallback shapes.
26
+ * @typedef {Object} RcsTemplateBody
27
+ * @property {"rcs"} channel - Always "rcs"
28
+ * @property {"rcs.blocks.v1"} contentFormat - Always "rcs.blocks.v1"
29
+ * @property {{ blocks: Object[] }} content - Ordered blocks of kind text, richCard, video, carousel, suggestions or attachment
30
+ * @property {RcsVariable[]} [variables] - Declared placeholders; omitted means []
31
+ * @property {{ trafficType?: string, ttlSeconds?: number }} [defaults] - Send defaults; omitted means {}
32
+ * @property {{ delivery?: { channel: "sms"|"mms", campaignId?: string, phoneNumber?: string, content: Object }, validitySeconds?: number }} [fallback] - SMS/MMS sent when RCS cannot be delivered; no delivery means no fallback
33
+ */
34
+
35
+ /**
36
+ * @typedef {Object} TemplateHead
37
+ * @property {string} templateId - "TPL" + 10 characters
38
+ * @property {string} groupId - Owning group
39
+ * @property {"rcs"} channel - Template channel
40
+ * @property {string} name - 1-120 characters, unique per group and channel
41
+ * @property {string|null} tag - Tag (1-60 characters) or null
42
+ * @property {string|null} archivedAt - When the template was archived
43
+ * @property {number|null} draftVersion - The current draft version
44
+ * @property {number|null} publishedVersion - The current published version
45
+ * @property {number} revision - Head revision, incremented on every write; starts at 1
46
+ * @property {string} createdBy - Creating user
47
+ * @property {string} createdAt - ISO timestamp
48
+ * @property {string} updatedAt - ISO timestamp
49
+ */
50
+
51
+ /**
52
+ * @typedef {TemplateHead & { state: "DRAFT"|"PUBLISHED"|"ARCHIVED", hasUnpublishedChanges: boolean }} TemplateSummary
53
+ */
54
+
55
+ /**
56
+ * @typedef {Object} VersionSummary
57
+ * @property {string} templateId - The template ID
58
+ * @property {string} groupId - Owning group
59
+ * @property {number} version - Version number
60
+ * @property {"DRAFT"|"PUBLISHED"} state - Version state
61
+ * @property {string} contentHash - Hex SHA-256 of the canonical body JSON
62
+ * @property {string} createdBy - Creating user
63
+ * @property {string} createdAt - ISO timestamp
64
+ * @property {string} updatedAt - ISO timestamp
65
+ * @property {string|null} publishedAt - ISO timestamp
66
+ */
67
+
68
+ /**
69
+ * @typedef {VersionSummary & { body: RcsTemplateBody }} TemplateVersion
70
+ */
71
+
72
+ /**
73
+ * @typedef {Object} TemplateDetail
74
+ * @property {TemplateSummary} head - The template head
75
+ * @property {TemplateVersion} version - The selected version
76
+ */
77
+
78
+ /**
79
+ * @template T
80
+ * @typedef {{ data: T[], total: number, page: number, limit: number }} Page
81
+ */
82
+
83
+ /**
84
+ * @typedef {Object} ValidationIssue
85
+ * @property {"error"|"warning"} severity - Issue severity
86
+ * @property {string} path - JSON path of the offending field
87
+ * @property {string} code - Machine-readable code
88
+ * @property {string} message - Human-readable message
89
+ * @property {string} [blockId] - The block the issue belongs to
90
+ */
91
+
92
+ /**
93
+ * @typedef {Object} ValidationResult
94
+ * @property {boolean} valid - Whether the body is publishable
95
+ * @property {string} contentHash - Hex SHA-256 of the canonical body JSON
96
+ * @property {string} validatorVersion - The validator that produced the result
97
+ * @property {ValidationIssue[]} errors - Publication errors
98
+ * @property {ValidationIssue[]} warnings - Non-blocking warnings
99
+ */
100
+
101
+ /**
102
+ * @typedef {Object} MediaAsset
103
+ * @property {string} mediaId - "MED" + 10 characters
104
+ * @property {string} groupId - Owning group
105
+ * @property {string} url - Public HTTPS URL to reference from a template body
106
+ * @property {number} bytes - File size
107
+ * @property {string} mimeType - MIME type
108
+ * @property {"image"|"video"|"audio"|"file"} kind - Media kind
109
+ * @property {number|null} width - Pixel width, when known
110
+ * @property {number|null} height - Pixel height, when known
111
+ * @property {string} createdAt - ISO timestamp
112
+ */
113
+
114
+ /**
115
+ * Server-defined channel capabilities; values come from the server and drive client-side editors.
116
+ * @typedef {Object} Capabilities
117
+ * @property {"rcs"} channel - The channel described
118
+ * @property {"rcs.blocks.v1"} contentFormat - The body format
119
+ * @property {string} validatorVersion - The validator version
120
+ * @property {string[]} blockKinds - Allowed block kinds
121
+ * @property {string[]} actionTypes - Allowed suggestion types
122
+ * @property {string[]} variableTypes - Allowed variable types
123
+ * @property {string[]} trafficTypes - Allowed traffic types
124
+ * @property {string[]} cardWidths - Allowed carousel card widths
125
+ * @property {string[]} mediaHeights - Allowed card media heights
126
+ * @property {Object<string, number>} limits - Length and count limits (maxBlocks, maxTextLength, maxCarouselCards, ...)
127
+ * @property {{ maxUploadBytes: number, mimeTypes: string[] }} media - Upload constraints
128
+ * @property {{ provider: "infobip"|"sinch", adapterVersion: string, limits: Object<string, number>, unsupported: Array<{ feature: string, code: string, message: string }> }|null} provider - Provider-specific limits when a provider was requested
129
+ */
130
+
131
+ /**
132
+ * Build the client-side validation error the SDK throws for a malformed request.
133
+ * @param {string} message - The error message
134
+ * @returns {Error & { status?: number }} - An error named SignalHouseValidationError with status 400
135
+ */
136
+ function validationError(message) {
137
+ const error = /** @type {Error & { status?: number }} */ (new Error(message));
138
+ error.name = "SignalHouseValidationError";
139
+ error.status = 400;
140
+ return error;
141
+ }
142
+
143
+ /**
144
+ * Build the update request body; a null tag stays on the wire, an undefined one is dropped.
145
+ * @param {Object} params - The update fields
146
+ * @param {string} params.groupId - The owning group
147
+ * @param {string} [params.name] - New name
148
+ * @param {string|null} [params.tag] - New tag; null clears it, undefined leaves it unchanged
149
+ * @param {RcsTemplateBody} [params.body] - Full replacement body
150
+ * @returns {Object} - The request body
151
+ */
152
+ export function templateUpdateBody({ groupId, name, tag, body }) {
153
+ return { groupId, name, tag, body };
154
+ }
155
+
156
+ /**
157
+ * Build the validate request body from exactly one of body or version.
158
+ * @param {Object} params - The validate fields
159
+ * @param {string} params.groupId - The owning group
160
+ * @param {RcsTemplateBody} [params.body] - An unsaved body to validate
161
+ * @param {VersionSelector} [params.version] - A stored version to validate
162
+ * @throws {Error} Throws a SignalHouseValidationError unless exactly one of body or version is given
163
+ * @returns {Object} - The request body
164
+ */
165
+ export function templateValidateBody({ groupId, body, version }) {
166
+ const hasBody = body !== undefined && body !== null;
167
+ const hasVersion = version !== undefined && version !== null;
168
+ if (hasBody === hasVersion) throw validationError("Pass exactly one of body or version");
169
+ return hasBody ? { groupId, body } : { groupId, version };
170
+ }
171
+
172
+ /**
173
+ * Build the multipart form for a media upload.
174
+ * @param {Object} params - The upload fields
175
+ * @param {string} params.groupId - The owning group
176
+ * @param {Blob|File} params.file - The file to upload
177
+ * @param {string} [params.fileName] - File name to send with the file
178
+ * @returns {FormData} - The form with file and groupId fields
179
+ */
180
+ export function templateMediaForm({ groupId, file, fileName }) {
181
+ const formData = new FormData();
182
+ if (fileName) formData.append("file", file, fileName);
183
+ else formData.append("file", file);
184
+ formData.append("groupId", groupId);
185
+ return formData;
186
+ }
187
+
188
+ /**
189
+ * Channel-generic message templates under /template. Templates keep an immutable published version and
190
+ * at most one editable draft; writes apply directly (last write wins). 404 while the environment's RCS
191
+ * switch is off.
192
+ */
193
+ export class Templates {
194
+ constructor(client, multipartClient, enableAdmin) {
195
+ this.client = client;
196
+ this.multipartClient = multipartClient;
197
+ this.enableAdmin = enableAdmin;
198
+ }
199
+
200
+ /**
201
+ * Create a template with draft version 1
202
+ * @async
203
+ * @roles api, admin, developer
204
+ * @param {Object} params - The parameters for creating a template
205
+ * @param {string} params.groupId - The owning group
206
+ * @param {string} params.name - Template name (1-120 characters, unique per group and channel)
207
+ * @param {string} [params.tag] - Tag (1-60 characters)
208
+ * @param {RcsTemplateBody} params.body - The raw template body
209
+ * @param {RequestOptions} [params.options] - Additional options for the request
210
+ * @returns {Promise<SdkResponse<TemplateDetail>>} A promise that resolves to the new template and its draft (201)
211
+ */
212
+ async create({ groupId, name, tag, body, options = {} }) {
213
+ this.client._require({ groupId, name, body });
214
+ return this.client(`/template`, {
215
+ method: "POST",
216
+ body: { groupId, name, tag, body },
217
+ ...options,
218
+ });
219
+ }
220
+
221
+ /**
222
+ * List a group's templates
223
+ * @async
224
+ * @roles api, admin, developer, billing, user
225
+ * @param {Object} params - The parameters for listing templates
226
+ * @param {string} params.groupId - The owning group
227
+ * @param {("DRAFT"|"PUBLISHED"|"ARCHIVED")} [params.state] - Filter by state
228
+ * @param {"rcs"} [params.channel] - Filter by channel
229
+ * @param {string} [params.tag] - Filter by tag
230
+ * @param {string} [params.search] - Search template names
231
+ * @param {("name"|"createdAt"|"updatedAt")} [params.sortBy] - Sort field
232
+ * @param {("asc"|"desc")} [params.sortOrder] - Sort direction
233
+ * @param {number} [params.page] - Page number (default 1)
234
+ * @param {number} [params.limit] - Templates per page (default 25, max 100)
235
+ * @param {RequestOptions} [params.options] - Additional options for the request
236
+ * @returns {Promise<SdkResponse<Page<TemplateSummary>>>} A promise that resolves to { data, total, page, limit }
237
+ */
238
+ async list({ groupId, state, channel, tag, search, sortBy, sortOrder, page, limit, options = {} }) {
239
+ this.client._require({ groupId });
240
+ const queryString = this.client._getQueryString({ groupId, state, channel, tag, search, sortBy, sortOrder, page, limit });
241
+ return this.client(`/template${queryString}`, { method: "GET", ...options });
242
+ }
243
+
244
+ /**
245
+ * Get a template with one of its versions
246
+ * @async
247
+ * @roles api, admin, developer, billing, user
248
+ * @param {Object} params - The parameters for getting a template
249
+ * @param {string} params.templateId - The template ID
250
+ * @param {string} params.groupId - The owning group
251
+ * @param {VersionSelector} [params.version] - Version to return; defaults to published, else draft
252
+ * @param {RequestOptions} [params.options] - Additional options for the request
253
+ * @returns {Promise<SdkResponse<TemplateDetail>>} A promise that resolves to { head, version }
254
+ */
255
+ async get({ templateId, groupId, version, options = {} }) {
256
+ this.client._require({ templateId, groupId });
257
+ const safeId = encodeURIComponent(templateId);
258
+ const queryString = this.client._getQueryString({ groupId, version });
259
+ return this.client(`/template/${safeId}${queryString}`, { method: "GET", ...options });
260
+ }
261
+
262
+ /**
263
+ * Update a template's name, tag or draft body; editing the body of a published template opens a new draft
264
+ * @async
265
+ * @roles api, admin, developer
266
+ * @param {Object} params - The parameters for updating a template
267
+ * @param {string} params.templateId - The template ID
268
+ * @param {string} params.groupId - The owning group
269
+ * @param {string} [params.name] - New name
270
+ * @param {string|null} [params.tag] - New tag; null clears it, omitted leaves it unchanged
271
+ * @param {RcsTemplateBody} [params.body] - Full replacement body
272
+ * @param {RequestOptions} [params.options] - Additional options for the request
273
+ * @returns {Promise<SdkResponse<TemplateDetail>>} A promise that resolves to { head, version }
274
+ */
275
+ async update({ templateId, groupId, name, tag, body, options = {} }) {
276
+ this.client._require({ templateId, groupId });
277
+ const safeId = encodeURIComponent(templateId);
278
+ return this.client(`/template/${safeId}`, {
279
+ method: "PUT",
280
+ body: templateUpdateBody({ groupId, name, tag, body }),
281
+ ...options,
282
+ });
283
+ }
284
+
285
+ /**
286
+ * Publish a template's current draft; no draft returns 409 NO_DRAFT, a draft changed mid-publish returns 409 DRAFT_CHANGED (publish again) and an incomplete draft returns 400 TEMPLATE_INVALID
287
+ * @async
288
+ * @roles api, admin, developer
289
+ * @param {Object} params - The parameters for publishing a template
290
+ * @param {string} params.templateId - The template ID
291
+ * @param {string} params.groupId - The owning group
292
+ * @param {RequestOptions} [params.options] - Additional options for the request
293
+ * @returns {Promise<SdkResponse<TemplateDetail>>} A promise that resolves to { head, version } for the published version
294
+ */
295
+ async publish({ templateId, groupId, options = {} }) {
296
+ this.client._require({ templateId, groupId });
297
+ const safeId = encodeURIComponent(templateId);
298
+ return this.client(`/template/${safeId}/publish`, {
299
+ method: "POST",
300
+ body: { groupId },
301
+ ...options,
302
+ });
303
+ }
304
+
305
+ /**
306
+ * Archive a template
307
+ * @async
308
+ * @roles api, admin, developer
309
+ * @param {Object} params - The parameters for archiving a template
310
+ * @param {string} params.templateId - The template ID
311
+ * @param {string} params.groupId - The owning group
312
+ * @param {RequestOptions} [params.options] - Additional options for the request
313
+ * @returns {Promise<SdkResponse<TemplateHead>>} A promise that resolves to the archived template head
314
+ */
315
+ async archive({ templateId, groupId, options = {} }) {
316
+ this.client._require({ templateId, groupId });
317
+ const safeId = encodeURIComponent(templateId);
318
+ return this.client(`/template/${safeId}/archive`, {
319
+ method: "POST",
320
+ body: { groupId },
321
+ ...options,
322
+ });
323
+ }
324
+
325
+ /**
326
+ * List a template's versions without their bodies
327
+ * @async
328
+ * @roles api, admin, developer, billing, user
329
+ * @param {Object} params - The parameters for listing versions
330
+ * @param {string} params.templateId - The template ID
331
+ * @param {string} params.groupId - The owning group
332
+ * @param {number} [params.page] - Page number (default 1)
333
+ * @param {number} [params.limit] - Versions per page (default 25, max 100)
334
+ * @param {RequestOptions} [params.options] - Additional options for the request
335
+ * @returns {Promise<SdkResponse<Page<VersionSummary>>>} A promise that resolves to { data, total, page, limit }
336
+ */
337
+ async versions({ templateId, groupId, page, limit, options = {} }) {
338
+ this.client._require({ templateId, groupId });
339
+ const safeId = encodeURIComponent(templateId);
340
+ const queryString = this.client._getQueryString({ groupId, page, limit });
341
+ return this.client(`/template/${safeId}/versions${queryString}`, { method: "GET", ...options });
342
+ }
343
+
344
+ /**
345
+ * Validate an unsaved body or a stored version without writing anything
346
+ * @async
347
+ * @roles api, admin, developer, billing, user
348
+ * @param {Object} params - The parameters for validation; pass exactly one of body or version
349
+ * @param {string} params.templateId - The template ID
350
+ * @param {string} params.groupId - The owning group
351
+ * @param {RcsTemplateBody} [params.body] - An unsaved body to validate
352
+ * @param {VersionSelector} [params.version] - A stored version to validate
353
+ * @param {RequestOptions} [params.options] - Additional options for the request
354
+ * @throws {Error} Throws a SignalHouseValidationError unless exactly one of body or version is given
355
+ * @returns {Promise<SdkResponse<ValidationResult>>} A promise that resolves to { valid, contentHash, validatorVersion, errors, warnings }
356
+ */
357
+ async validate({ templateId, groupId, body, version, options = {} }) {
358
+ this.client._require({ templateId, groupId });
359
+ const safeId = encodeURIComponent(templateId);
360
+ return this.client(`/template/${safeId}/validate`, {
361
+ method: "POST",
362
+ body: templateValidateBody({ groupId, body, version }),
363
+ ...options,
364
+ });
365
+ }
366
+
367
+ /**
368
+ * Get a channel's template capabilities: allowed blocks, actions, limits and upload constraints
369
+ * @async
370
+ * @roles api, admin, developer, billing, user
371
+ * @param {Object} params - The parameters for getting capabilities
372
+ * @param {string} params.groupId - The owning group
373
+ * @param {"rcs"} [params.channel] - The channel (default "rcs")
374
+ * @param {("infobip"|"sinch")} [params.provider] - Include this provider's limits and unsupported features
375
+ * @param {RequestOptions} [params.options] - Additional options for the request
376
+ * @returns {Promise<SdkResponse<Capabilities>>} A promise that resolves to the capabilities
377
+ */
378
+ async capabilities({ groupId, channel, provider, options = {} }) {
379
+ this.client._require({ groupId });
380
+ const queryString = this.client._getQueryString({ groupId, channel, provider });
381
+ return this.client(`/template/capabilities${queryString}`, { method: "GET", ...options });
382
+ }
383
+
384
+ /**
385
+ * Upload a media file for use in template bodies; returns its public URL. 503 when media storage is not configured
386
+ * @async
387
+ * @roles api, admin, developer
388
+ * @param {Object} params - The parameters for the upload
389
+ * @param {string} params.groupId - The owning group
390
+ * @param {Blob|File} params.file - The file to upload
391
+ * @param {string} [params.fileName] - File name to send with the file
392
+ * @param {RequestOptions} [params.options] - Additional options for the request
393
+ * @returns {Promise<SdkResponse<MediaAsset>>} A promise that resolves to the stored media asset (201)
394
+ */
395
+ async uploadMedia({ groupId, file, fileName, options = {} }) {
396
+ this.client._require({ groupId, file });
397
+ return this.multipartClient(`/template/media`, {
398
+ method: "POST",
399
+ body: templateMediaForm({ groupId, file, fileName }),
400
+ ...options,
401
+ });
402
+ }
403
+ }
@@ -1,6 +1,7 @@
1
1
  import { SipTrunks } from "./voice/SipTrunks.js";
2
2
  import { SipProfiles } from "./voice/SipProfiles.js";
3
3
  import { ProgrammableVoiceProfiles } from "./voice/ProgrammableVoiceProfiles.js";
4
+ import { NumberRoutes } from "./voice/NumberRoutes.js";
4
5
  import { Calls } from "./voice/Calls.js";
5
6
  import { CallLogs } from "./voice/CallLogs.js";
6
7
  import { GlobalVoiceSettings } from "./voice/GlobalVoiceSettings.js";
@@ -16,6 +17,7 @@ import { Analytics } from "./voice/Analytics.js";
16
17
  * - `sdk.voice.sipTrunks` — SIP trunk (peer-to-peer connections to PBX/carrier).
17
18
  * - `sdk.voice.sipProfiles` — SIP profile / endpoint (single registerable UA).
18
19
  * - `sdk.voice.programmableVoiceProfiles` — Programmable Voice Profile (route a set of numbers through Signal House).
20
+ * - `sdk.voice.numberRoutes` — Per-number inbound route (overrides the profile and subgroup settings).
19
21
  * - `sdk.voice.calls` — Twilio-compat programmatic call control (create/hangup/etc.).
20
22
  * - `sdk.voice.callLogs` — Account-scoped call history: list/get + presigned recording.
21
23
  * - `sdk.voice.globalVoiceSettings` — Account-wide voice defaults (accepted regions, max spend, E911).
@@ -29,6 +31,7 @@ export class Voice {
29
31
  this.sipTrunks = new SipTrunks(client, enableAdmin);
30
32
  this.sipProfiles = new SipProfiles(client, enableAdmin);
31
33
  this.programmableVoiceProfiles = new ProgrammableVoiceProfiles(client, enableAdmin);
34
+ this.numberRoutes = new NumberRoutes(client, enableAdmin);
32
35
  this.calls = new Calls(client, enableAdmin);
33
36
  this.callLogs = new CallLogs(client, enableAdmin);
34
37
  this.globalVoiceSettings = new GlobalVoiceSettings(client, enableAdmin);
@@ -43,6 +43,10 @@ export class Webhooks {
43
43
  * `verifyWebhookSignature` from `@signalhousellc/sdk/webhooks` (Node only). `webhookData.authType` is one of
44
44
  * `NONE` (default), `BASIC`, `API_KEY`, `OAUTH2` or `HMAC`; `HMAC` (SHGHL-3595) sends no credentials, so the
45
45
  * receiver authenticates by the signature alone, and must be sent without `credentials` or `apiHeaderPrefix`.
46
+ * `webhookData.eventSettings` (SHGHL-3884) holds per-event options keyed by event name:
47
+ * `NUMBER_HEALTH_BELOW_THRESHOLD` and `CAMPAIGN_HEALTH_BELOW_THRESHOLD` each take `{ threshold }`, the 7-day
48
+ * health score (1.5–10, in 0.5 steps) below which the event fires; a subscribed health event without one is
49
+ * stored with the default, 6.
46
50
  * @async
47
51
  * @roles api, admin, developer
48
52
  * @param {Object} params - The parameters for creating a webhook
@@ -62,7 +66,9 @@ export class Webhooks {
62
66
  * to change the endpoint's own credentials in place: credentials are required when switching to an auth
63
67
  * type that needs them, and omitted credentials keep the stored ones. Switching to `NONE` or `HMAC` clears
64
68
  * the stored credentials; `HMAC` is rejected for an endpoint with no signing secret (call
65
- * `rotateWebhookSecret` first). Like every read of a webhook, the
69
+ * `rotateWebhookSecret` first). `updateData.eventSettings` changes per-event options by event name
70
+ * (`{ NUMBER_HEALTH_BELOW_THRESHOLD: { threshold } }`, `{ CAMPAIGN_HEALTH_BELOW_THRESHOLD: { threshold } }`,
71
+ * 1.5–10 in 0.5 steps); events you omit keep their stored settings. Like every read of a webhook, the
66
72
  * response carries `hasSigningSecret` and `hasCredentials` (booleans), never a secret or credential.
67
73
  * @async
68
74
  * @roles api, admin, developer
@@ -0,0 +1,82 @@
1
+ /** @typedef {import('../../SignalHouseSDK.js').RequestOptions} RequestOptions */
2
+ /**
3
+ * @template T
4
+ * @typedef {import('../../SignalHouseSDK.js').SdkResponse<T>} SdkResponse
5
+ */
6
+
7
+ /**
8
+ * @typedef {Object} NumberRouteData
9
+ * @property {"FORWARD"|"WEBRTC"|"SIP_TRUNK"|"SIP_PROFILE"|"CALL_CONTROL"|"AI"|"VOICEMAIL"} routeAction - How inbound calls to the number are handled.
10
+ * @property {string|null} [forwardToE164] - FORWARD destination (required for FORWARD), or the WEBRTC no-answer destination when noAnswerAction is FORWARD.
11
+ * @property {"VOICEMAIL"|"FORWARD"|"KEEP_RINGING"|null} [noAnswerAction] - WEBRTC only: what happens when nobody answers (default VOICEMAIL).
12
+ * @property {number|null} [noAnswerSeconds] - WEBRTC only: seconds to ring before the no-answer action, 5-120 (default 20).
13
+ * @property {string|null} [routeSipTrunkId] - SIP trunk to deliver to (required for SIP_TRUNK).
14
+ * @property {string|null} [routeSipProfileId] - SIP endpoint to ring (required for SIP_PROFILE).
15
+ * @property {string|null} [webhookUrl] - SHML program URL (required for CALL_CONTROL).
16
+ * @property {string|null} [routeAgentProfileId] - AI agent profile id, AGP… (required for AI).
17
+ * @property {boolean} [recordingEnabled] - Record calls handled by this route.
18
+ */
19
+
20
+ /**
21
+ * Number Routes — one inbound route set directly on a single phone number. A
22
+ * number's own route takes priority over its Programmable Voice Profile and
23
+ * subgroup voice settings. Calls the voice-backend service mounted under
24
+ * /voice. Accessed via `sdk.voice.numberRoutes`.
25
+ */
26
+ export class NumberRoutes {
27
+ constructor(client, enableAdmin) {
28
+ this.client = client;
29
+ this.enableAdmin = enableAdmin;
30
+ }
31
+
32
+ /**
33
+ * Get a number's own route and the routing currently in effect for it.
34
+ * @async
35
+ * @roles api, admin, developer, billing, user
36
+ * @param {Object} params - Request parameters.
37
+ * @param {string} params.e164 - The phone number in E.164 (leading + optional).
38
+ * @param {RequestOptions} [params.options] - Additional options for the request.
39
+ * @throws {Error} If e164 is missing.
40
+ * @returns {Promise<SdkResponse<any>>} `{ e164, route: {...} | null, effective: {...} }`.
41
+ */
42
+ async get({ e164, options = {} }) {
43
+ this.client._require({ e164 });
44
+ const safeE164 = encodeURIComponent(e164);
45
+ return this.client(`/voice/api/v1/numbers/${safeE164}/route`, { method: "GET", ...options });
46
+ }
47
+
48
+ /**
49
+ * Set (create or replace) a number's route. The whole route is replaced:
50
+ * fields the new routeAction doesn't use are cleared.
51
+ * @async
52
+ * @roles api, admin, developer
53
+ * @param {Object} params - Request parameters.
54
+ * @param {string} params.e164 - The phone number in E.164 (leading + optional).
55
+ * @param {NumberRouteData} params.routeData - The route to set.
56
+ * @param {RequestOptions} [params.options] - Additional options for the request.
57
+ * @throws {Error} If e164 or routeData is missing.
58
+ * @returns {Promise<SdkResponse<any>>} `{ e164, route: {...}, effective: {...} }`.
59
+ */
60
+ async set({ e164, routeData, options = {} }) {
61
+ this.client._require({ e164, routeData });
62
+ const safeE164 = encodeURIComponent(e164);
63
+ return this.client(`/voice/api/v1/numbers/${safeE164}/route`, { method: "PUT", body: routeData, ...options });
64
+ }
65
+
66
+ /**
67
+ * Remove a number's own route. The number falls back to its Programmable
68
+ * Voice Profile, subgroup voice settings, or the default.
69
+ * @async
70
+ * @roles api, admin, developer
71
+ * @param {Object} params - Request parameters.
72
+ * @param {string} params.e164 - The phone number in E.164 (leading + optional).
73
+ * @param {RequestOptions} [params.options] - Additional options for the request.
74
+ * @throws {Error} If e164 is missing.
75
+ * @returns {Promise<SdkResponse<any>>} `{ e164, route: null, effective: {...} }`.
76
+ */
77
+ async delete({ e164, options = {} }) {
78
+ this.client._require({ e164 });
79
+ const safeE164 = encodeURIComponent(e164);
80
+ return this.client(`/voice/api/v1/numbers/${safeE164}/route`, { method: "DELETE", ...options });
81
+ }
82
+ }