synup-js 0.1.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.cjs ADDED
@@ -0,0 +1,1405 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ SynupAPIError: () => SynupAPIError,
24
+ SynupClient: () => SynupClient,
25
+ encodeLocationId: () => encodeLocationId
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/errors.ts
30
+ var SynupAPIError = class extends Error {
31
+ statusCode;
32
+ responseBody;
33
+ constructor(message, statusCode, responseBody) {
34
+ const fullMessage = responseBody ? `${message} \u2014 ${responseBody}` : message;
35
+ super(fullMessage);
36
+ this.name = "SynupAPIError";
37
+ this.statusCode = statusCode ?? null;
38
+ this.responseBody = responseBody ?? null;
39
+ }
40
+ };
41
+
42
+ // src/resources/locations.ts
43
+ function createLocationMethods(http) {
44
+ function parsePaginatedLocations(container) {
45
+ const edges = container?.edges ?? [];
46
+ const rawPageInfo = container?.pageInfo ?? {};
47
+ const locations = edges.map((e) => e.node);
48
+ const startCursor = edges.length > 0 ? edges[0].cursor : null;
49
+ const endCursor = edges.length > 0 ? edges[edges.length - 1].cursor : null;
50
+ return {
51
+ locations,
52
+ pageInfo: {
53
+ hasNextPage: rawPageInfo.hasNextPage ?? false,
54
+ hasPreviousPage: rawPageInfo.hasPreviousPage ?? false,
55
+ startCursor,
56
+ endCursor,
57
+ ...rawPageInfo.total !== void 0 ? { total: rawPageInfo.total } : {}
58
+ }
59
+ };
60
+ }
61
+ function paginationParams(options) {
62
+ const params = {};
63
+ if (!options) return params;
64
+ if (options.first !== void 0) params.first = options.first;
65
+ if (options.after !== void 0) params.after = options.after;
66
+ if (options.before !== void 0) params.before = options.before;
67
+ if (options.last !== void 0) params.last = options.last;
68
+ return params;
69
+ }
70
+ async function fetchLocationsPage(options) {
71
+ const params = paginationParams(options);
72
+ const data = await http.apiGet("locations", params);
73
+ const allLocations = data?.data?.allLocations ?? {};
74
+ const { locations, pageInfo } = parsePaginatedLocations(allLocations);
75
+ return {
76
+ success: true,
77
+ locations,
78
+ pageInfo,
79
+ raw: data
80
+ };
81
+ }
82
+ async function fetchAllLocationsPaginated(pageSize = 100) {
83
+ const allNodes = [];
84
+ let after;
85
+ while (true) {
86
+ const page = await fetchLocationsPage({ first: pageSize, after });
87
+ allNodes.push(...page.locations);
88
+ if (!page.pageInfo.hasNextPage) break;
89
+ after = page.pageInfo.endCursor ?? void 0;
90
+ if (!after) break;
91
+ }
92
+ return allNodes;
93
+ }
94
+ return {
95
+ /**
96
+ * Get locations for the account, with optional pagination or fetch-all.
97
+ *
98
+ * When fetchAll is true, returns a flat array of all locations.
99
+ * Otherwise returns a paginated response with locations, pageInfo, and raw data.
100
+ */
101
+ async fetchAllLocations(options) {
102
+ if (options?.fetchAll) {
103
+ return fetchAllLocationsPaginated(options.pageSize ?? 100);
104
+ }
105
+ return fetchLocationsPage(options);
106
+ },
107
+ /**
108
+ * Get locations by a list of IDs. Accepts numeric or base64-encoded IDs.
109
+ */
110
+ async fetchLocationsByIds(locationIds) {
111
+ if (!locationIds || locationIds.length === 0) return [];
112
+ const encodedIds = locationIds.map((id) => http.encodeLocationId(id));
113
+ const data = await http.apiGet("locations-by-ids", {
114
+ ids: JSON.stringify(encodedIds)
115
+ });
116
+ return data?.data?.getLocationsByIds ?? [];
117
+ },
118
+ /**
119
+ * Get locations that match the given store codes.
120
+ */
121
+ async fetchLocationsByStoreCodes(storeCodes) {
122
+ if (!storeCodes || storeCodes.length === 0) return [];
123
+ const data = await http.apiGet("locations-by-store-codes", {
124
+ storeCodes: JSON.stringify(storeCodes)
125
+ });
126
+ return data?.data?.getLocationsByStoreCodes ?? [];
127
+ },
128
+ /**
129
+ * Search locations by keyword (name, address, or store ID).
130
+ */
131
+ async searchLocations(query, options) {
132
+ if (options?.fetchAll) {
133
+ const allNodes = [];
134
+ let after;
135
+ const pageSize = options.pageSize ?? 100;
136
+ while (true) {
137
+ const params2 = {
138
+ query,
139
+ first: pageSize,
140
+ ...paginationParams({ after })
141
+ };
142
+ if (options.fields) {
143
+ params2.fields = JSON.stringify(options.fields);
144
+ }
145
+ const data2 = await http.apiGet("locations/search", params2);
146
+ const searchResult2 = data2?.data?.searchLocations ?? {};
147
+ const { locations: locations2, pageInfo: pageInfo2 } = parsePaginatedLocations(searchResult2);
148
+ allNodes.push(...locations2);
149
+ if (!pageInfo2.hasNextPage) break;
150
+ after = pageInfo2.endCursor ?? void 0;
151
+ if (!after) break;
152
+ }
153
+ return allNodes;
154
+ }
155
+ const params = {
156
+ query,
157
+ ...paginationParams(options)
158
+ };
159
+ if (options?.fields) {
160
+ params.fields = JSON.stringify(options.fields);
161
+ }
162
+ const data = await http.apiGet("locations/search", params);
163
+ const searchResult = data?.data?.searchLocations ?? {};
164
+ const { locations, pageInfo } = parsePaginatedLocations(searchResult);
165
+ return {
166
+ success: true,
167
+ locations,
168
+ pageInfo,
169
+ raw: data
170
+ };
171
+ },
172
+ /**
173
+ * Get all locations in a folder (including subfolders). Use folderId or folderName.
174
+ */
175
+ async fetchLocationsByFolder(options) {
176
+ if (!options.folderId && !options.folderName) {
177
+ throw new Error("Provide either folderId or folderName");
178
+ }
179
+ const params = {};
180
+ if (options.folderId) params.folderId = options.folderId;
181
+ if (options.folderName) params.folderName = options.folderName;
182
+ const data = await http.apiGet("folder-locations", params);
183
+ return data?.data?.getLocationsForFolder ?? [];
184
+ },
185
+ /**
186
+ * Get locations that have any of the given tags.
187
+ */
188
+ async fetchLocationsByTags(tags, options) {
189
+ if (!tags || tags.length === 0) {
190
+ if (options?.fetchAll) return [];
191
+ return {
192
+ success: true,
193
+ locations: [],
194
+ pageInfo: {
195
+ hasNextPage: false,
196
+ hasPreviousPage: false,
197
+ startCursor: null,
198
+ endCursor: null
199
+ },
200
+ raw: {}
201
+ };
202
+ }
203
+ if (options?.fetchAll) {
204
+ const allNodes = [];
205
+ let after;
206
+ const pageSize = options.pageSize ?? 100;
207
+ while (true) {
208
+ const params2 = {
209
+ tags: JSON.stringify(tags),
210
+ first: pageSize,
211
+ ...paginationParams({ after })
212
+ };
213
+ if (options.archived !== void 0) {
214
+ params2.archived = JSON.stringify(options.archived);
215
+ }
216
+ const data2 = await http.apiGet("tags/locations", params2);
217
+ const tagResult2 = data2?.data?.searchLocationsByTag ?? {};
218
+ const { locations: locations2, pageInfo: pageInfo2 } = parsePaginatedLocations(tagResult2);
219
+ allNodes.push(...locations2);
220
+ if (!pageInfo2.hasNextPage) break;
221
+ after = pageInfo2.endCursor ?? void 0;
222
+ if (!after) break;
223
+ }
224
+ return allNodes;
225
+ }
226
+ const params = {
227
+ tags: JSON.stringify(tags),
228
+ ...paginationParams(options)
229
+ };
230
+ if (options?.archived !== void 0) {
231
+ params.archived = JSON.stringify(options.archived);
232
+ }
233
+ const data = await http.apiGet("tags/locations", params);
234
+ const tagResult = data?.data?.searchLocationsByTag ?? {};
235
+ const { locations, pageInfo } = parsePaginatedLocations(tagResult);
236
+ return {
237
+ success: true,
238
+ locations,
239
+ pageInfo,
240
+ raw: data
241
+ };
242
+ },
243
+ /**
244
+ * Create a new location.
245
+ */
246
+ async createLocation(input) {
247
+ const data = await http.apiPost("locations", { input });
248
+ return data?.data?.createLocation ?? {};
249
+ },
250
+ /**
251
+ * Update a location. Pass id plus any fields to change.
252
+ */
253
+ async updateLocation(input) {
254
+ const encoded = {
255
+ ...input,
256
+ id: http.encodeLocationId(input.id)
257
+ };
258
+ const data = await http.apiPost("locations/update", { input: encoded });
259
+ return data?.data?.updateLocation ?? {};
260
+ },
261
+ /**
262
+ * Archive one or more locations.
263
+ */
264
+ async archiveLocations(locationIds) {
265
+ const encodedIds = locationIds.map((id) => http.encodeLocationId(id));
266
+ const data = await http.apiPost("locations/archive", {
267
+ input: { locationIds: encodedIds }
268
+ });
269
+ return data?.data?.archiveLocations ?? {};
270
+ },
271
+ /**
272
+ * Reactivate previously archived locations.
273
+ */
274
+ async activateLocations(locationIds) {
275
+ const encodedIds = locationIds.map((id) => http.encodeLocationId(id));
276
+ const data = await http.apiPost("locations/activate", {
277
+ input: { locationIds: encodedIds }
278
+ });
279
+ return data?.data?.activateLocations ?? {};
280
+ },
281
+ /**
282
+ * Cancel scheduled archival for the given locations.
283
+ */
284
+ async cancelArchiveLocations(locationIds, selectionType, changedBy) {
285
+ const encodedIds = locationIds.map((id) => http.encodeLocationId(id));
286
+ const data = await http.apiPost("locations/cancel_archive", {
287
+ input: {
288
+ locationIds: encodedIds,
289
+ selectionType,
290
+ changedBy
291
+ }
292
+ });
293
+ return data?.data?.cancelLocationsArchive ?? {};
294
+ }
295
+ };
296
+ }
297
+
298
+ // src/resources/listings.ts
299
+ function createListingMethods(http) {
300
+ return {
301
+ /**
302
+ * Get premium (directory) listings for a location (Google, Yelp, etc.).
303
+ */
304
+ async fetchPremiumListings(locationId) {
305
+ const data = await http.listingsGet(locationId, "listings/premium");
306
+ return data?.data?.listingsForLocation ?? [];
307
+ },
308
+ /**
309
+ * Get voice assistant listings for a location (Google, Alexa, Siri, etc.).
310
+ */
311
+ async fetchVoiceListings(locationId) {
312
+ const data = await http.listingsGet(locationId, "voice-assistants");
313
+ return data?.data?.voiceAssistantsForLocation ?? [];
314
+ },
315
+ /**
316
+ * Get additional (non-premium) listings for a location.
317
+ */
318
+ async fetchAdditionalListings(locationId) {
319
+ const data = await http.listingsGet(locationId, "listings/additional");
320
+ return data?.data?.listingsForLocation ?? [];
321
+ },
322
+ /**
323
+ * Get duplicate listings detected for a location.
324
+ */
325
+ async fetchDuplicateListings(locationId) {
326
+ const data = await http.listingsGet(locationId, "listings/duplicates");
327
+ return data?.data?.duplicateListingsForLocation ?? [];
328
+ },
329
+ /**
330
+ * Get a rollup of duplicate listings across all locations,
331
+ * with optional tag filter and pagination.
332
+ */
333
+ async fetchAllDuplicateListings(options) {
334
+ const params = {};
335
+ if (options?.tag !== void 0) params.tag = options.tag;
336
+ if (options?.page !== void 0) params.page = options.page;
337
+ const data = await http.apiGet("locations/listings/duplicates", params);
338
+ return data?.data?.duplicateListingsRollup ?? {};
339
+ },
340
+ /**
341
+ * Get AI-generated listing suggestions for a location.
342
+ */
343
+ async fetchAiListings(locationId) {
344
+ const data = await http.listingsGet(locationId, "ai-listings");
345
+ return data?.data?.fetchAiListings ?? {};
346
+ },
347
+ /**
348
+ * Mark one or more listing items as duplicate for a location.
349
+ */
350
+ async markListingsAsDuplicate(locationId, listingItemIds) {
351
+ const data = await http.apiPost("locations/listings/mark-as-duplicate", {
352
+ input: {
353
+ locationId: http.encodeLocationId(locationId),
354
+ listingItemIds
355
+ }
356
+ });
357
+ return data?.data?.markAsDuplicate ?? {};
358
+ },
359
+ /**
360
+ * Clear duplicate status for listing items.
361
+ */
362
+ async markListingsAsNotDuplicate(locationId, listingItemIds) {
363
+ const data = await http.apiPost(
364
+ "locations/listings/mark-as-not-duplicate",
365
+ {
366
+ input: {
367
+ locationId: http.encodeLocationId(locationId),
368
+ listingItemIds
369
+ }
370
+ }
371
+ );
372
+ return data?.data?.markAsNotDuplicate ?? {};
373
+ }
374
+ };
375
+ }
376
+
377
+ // src/resources/reviews.ts
378
+ function createReviewMethods(http) {
379
+ async function fetchInteractionsPage(locationId, options = {}) {
380
+ const params = {};
381
+ if (options.first !== void 0) params.first = options.first;
382
+ if (options.after !== void 0) params.after = options.after;
383
+ if (options.before !== void 0) params.before = options.before;
384
+ if (options.last !== void 0) params.last = options.last;
385
+ if (options.startDate !== void 0) params.startDate = options.startDate;
386
+ if (options.endDate !== void 0) params.endDate = options.endDate;
387
+ if (options.category !== void 0) params.category = options.category;
388
+ if (options.siteUrls !== void 0) params.siteUrls = JSON.stringify(options.siteUrls);
389
+ if (options.ratingFilters !== void 0) params.ratingFilters = JSON.stringify(options.ratingFilters);
390
+ const data = await http.listingsGet(locationId, "reviews", params);
391
+ const interactionsData = data?.data?.interactions ?? {};
392
+ const edges = interactionsData.edges ?? [];
393
+ const pageInfo = interactionsData.pageInfo ?? {};
394
+ const interactions = edges.map((e) => e.node);
395
+ const startCursor = edges.length > 0 ? edges[0].cursor : null;
396
+ const endCursor = edges.length > 0 ? edges[edges.length - 1].cursor : null;
397
+ return {
398
+ success: true,
399
+ interactions,
400
+ pageInfo: {
401
+ hasNextPage: pageInfo.hasNextPage ?? false,
402
+ hasPreviousPage: pageInfo.hasPreviousPage ?? false,
403
+ startCursor,
404
+ endCursor
405
+ },
406
+ totalCount: interactionsData.totalCount,
407
+ raw: data
408
+ };
409
+ }
410
+ async function fetchAllInteractions(locationId, options = {}) {
411
+ const allNodes = [];
412
+ let afterCursor;
413
+ const pageSize = options.pageSize ?? 100;
414
+ while (true) {
415
+ const page = await fetchInteractionsPage(locationId, {
416
+ ...options,
417
+ first: pageSize,
418
+ after: afterCursor
419
+ });
420
+ allNodes.push(...page.interactions);
421
+ if (!page.pageInfo.hasNextPage || page.interactions.length === 0) break;
422
+ afterCursor = page.pageInfo.endCursor ?? void 0;
423
+ if (!afterCursor) break;
424
+ }
425
+ return allNodes;
426
+ }
427
+ return {
428
+ /**
429
+ * Get reviews/interactions for a location with pagination and filters.
430
+ * When fetchAll is true, returns a flat array of all interactions.
431
+ * Otherwise returns a paginated response object.
432
+ */
433
+ async fetchInteractions(locationId, options = {}) {
434
+ if (options.fetchAll) {
435
+ return fetchAllInteractions(locationId, {
436
+ ...options,
437
+ pageSize: options.pageSize
438
+ });
439
+ }
440
+ return fetchInteractionsPage(locationId, options);
441
+ },
442
+ /**
443
+ * Get review source settings for a location.
444
+ */
445
+ async fetchReviewSettings(locationId) {
446
+ const data = await http.listingsGet(locationId, "reviews/settings");
447
+ return data?.data?.interactionsSetting ?? {};
448
+ },
449
+ /**
450
+ * Get overall review analytics for a location.
451
+ */
452
+ async fetchReviewAnalyticsOverview(locationId, options = {}) {
453
+ const params = {};
454
+ if (options.startDate !== void 0) params.startDate = options.startDate;
455
+ if (options.endDate !== void 0) params.endDate = options.endDate;
456
+ const data = await http.listingsGet(locationId, "review-analytics-overview", params);
457
+ return data?.data?.interactionsAnalyticsStats ?? {};
458
+ },
459
+ /**
460
+ * Get review analytics over time for a location.
461
+ */
462
+ async fetchReviewAnalyticsTimeline(locationId, options = {}) {
463
+ const params = {};
464
+ if (options.startDate !== void 0) params.startDate = options.startDate;
465
+ if (options.endDate !== void 0) params.endDate = options.endDate;
466
+ const data = await http.listingsGet(locationId, "review-analytics-timeline", params);
467
+ return data?.data?.interactionsChartData ?? {};
468
+ },
469
+ /**
470
+ * Get review analytics broken down by site for a location.
471
+ */
472
+ async fetchReviewAnalyticsSitesStats(locationId, options = {}) {
473
+ const params = {};
474
+ if (options.startDate !== void 0) params.startDate = options.startDate;
475
+ if (options.endDate !== void 0) params.endDate = options.endDate;
476
+ const data = await http.listingsGet(locationId, "review-analytics-sites-stats", params);
477
+ return data?.data?.interactionsSitesStats ?? {};
478
+ },
479
+ /**
480
+ * Get eligible review sources and site config for the account.
481
+ */
482
+ async fetchReviewSiteConfig() {
483
+ const data = await http.apiGet("reviews/site-config");
484
+ return data?.data?.interactionSiteConfig ?? [];
485
+ },
486
+ /**
487
+ * Get detailed information for specific reviews by interaction IDs.
488
+ */
489
+ async fetchReviewDetails(interactionIds) {
490
+ if (interactionIds.length === 0) return {};
491
+ const params = { interactionIds: JSON.stringify(interactionIds) };
492
+ const data = await http.apiGet("reviewDetails", params);
493
+ return data?.data?.interactionDetails ?? {};
494
+ },
495
+ /**
496
+ * Get review phrase analysis for given locations.
497
+ */
498
+ async fetchReviewPhrases(options) {
499
+ if (options.locationIds.length === 0) return [];
500
+ const params = {
501
+ locationIds: JSON.stringify(options.locationIds)
502
+ };
503
+ if (options.siteUrls !== void 0) params.siteUrls = JSON.stringify(options.siteUrls);
504
+ if (options.startDate !== void 0) params.startDate = options.startDate;
505
+ if (options.endDate !== void 0) params.endDate = options.endDate;
506
+ const data = await http.apiGet("review-phrases", params);
507
+ return data?.data?.newReviewPhrases ?? [];
508
+ },
509
+ /**
510
+ * Post a reply to a review/interaction.
511
+ */
512
+ async respondToReview(interactionId, responseContent) {
513
+ const data = await http.apiPost("locations/reviews/respond", {
514
+ interactionId,
515
+ responseContent
516
+ });
517
+ return data?.data?.respondToInteraction ?? {};
518
+ },
519
+ /**
520
+ * Edit an existing reply to a review.
521
+ */
522
+ async editReviewResponse(reviewId, responseId, responseContent) {
523
+ const data = await http.apiPost("locations/reviews/respond/edit", {
524
+ reviewId,
525
+ responseId,
526
+ responseContent
527
+ });
528
+ return data?.data?.editResponse ?? {};
529
+ },
530
+ /**
531
+ * Archive (hide) a reply to a review.
532
+ */
533
+ async archiveReviewResponse(responseId) {
534
+ const data = await http.apiPost("locations/reviews/respond/archive", {
535
+ responseId
536
+ });
537
+ return data?.data?.archiveResponse ?? {};
538
+ },
539
+ /**
540
+ * Set or update review source URLs for a location.
541
+ */
542
+ async editReviewSettings(locationId, siteUrls) {
543
+ const data = await http.apiPost("locations/reviews/settings/edit", {
544
+ locationId: http.encodeLocationId(locationId),
545
+ siteUrls
546
+ });
547
+ return data?.data?.editInteractionsSetting ?? {};
548
+ }
549
+ };
550
+ }
551
+
552
+ // src/resources/keywords.ts
553
+ function createKeywordMethods(http) {
554
+ return {
555
+ /**
556
+ * Get all keywords tracked for a location.
557
+ */
558
+ async fetchKeywords(locationId) {
559
+ const data = await http.listingsGet(locationId, "keywords");
560
+ return data?.data?.keywordsByLocationId ?? [];
561
+ },
562
+ /**
563
+ * Get ranking performance for a location's keywords over an optional date range.
564
+ */
565
+ async fetchKeywordsPerformance(locationId, options = {}) {
566
+ const params = {};
567
+ if (options.fromDate !== void 0) params.fromDate = options.fromDate;
568
+ if (options.toDate !== void 0) params.toDate = options.toDate;
569
+ const data = await http.listingsGet(locationId, "keywords-performance", params);
570
+ return data?.data?.keywordsByLocationId ?? [];
571
+ },
572
+ /**
573
+ * Add keywords to a location for ranking tracking.
574
+ */
575
+ async addKeywords(locationId, keywords) {
576
+ const data = await http.apiPost("locations/keywords", {
577
+ locationId: http.encodeLocationId(locationId),
578
+ inputKeywords: keywords
579
+ });
580
+ const addResult = data?.data?.addKeywords ?? {};
581
+ return addResult.keywords ?? [];
582
+ },
583
+ /**
584
+ * Archive a keyword so it is no longer tracked.
585
+ */
586
+ async archiveKeyword(keywordId) {
587
+ const data = await http.apiPost("locations/keywords/archive", {
588
+ id: keywordId
589
+ });
590
+ const archiveResult = data?.data?.archiveKeyword ?? {};
591
+ return archiveResult.keyword ?? {};
592
+ },
593
+ /**
594
+ * Get ranking analytics timeline (positions over time) for locations and a source.
595
+ */
596
+ async fetchRankingAnalyticsTimeline(input) {
597
+ const encodedIds = input.locationIds.map((id) => http.encodeLocationId(id));
598
+ const data = await http.apiPost("locations/ranking-analytics-timeline", {
599
+ fromDate: input.fromDate,
600
+ toDate: input.toDate,
601
+ locationIds: encodedIds,
602
+ source: input.source
603
+ });
604
+ return data?.data?.rankingsRollupByDate ?? [];
605
+ },
606
+ /**
607
+ * Get ranking histogram by keyword count for locations and a source.
608
+ */
609
+ async fetchRankingSitewiseHistogram(input) {
610
+ const encodedIds = input.locationIds.map((id) => http.encodeLocationId(id));
611
+ const data = await http.apiPost("locations/ranking-sitewise-histogram", {
612
+ fromDate: input.fromDate,
613
+ toDate: input.toDate,
614
+ locationIds: encodedIds,
615
+ source: input.source
616
+ });
617
+ return data?.data?.rankingsRollupByKeywordCount ?? [];
618
+ }
619
+ };
620
+ }
621
+
622
+ // src/resources/campaigns.ts
623
+ function createCampaignMethods(http) {
624
+ return {
625
+ /**
626
+ * Get review campaigns for a location, optionally filtered by date range.
627
+ */
628
+ async fetchReviewCampaigns(locationId, options = {}) {
629
+ const params = {};
630
+ if (options.startDate !== void 0) params.startDate = options.startDate;
631
+ if (options.endDate !== void 0) params.endDate = options.endDate;
632
+ const data = await http.listingsGet(locationId, "review-campaigns", params);
633
+ const listData = data?.data?.listReviewCampaigns ?? {};
634
+ return listData.reviewCampaigns ?? [];
635
+ },
636
+ /**
637
+ * Get customer details for a specific review campaign.
638
+ */
639
+ async fetchReviewCampaignCustomers(reviewCampaignId) {
640
+ const data = await http.apiGet(
641
+ `locations/review-campaigns/${reviewCampaignId}/customers`
642
+ );
643
+ return data?.data?.reviewCampaignInfo ?? {};
644
+ },
645
+ /**
646
+ * Create a review campaign for a location with customers and optional templates.
647
+ */
648
+ async createReviewCampaign(input) {
649
+ const payload = {
650
+ locationId: http.encodeLocationId(input.locationId),
651
+ name: input.name,
652
+ locationCustomers: input.locationCustomers
653
+ };
654
+ if (input.screening !== void 0) payload.screening = input.screening;
655
+ if (input.landingPageTemplate !== void 0) payload.landingPageTemplate = input.landingPageTemplate;
656
+ if (input.openingEmailTemplate !== void 0) payload.openingEmailTemplate = input.openingEmailTemplate;
657
+ if (input.smsTemplate !== void 0) payload.smsTemplate = input.smsTemplate;
658
+ if (input.emailDetails !== void 0) payload.emailDetails = input.emailDetails;
659
+ if (input.smsDetails !== void 0) payload.smsDetails = input.smsDetails;
660
+ const data = await http.apiPost("locations/review-campaigns", { input: payload });
661
+ return data?.data?.createReviewCampaign ?? {};
662
+ },
663
+ /**
664
+ * Add customers to an existing review campaign.
665
+ */
666
+ async addReviewCampaignCustomers(reviewCampaignId, locationCustomers) {
667
+ const data = await http.apiPost("locations/review-campaigns/customers", {
668
+ input: {
669
+ reviewCampaignId,
670
+ locationCustomers
671
+ }
672
+ });
673
+ return data?.data?.addCustomersToReviewCampaign ?? {};
674
+ }
675
+ };
676
+ }
677
+
678
+ // src/resources/analytics.ts
679
+ function createAnalyticsMethods(http) {
680
+ return {
681
+ /**
682
+ * Get Google (GMB) profile analytics for a location.
683
+ */
684
+ async fetchGoogleAnalytics(locationId, options) {
685
+ const params = {};
686
+ if (options?.fromDate) params.fromDate = options.fromDate;
687
+ if (options?.toDate) params.toDate = options.toDate;
688
+ const data = await http.listingsGet(locationId, "google-analytics", params);
689
+ return data?.data?.googleInsights ?? {};
690
+ },
691
+ /**
692
+ * Get Bing profile analytics for a location.
693
+ */
694
+ async fetchBingAnalytics(locationId, options) {
695
+ const params = {};
696
+ if (options?.fromDate) params.fromDate = options.fromDate;
697
+ if (options?.toDate) params.toDate = options.toDate;
698
+ const data = await http.listingsGet(locationId, "bing-analytics", params);
699
+ return data?.data?.bingInsights ?? {};
700
+ },
701
+ /**
702
+ * Get Facebook page analytics for a location.
703
+ */
704
+ async fetchFacebookAnalytics(locationId, options) {
705
+ const params = {};
706
+ if (options?.fromDate) params.fromDate = options.fromDate;
707
+ if (options?.toDate) params.toDate = options.toDate;
708
+ const data = await http.listingsGet(locationId, "facebook-analytics", params);
709
+ return data?.data?.facebookInsights ?? {};
710
+ }
711
+ };
712
+ }
713
+
714
+ // src/resources/photos.ts
715
+ function createPhotoMethods(http) {
716
+ return {
717
+ /**
718
+ * Get photos and media attached to a location.
719
+ */
720
+ async fetchLocationPhotos(locationId) {
721
+ const data = await http.listingsGet(locationId, "photos");
722
+ return data?.data?.mediaFilesOfLocation ?? [];
723
+ },
724
+ /**
725
+ * Add one or more photos to a location.
726
+ */
727
+ async addLocationPhotos(locationId, photos) {
728
+ const data = await http.apiPost("locations/photos", {
729
+ input: {
730
+ locationId: http.encodeLocationId(locationId),
731
+ photos
732
+ }
733
+ });
734
+ return data?.data?.addLocationPhotos ?? {};
735
+ },
736
+ /**
737
+ * Remove photos from a location by photo IDs.
738
+ */
739
+ async removeLocationPhotos(locationId, photoIds) {
740
+ const data = await http.apiPost("locations/photos/remove", {
741
+ input: {
742
+ locationId: http.encodeLocationId(locationId),
743
+ photoIds
744
+ }
745
+ });
746
+ return data?.data?.removeLocationPhotos ?? {};
747
+ },
748
+ /**
749
+ * Star or unstar location photos.
750
+ */
751
+ async starLocationPhotos(locationId, mediaIds, starred) {
752
+ const data = await http.apiPost("locations/photos/star", {
753
+ input: {
754
+ locationId: http.encodeLocationId(locationId),
755
+ mediaIds,
756
+ starred
757
+ }
758
+ });
759
+ return data?.data?.starUnstarLocationPhotos ?? {};
760
+ },
761
+ /**
762
+ * Get the processing status for a bulk photo upload request.
763
+ */
764
+ async fetchPhotoUploadStatus(requestId) {
765
+ const data = await http.apiGet(`locations/photos/requests/${requestId}`);
766
+ return data?.data?.bulkImageProcessingStatus ?? {};
767
+ }
768
+ };
769
+ }
770
+
771
+ // src/resources/folders.ts
772
+ function createFolderMethods(http) {
773
+ return {
774
+ /**
775
+ * Get all folders as a flat list.
776
+ */
777
+ async fetchFoldersFlat() {
778
+ const data = await http.apiGet("folders/flat");
779
+ return data?.data?.getUserFolders ?? [];
780
+ },
781
+ /**
782
+ * Get all folders as a nested tree structure.
783
+ */
784
+ async fetchFoldersTree() {
785
+ const data = await http.apiGet("folders/tree");
786
+ return data?.data?.getFolderTree ?? [];
787
+ },
788
+ /**
789
+ * Get details for a specific folder by ID or name.
790
+ */
791
+ async fetchFolderDetails(options) {
792
+ const params = {};
793
+ if (options.folderId) params.folderId = options.folderId;
794
+ if (options.folderName) params.folderName = options.folderName;
795
+ const data = await http.apiGet("folder-details", params);
796
+ return data?.data?.getFolderDetails ?? {};
797
+ },
798
+ /**
799
+ * Create a folder to organize locations.
800
+ */
801
+ async createFolder(name, options) {
802
+ const payload = { name };
803
+ if (options?.parentFolder) payload.parentFolder = options.parentFolder;
804
+ if (options?.parentFolderName) payload.parentFolderName = options.parentFolderName;
805
+ const data = await http.apiPost("folders/create", { input: payload });
806
+ return data?.data?.createFolder ?? {};
807
+ },
808
+ /**
809
+ * Rename an existing folder.
810
+ */
811
+ async renameFolder(oldName, newName) {
812
+ const data = await http.apiPost("locations/folders/rename", {
813
+ input: { oldName, name: newName }
814
+ });
815
+ return data?.data?.renameFolder ?? {};
816
+ },
817
+ /**
818
+ * Delete a folder by name. Locations in the folder are not deleted.
819
+ */
820
+ async deleteFolder(name) {
821
+ const data = await http.apiPost("folders/delete", { input: { name } });
822
+ return data?.data?.deleteFolder ?? {};
823
+ },
824
+ /**
825
+ * Add locations to a folder. Folder is created if it does not exist.
826
+ */
827
+ async addLocationsToFolder(folderName, locationIds) {
828
+ const encodedIds = locationIds.map((id) => http.encodeLocationId(id));
829
+ const data = await http.apiPost("locations/folders", {
830
+ input: { name: folderName, locationIds: encodedIds }
831
+ });
832
+ return data?.data?.addLocationsToFolder ?? {};
833
+ },
834
+ /**
835
+ * Remove locations from their current folder.
836
+ */
837
+ async removeLocationsFromFolder(locationIds) {
838
+ const encodedIds = locationIds.map((id) => http.encodeLocationId(id));
839
+ const data = await http.apiPost("locations/folders/remove", {
840
+ input: { locationIds: encodedIds }
841
+ });
842
+ return data?.data?.deleteLocationsFromFolder ?? {};
843
+ }
844
+ };
845
+ }
846
+
847
+ // src/resources/tags.ts
848
+ function createTagMethods(http) {
849
+ return {
850
+ /**
851
+ * Get all tags defined in the account.
852
+ */
853
+ async fetchTags() {
854
+ const data = await http.apiGet("tags");
855
+ return data?.data?.listAllTags ?? [];
856
+ },
857
+ /**
858
+ * Add a tag to a location. Tag is created if it does not exist.
859
+ */
860
+ async addLocationTag(locationId, tag) {
861
+ const data = await http.apiPost("locations/tags", {
862
+ input: {
863
+ locationId: http.encodeLocationId(locationId),
864
+ tag
865
+ }
866
+ });
867
+ return data?.data?.addTag ?? {};
868
+ },
869
+ /**
870
+ * Remove a tag from a location.
871
+ */
872
+ async removeLocationTag(locationId, tag) {
873
+ const data = await http.apiPost("locations/tags/remove", {
874
+ input: {
875
+ locationId: http.encodeLocationId(locationId),
876
+ tag
877
+ }
878
+ });
879
+ return data?.data?.removeTag ?? {};
880
+ }
881
+ };
882
+ }
883
+
884
+ // src/resources/users.ts
885
+ function createUserMethods(http) {
886
+ return {
887
+ /**
888
+ * Get all users in the account.
889
+ */
890
+ async fetchUsers() {
891
+ const data = await http.apiGet("users");
892
+ return data?.data?.users ?? [];
893
+ },
894
+ /**
895
+ * Get users by a list of user IDs.
896
+ */
897
+ async fetchUsersByIds(userIds) {
898
+ if (!userIds.length) return [];
899
+ const data = await http.apiGet("users-by-ids", {
900
+ userIds: JSON.stringify(userIds)
901
+ });
902
+ return data?.data?.usersByIds ?? [];
903
+ },
904
+ /**
905
+ * Get resources (locations, folders, etc.) assigned to a specific user.
906
+ */
907
+ async fetchUserResources(userId) {
908
+ const data = await http.apiGet(`users/${userId}/resources`);
909
+ return data?.data?.listUserResources ?? [];
910
+ },
911
+ /**
912
+ * Get all roles defined in the account.
913
+ */
914
+ async fetchRoles() {
915
+ const data = await http.apiGet("roles");
916
+ return data?.data?.fetchAccountRoles ?? [];
917
+ },
918
+ /**
919
+ * Create a user in the account with the given role.
920
+ */
921
+ async createUser(input) {
922
+ const { email, roleId, firstName, lastName, directCustomer, ...extra } = input;
923
+ const payload = { email, roleId, firstName };
924
+ if (lastName !== void 0) payload.lastName = lastName;
925
+ if (directCustomer !== void 0) payload.directCustomer = directCustomer;
926
+ Object.assign(payload, extra);
927
+ const data = await http.apiPost("users/create", { input: payload });
928
+ return data?.data?.addUser ?? {};
929
+ },
930
+ /**
931
+ * Update a user. Pass userId and any fields to change.
932
+ */
933
+ async updateUser(input) {
934
+ const { userId, email, roleId, firstName, lastName, phone, archived, directCustomer, ...extra } = input;
935
+ const payload = { id: userId };
936
+ if (email !== void 0) payload.email = email;
937
+ if (roleId !== void 0) payload.roleId = roleId;
938
+ if (firstName !== void 0) payload.firstName = firstName;
939
+ if (lastName !== void 0) payload.lastName = lastName;
940
+ if (phone !== void 0) payload.phone = phone;
941
+ if (archived !== void 0) payload.archived = archived;
942
+ if (directCustomer !== void 0) payload.directCustomer = directCustomer;
943
+ Object.assign(payload, extra);
944
+ const data = await http.apiPost("users/update", { input: payload });
945
+ return data?.data?.updateUser ?? {};
946
+ },
947
+ /**
948
+ * Assign locations to a user.
949
+ */
950
+ async addUserLocations(userId, locationIds) {
951
+ const encodedIds = locationIds.map((id) => http.encodeLocationId(id));
952
+ const data = await http.apiPost("users/locations/add", {
953
+ input: { userId, locationIds: encodedIds }
954
+ });
955
+ return data?.data?.addLocationsForUser ?? {};
956
+ },
957
+ /**
958
+ * Remove location assignments from a user.
959
+ */
960
+ async removeUserLocations(userId, locationIds) {
961
+ const encodedIds = locationIds.map((id) => http.encodeLocationId(id));
962
+ const data = await http.apiPost("users/locations/remove", {
963
+ input: { userId, locationIds: encodedIds }
964
+ });
965
+ return data?.data?.removeLocationsForUser ?? {};
966
+ },
967
+ /**
968
+ * Assign folders to a user.
969
+ */
970
+ async addUserFolders(userId, folderIds) {
971
+ const data = await http.apiPost("users/folders/add", {
972
+ input: { userId, folderIds }
973
+ });
974
+ return data?.data?.addFoldersForUser ?? {};
975
+ },
976
+ /**
977
+ * Remove folder assignments from a user.
978
+ */
979
+ async removeUserFolders(userId, folderIds) {
980
+ const data = await http.apiPost("users/folders/remove", {
981
+ input: { userId, folderIds }
982
+ });
983
+ return data?.data?.removeFoldersForUser ?? {};
984
+ },
985
+ /**
986
+ * Create a user and a folder, then assign the folder to the user in one call.
987
+ */
988
+ async addUserAndFolder(input) {
989
+ const data = await http.apiPost("users/add_user_and_folder", { input });
990
+ return data?.data?.addUserAndFolder ?? {};
991
+ }
992
+ };
993
+ }
994
+
995
+ // src/resources/connections.ts
996
+ function createConnectionMethods(http) {
997
+ return {
998
+ /**
999
+ * Get a URL to connect a Google or Facebook profile to a location.
1000
+ */
1001
+ async getOauthConnectUrl(locationId, site, successUrl, errorUrl) {
1002
+ const data = await http.apiPost("locations/oauth_connect_url", {
1003
+ input: {
1004
+ locationId: http.encodeLocationId(locationId),
1005
+ site: site.toUpperCase(),
1006
+ successUrl,
1007
+ errorUrl
1008
+ }
1009
+ });
1010
+ return data?.data?.connectUrl ?? {};
1011
+ },
1012
+ /**
1013
+ * Disconnect a Google or Facebook profile from a location.
1014
+ */
1015
+ async oauthDisconnect(locationId, site) {
1016
+ const data = await http.apiPost("locations/oauth-disconnect", {
1017
+ input: {
1018
+ locationId: http.encodeLocationId(locationId),
1019
+ site: site.toUpperCase()
1020
+ }
1021
+ });
1022
+ return data?.data?.disconnectConnectedAccountsLocations ?? {};
1023
+ },
1024
+ /**
1025
+ * Get a URL to connect a Google account for bulk use across locations.
1026
+ */
1027
+ async connectGoogleAccount(successUrl, errorUrl) {
1028
+ const data = await http.apiPost("connected-accounts/connect-google", {
1029
+ input: { successUrl, errorUrl }
1030
+ });
1031
+ return data?.data?.bulkConnectLinkForGoogle ?? {};
1032
+ },
1033
+ /**
1034
+ * Get a URL to connect a Facebook account for bulk use across locations.
1035
+ */
1036
+ async connectFacebookAccount(successUrl, errorUrl) {
1037
+ const data = await http.apiPost("connected-accounts/connect-facebook", {
1038
+ input: { successUrl, errorUrl }
1039
+ });
1040
+ return data?.data?.bulkConnectLinkForFacebook ?? {};
1041
+ },
1042
+ /**
1043
+ * Disconnect a Google connected account (bulk).
1044
+ */
1045
+ async disconnectGoogleAccount(connectedAccountId) {
1046
+ const data = await http.apiPost("connected-accounts/disconnect-google", {
1047
+ input: { connectedAccountId }
1048
+ });
1049
+ return data?.data?.gmbBulkDisconnect ?? {};
1050
+ },
1051
+ /**
1052
+ * Disconnect a Facebook connected account (bulk).
1053
+ */
1054
+ async disconnectFacebookAccount(connectedAccountId) {
1055
+ const data = await http.apiPost("connected-accounts/disconnect-facebook", {
1056
+ input: { connectedAccountId }
1057
+ });
1058
+ return data?.data?.fbBulkDisconnect ?? {};
1059
+ },
1060
+ /**
1061
+ * Get connected third-party accounts with optional filters.
1062
+ */
1063
+ async fetchConnectedAccounts(options) {
1064
+ const params = {};
1065
+ if (options?.publisher !== void 0) params.publisher = options.publisher;
1066
+ if (options?.status !== void 0) params.status = options.status;
1067
+ if (options?.page !== void 0) params.page = options.page;
1068
+ if (options?.perPage !== void 0) params.perPage = options.perPage;
1069
+ const data = await http.apiGet("connected-accounts", params);
1070
+ return data?.data?.connectedAccountsInfo ?? {};
1071
+ },
1072
+ /**
1073
+ * Get detailed information about a specific connected account.
1074
+ */
1075
+ async fetchConnectedAccountDetails(connectedAccountId) {
1076
+ const data = await http.apiGet(
1077
+ `connected-accounts/${connectedAccountId}/details`
1078
+ );
1079
+ return data?.data?.connectedAccountDetails ?? {};
1080
+ },
1081
+ /**
1082
+ * Get folders (business groups) under a connected Google account.
1083
+ */
1084
+ async fetchConnectedAccountFolders(connectedAccountId, options) {
1085
+ const params = {};
1086
+ if (options?.folderName) params.folderName = options.folderName;
1087
+ const data = await http.apiGet(
1088
+ `connected-accounts/${connectedAccountId}/folders`,
1089
+ params
1090
+ );
1091
+ return data?.data?.getFoldersUnderGoogleAccount ?? [];
1092
+ },
1093
+ /**
1094
+ * Get listings the connected Google/Facebook account has access to.
1095
+ */
1096
+ async fetchConnectedAccountListings(connectedAccountId, options) {
1097
+ const payload = { connectedAccountId };
1098
+ if (options?.locationInfo !== void 0) payload.locationInfo = options.locationInfo;
1099
+ if (options?.page !== void 0) payload.page = options.page;
1100
+ if (options?.perPage !== void 0) payload.perPage = options.perPage;
1101
+ const data = await http.apiPost(
1102
+ "connected-accounts/connected-account-listings",
1103
+ payload
1104
+ );
1105
+ return data?.data?.connectedAccountListings ?? {};
1106
+ },
1107
+ /**
1108
+ * Get suggested matches between a connected account's listings and Synup locations.
1109
+ */
1110
+ async fetchConnectionSuggestions(connectedAccountId, options) {
1111
+ const params = {};
1112
+ if (options?.page !== void 0) params.page = options.page;
1113
+ if (options?.perPage !== void 0) params.perPage = options.perPage;
1114
+ const data = await http.apiGet(
1115
+ `connected-accounts/${connectedAccountId}/connection-suggestions`,
1116
+ params
1117
+ );
1118
+ return data?.data?.connectionSuggestionsForAccount ?? {};
1119
+ },
1120
+ /**
1121
+ * Trigger matching of Google/Facebook profile locations to Synup locations.
1122
+ */
1123
+ async triggerConnectedAccountMatches(connectedAccountIds) {
1124
+ const data = await http.apiPost("connected-accounts/trigger-matches", {
1125
+ input: { connectedAccountIds }
1126
+ });
1127
+ return data?.data?.connectedAccountsTriggerMatches ?? {};
1128
+ },
1129
+ /**
1130
+ * Confirm suggested matches between connected account listings and Synup locations.
1131
+ */
1132
+ async confirmConnectedAccountMatches(matchRecordIds) {
1133
+ const data = await http.apiPost("connected-accounts/confirm-matches", {
1134
+ input: { matchRecordIds }
1135
+ });
1136
+ return data?.data?.confirmConnectMatches ?? {};
1137
+ },
1138
+ /**
1139
+ * Link a Synup location to a listing from a connected Google/Facebook account.
1140
+ */
1141
+ async connectListing(locationId, connectedAccountListingId, connectedAccountId) {
1142
+ const data = await http.apiPost("connected-accounts/connect-listing", {
1143
+ input: {
1144
+ locationId: http.encodeLocationId(locationId),
1145
+ connectedAccountListingId,
1146
+ connectedAccountId
1147
+ }
1148
+ });
1149
+ return data?.data?.connectListing ?? {};
1150
+ },
1151
+ /**
1152
+ * Unlink a location from its Google or Facebook listing.
1153
+ */
1154
+ async disconnectListing(locationId, site) {
1155
+ const data = await http.apiPost("connected-accounts/disconnect-listing", {
1156
+ input: {
1157
+ locationId: http.encodeLocationId(locationId),
1158
+ site: site.toUpperCase()
1159
+ }
1160
+ });
1161
+ return data?.data?.disconnectConnectedAccountsLocations ?? {};
1162
+ },
1163
+ /**
1164
+ * Create a Google Business Profile listing for an existing Synup location.
1165
+ */
1166
+ async createGmbListing(locationId, connectedAccountId, options) {
1167
+ const payload = {
1168
+ locationId: http.encodeLocationId(locationId),
1169
+ connectedAccountId
1170
+ };
1171
+ if (options?.folderId) payload.folderId = options.folderId;
1172
+ const data = await http.apiPost("locations/create/gmb-listing", {
1173
+ input: payload
1174
+ });
1175
+ return data?.data?.createGmbListingForLocation ?? {};
1176
+ }
1177
+ };
1178
+ }
1179
+
1180
+ // src/resources/grid-rank.ts
1181
+ function createGridRankMethods(http) {
1182
+ return {
1183
+ /**
1184
+ * Create a Local Rank Grid report.
1185
+ */
1186
+ async createGridReport(input) {
1187
+ const body = {
1188
+ locationId: http.encodeLocationId(input.locationId),
1189
+ keywords: input.keywords,
1190
+ businessName: input.businessName,
1191
+ businessStreet: input.businessStreet,
1192
+ businessCity: input.businessCity,
1193
+ businessState: input.businessState,
1194
+ businessCountry: input.businessCountry,
1195
+ latitude: input.latitude,
1196
+ longitude: input.longitude,
1197
+ distance: input.distance,
1198
+ distanceUnit: input.distanceUnit,
1199
+ gridSize: input.gridSize
1200
+ };
1201
+ const data = await http.apiPost("create-grid-report", body);
1202
+ return data?.data?.createGridrankReport ?? {};
1203
+ },
1204
+ /**
1205
+ * Get a Local Rank Grid report by its ID.
1206
+ */
1207
+ async fetchGridReport(reportId) {
1208
+ const data = await http.apiGet(`grid-report/${reportId}`);
1209
+ return data?.data?.gridrankReportById ?? {};
1210
+ },
1211
+ /**
1212
+ * Get all Local Rank Grid reports for a location with optional filters.
1213
+ */
1214
+ async fetchLocationGridReports(locationId, options) {
1215
+ const params = {};
1216
+ if (options?.searchString !== void 0) params.searchString = options.searchString;
1217
+ if (options?.gridSize !== void 0) params.gridSize = options.gridSize;
1218
+ if (options?.fromDate !== void 0) params.fromDate = options.fromDate;
1219
+ if (options?.toDate !== void 0) params.toDate = options.toDate;
1220
+ if (options?.sortField !== void 0) params.sortField = options.sortField;
1221
+ if (options?.sortOrder !== void 0) params.sortOrder = options.sortOrder;
1222
+ if (options?.pageSize !== void 0) params.pageSize = options.pageSize;
1223
+ if (options?.page !== void 0) params.page = options.page;
1224
+ const data = await http.listingsGet(locationId, "grid-reports", params);
1225
+ const listData = data?.data?.allGridrankReports ?? {};
1226
+ return {
1227
+ reports: listData.reports ?? [],
1228
+ total: listData.total
1229
+ };
1230
+ }
1231
+ };
1232
+ }
1233
+
1234
+ // src/resources/account.ts
1235
+ function createAccountMethods(http) {
1236
+ return {
1237
+ /**
1238
+ * Get supported directories and site details for your plan.
1239
+ */
1240
+ async fetchPlanSites() {
1241
+ const data = await http.apiGet("plan-sites");
1242
+ return data?.data?.planSites ?? [];
1243
+ },
1244
+ /**
1245
+ * Get supported countries and states.
1246
+ */
1247
+ async fetchCountries() {
1248
+ const data = await http.apiGet("countries");
1249
+ return data?.data?.supportedCountries ?? [];
1250
+ },
1251
+ /**
1252
+ * Get active subscriptions for the account.
1253
+ */
1254
+ async fetchSubscriptions() {
1255
+ const data = await http.apiGet("subscriptions");
1256
+ return data?.data?.activeSubscriptions ?? [];
1257
+ },
1258
+ /**
1259
+ * Get OAuth connection status for a location.
1260
+ */
1261
+ async fetchConnectionInfo(locationId) {
1262
+ const data = await http.listingsGet(locationId, "connection_info");
1263
+ return data?.data?.locationConnectionInfo ?? {};
1264
+ },
1265
+ /**
1266
+ * Create an automation that temporarily closes a location and reopens on a set date.
1267
+ */
1268
+ async createTemporaryCloseAutomation(input) {
1269
+ const data = await http.apiPost(
1270
+ "automations/temporary-close-location-with-reopening-date",
1271
+ {
1272
+ input: {
1273
+ name: input.name,
1274
+ startDate: input.startDate,
1275
+ startTime: input.startTime,
1276
+ endDate: input.endDate,
1277
+ locationId: http.encodeLocationId(input.locationId)
1278
+ }
1279
+ }
1280
+ );
1281
+ return data?.data?.createFlowFromRecipe ?? {};
1282
+ }
1283
+ };
1284
+ }
1285
+
1286
+ // src/client.ts
1287
+ var DEFAULT_BASE_URL = "https://api.synup.com";
1288
+ function encodeLocationId(idValue) {
1289
+ if (typeof idValue === "number") {
1290
+ return btoa(`Location:${idValue}`);
1291
+ }
1292
+ const s = String(idValue).trim();
1293
+ if (/^\d+$/.test(s)) {
1294
+ return btoa(`Location:${s}`);
1295
+ }
1296
+ return s;
1297
+ }
1298
+ function buildQueryString(params) {
1299
+ const entries = Object.entries(params).filter(
1300
+ ([, v]) => v !== void 0 && v !== null
1301
+ );
1302
+ if (entries.length === 0) return "";
1303
+ const searchParams = new URLSearchParams();
1304
+ for (const [key, value] of entries) {
1305
+ searchParams.set(key, String(value));
1306
+ }
1307
+ return `?${searchParams.toString()}`;
1308
+ }
1309
+ var SynupClient = class {
1310
+ baseUrl;
1311
+ headers;
1312
+ constructor(options) {
1313
+ this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
1314
+ this.headers = {
1315
+ Authorization: `API ${options.apiKey}`,
1316
+ "Content-Type": "application/json"
1317
+ };
1318
+ const http = {
1319
+ apiGet: this.apiGet.bind(this),
1320
+ apiPost: this.apiPost.bind(this),
1321
+ listingsGet: this.listingsGet.bind(this),
1322
+ encodeLocationId
1323
+ };
1324
+ Object.assign(this, createLocationMethods(http));
1325
+ Object.assign(this, createListingMethods(http));
1326
+ Object.assign(this, createReviewMethods(http));
1327
+ Object.assign(this, createKeywordMethods(http));
1328
+ Object.assign(this, createCampaignMethods(http));
1329
+ Object.assign(this, createAnalyticsMethods(http));
1330
+ Object.assign(this, createPhotoMethods(http));
1331
+ Object.assign(this, createFolderMethods(http));
1332
+ Object.assign(this, createTagMethods(http));
1333
+ Object.assign(this, createUserMethods(http));
1334
+ Object.assign(this, createConnectionMethods(http));
1335
+ Object.assign(this, createGridRankMethods(http));
1336
+ Object.assign(this, createAccountMethods(http));
1337
+ }
1338
+ /**
1339
+ * GET request to an account-level API endpoint.
1340
+ */
1341
+ async apiGet(path, params) {
1342
+ const qs = params ? buildQueryString(params) : "";
1343
+ const url = `${this.baseUrl}/api/v4/${path}${qs}`;
1344
+ const response = await fetch(url, {
1345
+ method: "GET",
1346
+ headers: this.headers
1347
+ });
1348
+ if (!response.ok) {
1349
+ const body = await response.text();
1350
+ throw new SynupAPIError(
1351
+ `API request failed: ${response.status}`,
1352
+ response.status,
1353
+ body
1354
+ );
1355
+ }
1356
+ return response.json();
1357
+ }
1358
+ /**
1359
+ * POST request to an account-level API endpoint.
1360
+ */
1361
+ async apiPost(path, body) {
1362
+ const url = `${this.baseUrl}/api/v4/${path}`;
1363
+ const response = await fetch(url, {
1364
+ method: "POST",
1365
+ headers: this.headers,
1366
+ body: JSON.stringify(body)
1367
+ });
1368
+ if (!response.ok) {
1369
+ const bodyText = await response.text();
1370
+ throw new SynupAPIError(
1371
+ `API request failed: ${response.status}`,
1372
+ response.status,
1373
+ bodyText
1374
+ );
1375
+ }
1376
+ return response.json();
1377
+ }
1378
+ /**
1379
+ * GET request to a location-scoped API endpoint.
1380
+ */
1381
+ async listingsGet(locationId, path, params) {
1382
+ const encodedId = encodeLocationId(locationId);
1383
+ const qs = params ? buildQueryString(params) : "";
1384
+ const url = `${this.baseUrl}/api/v4/locations/${encodedId}/${path}${qs}`;
1385
+ const response = await fetch(url, {
1386
+ method: "GET",
1387
+ headers: this.headers
1388
+ });
1389
+ if (!response.ok) {
1390
+ const body = await response.text();
1391
+ throw new SynupAPIError(
1392
+ `API request failed: ${response.status}`,
1393
+ response.status,
1394
+ body
1395
+ );
1396
+ }
1397
+ return response.json();
1398
+ }
1399
+ };
1400
+ // Annotate the CommonJS export names for ESM import in node:
1401
+ 0 && (module.exports = {
1402
+ SynupAPIError,
1403
+ SynupClient,
1404
+ encodeLocationId
1405
+ });