upload-post 2.0.4 → 2.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.
Files changed (4) hide show
  1. package/README.md +11 -0
  2. package/index.d.ts +82 -7
  3. package/index.js +145 -16
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -123,6 +123,13 @@ const status = await client.getStatus('request_id_from_upload');
123
123
  console.log(status);
124
124
  ```
125
125
 
126
+ For scheduled or queued posts, check the status using the job_id:
127
+
128
+ ```javascript
129
+ const status = await client.getJobStatus('job_id_from_scheduled_post');
130
+ console.log(status);
131
+ ```
132
+
126
133
  ### Get Upload History
127
134
 
128
135
  ```javascript
@@ -267,6 +274,7 @@ const boards = await client.getPinterestBoards('my-profile');
267
274
  ### Threads
268
275
  - `threadsLongTextAsPost` - Post long text as single post (vs thread)
269
276
  - `threadsThreadMediaLayout` - Comma-separated list of how many media items to include in each Threads post. Each value must be 1-10, and the total must equal the number of files. Example: '5,5' splits 10 items into 2 posts with 5 each. If omitted and more than 10 items are provided, auto-chunks into groups of 10.
277
+ - `threadsTopicTag` - Topic tag for the Threads post (1-50 characters, no periods or ampersands). One tag per post. Helps increase reach.
270
278
 
271
279
  ### Reddit
272
280
  - `redditSubreddit` - Subreddit name (without r/)
@@ -286,6 +294,7 @@ These options work across all upload methods:
286
294
  | `scheduledDate` | ISO date for scheduling |
287
295
  | `timezone` | Timezone for scheduled date |
288
296
  | `addToQueue` | Add to posting queue |
297
+ | `maxPostsPerSlot` | Max posts per queue slot (overrides profile setting) |
289
298
  | `asyncUpload` | Process asynchronously (default: true) |
290
299
 
291
300
  ## TypeScript Support
@@ -327,3 +336,5 @@ try {
327
336
  ## License
328
337
 
329
338
  MIT
339
+
340
+ <!-- deployed 2026-03-16 17:49 UTC -->
package/index.d.ts CHANGED
@@ -2,13 +2,13 @@ declare module 'upload-post' {
2
2
  // ==================== Common Types ====================
3
3
 
4
4
  /** Supported platforms for video upload */
5
- export type VideoPlatform = 'tiktok' | 'instagram' | 'youtube' | 'linkedin' | 'facebook' | 'pinterest' | 'threads' | 'bluesky' | 'x';
5
+ export type VideoPlatform = 'tiktok' | 'instagram' | 'youtube' | 'linkedin' | 'facebook' | 'pinterest' | 'threads' | 'bluesky' | 'x' | 'google_business';
6
6
 
7
7
  /** Supported platforms for photo upload */
8
- export type PhotoPlatform = 'tiktok' | 'instagram' | 'linkedin' | 'facebook' | 'pinterest' | 'threads' | 'reddit' | 'bluesky' | 'x';
8
+ export type PhotoPlatform = 'tiktok' | 'instagram' | 'linkedin' | 'facebook' | 'pinterest' | 'threads' | 'reddit' | 'bluesky' | 'x' | 'google_business';
9
9
 
10
10
  /** Supported platforms for text upload */
11
- export type TextPlatform = 'x' | 'linkedin' | 'facebook' | 'threads' | 'reddit' | 'bluesky';
11
+ export type TextPlatform = 'x' | 'linkedin' | 'facebook' | 'threads' | 'reddit' | 'bluesky' | 'google_business';
12
12
 
13
13
  /** TikTok privacy levels */
14
14
  export type TikTokPrivacyLevel = 'PUBLIC_TO_EVERYONE' | 'MUTUAL_FOLLOW_FRIENDS' | 'FOLLOWER_OF_CREATOR' | 'SELF_ONLY';
@@ -43,8 +43,8 @@ declare module 'upload-post' {
43
43
  // ==================== Common Options ====================
44
44
 
45
45
  export interface CommonUploadOptions {
46
- /** Post title/caption */
47
- title: string;
46
+ /** Post title/caption. Required for YouTube, Reddit, and text posts. Optional for TikTok, Instagram, Facebook, LinkedIn, X, Threads, Bluesky, Pinterest. */
47
+ title?: string;
48
48
  /** User identifier (profile name) */
49
49
  user: string;
50
50
  /** First comment to post after publishing */
@@ -57,6 +57,8 @@ declare module 'upload-post' {
57
57
  timezone?: string;
58
58
  /** Add to posting queue instead of immediate post */
59
59
  addToQueue?: boolean;
60
+ /** Maximum number of posts allowed per queue slot (overrides profile setting). Only used when addToQueue is true. */
61
+ maxPostsPerSlot?: number;
60
62
  /** Process upload asynchronously (default: true) */
61
63
  asyncUpload?: boolean;
62
64
 
@@ -343,12 +345,22 @@ declare module 'upload-post' {
343
345
  platforms: PhotoPlatform[];
344
346
  }
345
347
 
348
+ export interface LinkPreviewOptions {
349
+ /** Generic URL for link preview card (works for LinkedIn, Bluesky, Facebook). Platform-specific params take priority. */
350
+ linkUrl?: string;
351
+ /** URL for link preview card on LinkedIn */
352
+ linkedinLinkUrl?: string;
353
+ /** URL for external embed link preview on Bluesky */
354
+ blueskyLinkUrl?: string;
355
+ }
356
+
346
357
  export interface UploadTextOptions extends CommonUploadOptions,
347
358
  LinkedInOptions,
348
359
  FacebookTextOptions,
349
360
  XTextOptions,
350
361
  ThreadsOptions,
351
- RedditOptions {
362
+ RedditOptions,
363
+ LinkPreviewOptions {
352
364
  /** Target platforms */
353
365
  platforms: TextPlatform[];
354
366
  }
@@ -370,6 +382,8 @@ declare module 'upload-post' {
370
382
  timezone?: string;
371
383
  /** Add to posting queue */
372
384
  addToQueue?: boolean;
385
+ /** Maximum number of posts allowed per queue slot (overrides profile setting). Only used when addToQueue is true. */
386
+ maxPostsPerSlot?: number;
373
387
  /** Process upload asynchronously */
374
388
  asyncUpload?: boolean;
375
389
  }
@@ -501,6 +515,12 @@ declare module 'upload-post' {
501
515
  */
502
516
  getStatus(requestId: string): Promise<StatusResponse>;
503
517
 
518
+ /**
519
+ * Get the status of a scheduled or queued upload by job ID
520
+ * @param jobId - The job_id from a scheduled or queued upload
521
+ */
522
+ getJobStatus(jobId: string): Promise<StatusResponse>;
523
+
504
524
  /**
505
525
  * Get upload history
506
526
  * @param options - Query options
@@ -512,7 +532,62 @@ declare module 'upload-post' {
512
532
  * @param profileUsername - Profile username
513
533
  * @param options - Query options
514
534
  */
515
- getAnalytics(profileUsername: string, options?: { platforms?: string[] }): Promise<AnalyticsResponse>;
535
+ getAnalytics(profileUsername: string, options?: { platforms?: string[]; pageId?: string; pageUrn?: string }): Promise<AnalyticsResponse>;
536
+
537
+ /**
538
+ * Get total impressions for a profile from daily snapshots
539
+ * @param profileUsername - Profile username
540
+ * @param options - Query options
541
+ */
542
+ getTotalImpressions(profileUsername: string, options?: {
543
+ period?: 'last_day' | 'last_week' | 'last_month' | 'last_3months' | 'last_year';
544
+ startDate?: string;
545
+ endDate?: string;
546
+ date?: string;
547
+ platforms?: string[];
548
+ breakdown?: boolean;
549
+ metrics?: string[];
550
+ }): Promise<{
551
+ success: boolean;
552
+ profile_username: string;
553
+ start_date: string;
554
+ end_date: string;
555
+ total_impressions?: number;
556
+ metrics?: Record<string, number>;
557
+ per_platform?: Record<string, number | Record<string, number>>;
558
+ per_day?: Record<string, number | Record<string, number>>;
559
+ platforms_filter?: string[];
560
+ }>;
561
+
562
+ /**
563
+ * Get analytics for a specific post across all platforms
564
+ * @param requestId - The request_id from the upload
565
+ */
566
+ getPostAnalytics(requestId: string): Promise<{
567
+ success: boolean;
568
+ post: { request_id: string; profile_username: string; post_title: string; post_caption: string; media_type: string; upload_timestamp: string };
569
+ platforms: Record<string, {
570
+ success: boolean;
571
+ platform_post_id?: string;
572
+ post_url?: string;
573
+ post_metrics?: Record<string, number>;
574
+ post_metrics_source?: string;
575
+ post_metrics_error?: string;
576
+ profile_snapshot_at_post_date?: Record<string, number>;
577
+ profile_snapshot_latest?: Record<string, number>;
578
+ profile_snapshot_latest_date?: string;
579
+ error_message?: string;
580
+ }>;
581
+ }>;
582
+
583
+ /**
584
+ * Get available metrics configuration for all supported platforms
585
+ */
586
+ getPlatformMetrics(): Promise<Record<string, {
587
+ primary_impressions_field: string;
588
+ available_metrics: string[];
589
+ metric_labels: Record<string, string>;
590
+ }>>;
516
591
 
517
592
  // Scheduled Posts
518
593
  /**
package/index.js CHANGED
@@ -29,7 +29,8 @@ export class UploadPost {
29
29
  method,
30
30
  url: `${this.baseUrl}${endpoint}`,
31
31
  headers: {
32
- 'Authorization': `Apikey ${this.apiKey}`
32
+ 'Authorization': `Apikey ${this.apiKey}`,
33
+ 'X-Upload-Post-Source': 'npm',
33
34
  },
34
35
  maxContentLength: Infinity,
35
36
  maxBodyLength: Infinity
@@ -62,7 +63,7 @@ export class UploadPost {
62
63
  */
63
64
  _addCommonParams(form, options) {
64
65
  form.append('user', options.user);
65
- form.append('title', options.title);
66
+ if (options.title) form.append('title', options.title);
66
67
 
67
68
  // Platforms
68
69
  const platforms = Array.isArray(options.platforms) ? options.platforms : [options.platforms];
@@ -74,6 +75,7 @@ export class UploadPost {
74
75
  if (options.scheduledDate) form.append('scheduled_date', options.scheduledDate);
75
76
  if (options.timezone) form.append('timezone', options.timezone);
76
77
  if (options.addToQueue !== undefined) form.append('add_to_queue', String(options.addToQueue));
78
+ if (options.maxPostsPerSlot !== undefined) form.append('max_posts_per_slot', String(options.maxPostsPerSlot));
77
79
  if (options.asyncUpload !== undefined) form.append('async_upload', String(options.asyncUpload));
78
80
 
79
81
  // Platform-specific title overrides
@@ -103,6 +105,24 @@ export class UploadPost {
103
105
  if (options.youtubeFirstComment) form.append('youtube_first_comment', options.youtubeFirstComment);
104
106
  if (options.redditFirstComment) form.append('reddit_first_comment', options.redditFirstComment);
105
107
  if (options.blueskyFirstComment) form.append('bluesky_first_comment', options.blueskyFirstComment);
108
+ if (options.linkedinFirstComment) form.append('linkedin_first_comment', options.linkedinFirstComment);
109
+
110
+ if (options.firstCommentMedia) {
111
+ const mediaItems = Array.isArray(options.firstCommentMedia) ? options.firstCommentMedia : [options.firstCommentMedia];
112
+ for (const mediaItem of mediaItems) {
113
+ if (typeof mediaItem === 'string') {
114
+ if (mediaItem.toLowerCase().startsWith('http://') || mediaItem.toLowerCase().startsWith('https://')) {
115
+ form.append('first_comment_media[]', mediaItem);
116
+ } else {
117
+ if (fs.existsSync(mediaItem)) {
118
+ form.append('first_comment_media[]', createReadStream(mediaItem));
119
+ }
120
+ }
121
+ } else {
122
+ form.append('first_comment_media[]', mediaItem);
123
+ }
124
+ }
125
+ }
106
126
  }
107
127
 
108
128
  /**
@@ -175,9 +195,12 @@ export class UploadPost {
175
195
  * Add LinkedIn-specific parameters
176
196
  * @private
177
197
  */
178
- _addLinkedinParams(form, options) {
198
+ _addLinkedinParams(form, options, isText = false) {
179
199
  if (options.linkedinVisibility) form.append('visibility', options.linkedinVisibility);
180
200
  if (options.targetLinkedinPageId) form.append('target_linkedin_page_id', options.targetLinkedinPageId);
201
+ if (isText && (options.linkedinLinkUrl || options.linkUrl)) {
202
+ form.append('linkedin_link_url', options.linkedinLinkUrl || options.linkUrl);
203
+ }
181
204
  }
182
205
 
183
206
  /**
@@ -258,15 +281,19 @@ export class UploadPost {
258
281
  _addThreadsParams(form, options) {
259
282
  if (options.threadsLongTextAsPost !== undefined) form.append('threads_long_text_as_post', String(options.threadsLongTextAsPost));
260
283
  if (options.threadsThreadMediaLayout) form.append('threads_thread_media_layout', options.threadsThreadMediaLayout);
284
+ if (options.threadsTopicTag) form.append('threads_topic_tag', options.threadsTopicTag);
261
285
  }
262
286
 
263
287
  /**
264
288
  * Add Reddit-specific parameters
265
289
  * @private
266
290
  */
267
- _addRedditParams(form, options) {
291
+ _addRedditParams(form, options, isText = false) {
268
292
  if (options.redditSubreddit) form.append('subreddit', options.redditSubreddit);
269
293
  if (options.redditFlairId) form.append('flair_id', options.redditFlairId);
294
+ if (isText && (options.redditLinkUrl || options.linkUrl)) {
295
+ form.append('reddit_link_url', options.redditLinkUrl || options.linkUrl);
296
+ }
270
297
  }
271
298
 
272
299
  /**
@@ -352,6 +379,7 @@ export class UploadPost {
352
379
  * Threads options:
353
380
  * @param {boolean} [options.threadsLongTextAsPost] - Post long text as single post (vs thread)
354
381
  * @param {string} [options.threadsThreadMediaLayout] - Comma-separated list of how many media items per Threads post (e.g. "5,5")
382
+ * @param {string} [options.threadsTopicTag] - Topic tag for the Threads post (1-50 chars, no periods or ampersands)
355
383
  *
356
384
  * @returns {Promise<Object>} API response with request_id for async uploads
357
385
  */
@@ -359,7 +387,7 @@ export class UploadPost {
359
387
  const form = new FormData();
360
388
 
361
389
  // Handle video (file path or URL)
362
- if (videoPathOrUrl.toLowerCase().startsWith('http://') || videoPathOrUrl.toLowerCase().startsWith('https://')) {
390
+ if (typeof videoPathOrUrl === 'string' && (videoPathOrUrl.toLowerCase().startsWith('http://') || videoPathOrUrl.toLowerCase().startsWith('https://'))) {
363
391
  form.append('video', videoPathOrUrl);
364
392
  } else {
365
393
  if (!fs.existsSync(videoPathOrUrl)) {
@@ -433,6 +461,7 @@ export class UploadPost {
433
461
  * Threads options:
434
462
  * @param {boolean} [options.threadsLongTextAsPost] - Post long text as single post
435
463
  * @param {string} [options.threadsThreadMediaLayout] - Comma-separated list of how many media items per Threads post (e.g. "5,5")
464
+ * @param {string} [options.threadsTopicTag] - Topic tag for the Threads post (1-50 chars, no periods or ampersands)
436
465
  *
437
466
  * Reddit options:
438
467
  * @param {string} [options.redditSubreddit] - Subreddit name (without r/)
@@ -482,13 +511,18 @@ export class UploadPost {
482
511
  * @param {string} [options.timezone] - Timezone for scheduled date
483
512
  * @param {boolean} [options.addToQueue] - Add to posting queue
484
513
  * @param {boolean} [options.asyncUpload=true] - Process upload asynchronously
485
- *
514
+ * @param {string} [options.linkUrl] - Generic URL for link preview card (works for LinkedIn, Bluesky, Facebook). Platform-specific params take priority.
515
+ *
486
516
  * LinkedIn options:
487
517
  * @param {string} [options.targetLinkedinPageId] - Page ID for organization posts
488
- *
518
+ * @param {string} [options.linkedinLinkUrl] - URL to attach as link preview on LinkedIn
519
+ *
520
+ * Bluesky options:
521
+ * @param {string} [options.blueskyLinkUrl] - URL to attach as external embed link preview on Bluesky
522
+ *
489
523
  * Facebook options:
490
524
  * @param {string} [options.facebookPageId] - Facebook Page ID
491
- * @param {string} [options.facebookLinkUrl] - URL to attach as link preview
525
+ * @param {string} [options.facebookLinkUrl] - URL to attach as link preview on Facebook
492
526
  *
493
527
  * X (Twitter) options:
494
528
  * @param {string} [options.xReplySettings] - Who can reply
@@ -503,6 +537,7 @@ export class UploadPost {
503
537
  * Threads options:
504
538
  * @param {boolean} [options.threadsLongTextAsPost] - Post long text as single post
505
539
  * @param {string} [options.threadsThreadMediaLayout] - Comma-separated list of how many media items per Threads post (e.g. "5,5")
540
+ * @param {string} [options.threadsTopicTag] - Topic tag for the Threads post (1-50 chars, no periods or ampersands)
506
541
  *
507
542
  * Reddit options:
508
543
  * @param {string} [options.redditSubreddit] - Subreddit name (without r/)
@@ -512,15 +547,21 @@ export class UploadPost {
512
547
  */
513
548
  async uploadText(options) {
514
549
  const form = new FormData();
515
-
550
+
516
551
  this._addCommonParams(form, options);
517
-
552
+
553
+ // Generic link_url support
554
+ if (options.linkUrl) form.append('link_url', options.linkUrl);
555
+
518
556
  const platforms = Array.isArray(options.platforms) ? options.platforms : [options.platforms];
519
- if (platforms.includes('linkedin')) this._addLinkedinParams(form, options);
557
+ if (platforms.includes('linkedin')) this._addLinkedinParams(form, options, true);
520
558
  if (platforms.includes('facebook')) this._addFacebookParams(form, options, false, true);
521
559
  if (platforms.includes('x')) this._addXParams(form, options, true);
522
560
  if (platforms.includes('threads')) this._addThreadsParams(form, options);
523
- if (platforms.includes('reddit')) this._addRedditParams(form, options);
561
+ if (platforms.includes('reddit')) this._addRedditParams(form, options, true);
562
+ if (platforms.includes('bluesky') && (options.blueskyLinkUrl || options.linkUrl)) {
563
+ form.append('bluesky_link_url', options.blueskyLinkUrl || options.linkUrl);
564
+ }
524
565
 
525
566
  return this._request('/upload_text', 'POST', form, true);
526
567
  }
@@ -546,7 +587,7 @@ export class UploadPost {
546
587
  const form = new FormData();
547
588
 
548
589
  // Handle document (file path or URL)
549
- if (documentPathOrUrl.toLowerCase().startsWith('http://') || documentPathOrUrl.toLowerCase().startsWith('https://')) {
590
+ if (typeof documentPathOrUrl === 'string' && (documentPathOrUrl.toLowerCase().startsWith('http://') || documentPathOrUrl.toLowerCase().startsWith('https://'))) {
550
591
  form.append('document', documentPathOrUrl);
551
592
  } else {
552
593
  if (!fs.existsSync(documentPathOrUrl)) {
@@ -564,6 +605,7 @@ export class UploadPost {
564
605
  if (options.scheduledDate) form.append('scheduled_date', options.scheduledDate);
565
606
  if (options.timezone) form.append('timezone', options.timezone);
566
607
  if (options.addToQueue !== undefined) form.append('add_to_queue', String(options.addToQueue));
608
+ if (options.maxPostsPerSlot !== undefined) form.append('max_posts_per_slot', String(options.maxPostsPerSlot));
567
609
  if (options.asyncUpload !== undefined) form.append('async_upload', String(options.asyncUpload));
568
610
 
569
611
  this._addLinkedinParams(form, options);
@@ -583,6 +625,16 @@ export class UploadPost {
583
625
  return this._request('/uploadposts/status', 'GET', { request_id: requestId });
584
626
  }
585
627
 
628
+ /**
629
+ * Get the status of a scheduled or queued upload by job ID
630
+ *
631
+ * @param {string} jobId - The job_id from a scheduled or queued upload
632
+ * @returns {Promise<Object>} Upload status
633
+ */
634
+ async getJobStatus(jobId) {
635
+ return this._request('/uploadposts/status', 'GET', { job_id: jobId });
636
+ }
637
+
586
638
  /**
587
639
  * Get upload history
588
640
  *
@@ -600,20 +652,73 @@ export class UploadPost {
600
652
 
601
653
  /**
602
654
  * Get analytics for a profile
603
- *
655
+ *
604
656
  * @param {string} profileUsername - Profile username
605
657
  * @param {Object} [options] - Query options
606
- * @param {string[]} [options.platforms] - Filter by platforms (instagram, linkedin, facebook, x)
607
- * @returns {Promise<Object>} Analytics data
658
+ * @param {string[]} [options.platforms] - Filter by platforms (instagram, linkedin, facebook, x, youtube, tiktok, threads, pinterest, reddit)
659
+ * @param {string} [options.pageId] - Facebook Page ID (required for Facebook analytics)
660
+ * @param {string} [options.pageUrn] - LinkedIn page URN (defaults to "me" for personal profile)
661
+ * @returns {Promise<Object>} Analytics data per platform
608
662
  */
609
663
  async getAnalytics(profileUsername, options = {}) {
610
664
  const params = {};
611
665
  if (options.platforms && options.platforms.length > 0) {
612
666
  params.platforms = options.platforms.join(',');
613
667
  }
668
+ if (options.pageId) params.page_id = options.pageId;
669
+ if (options.pageUrn) params.page_urn = options.pageUrn;
614
670
  return this._request(`/analytics/${encodeURIComponent(profileUsername)}`, 'GET', params);
615
671
  }
616
672
 
673
+ /**
674
+ * Get total impressions for a profile from daily snapshots
675
+ *
676
+ * @param {string} profileUsername - Profile username
677
+ * @param {Object} [options] - Query options
678
+ * @param {string} [options.period] - Period shortcut: last_day, last_week, last_month, last_3months, last_year
679
+ * @param {string} [options.startDate] - Start date in YYYY-MM-DD format
680
+ * @param {string} [options.endDate] - End date in YYYY-MM-DD format
681
+ * @param {string} [options.date] - Single date in YYYY-MM-DD format
682
+ * @param {string[]} [options.platforms] - Filter by platforms
683
+ * @param {boolean} [options.breakdown] - Include per-platform and per-day breakdown
684
+ * @param {string[]} [options.metrics] - Specific metrics to aggregate (e.g., ['likes', 'comments', 'shares'])
685
+ * @returns {Promise<Object>} Total impressions data
686
+ */
687
+ async getTotalImpressions(profileUsername, options = {}) {
688
+ const params = {};
689
+ if (options.period) params.period = options.period;
690
+ if (options.startDate) params.start_date = options.startDate;
691
+ if (options.endDate) params.end_date = options.endDate;
692
+ if (options.date) params.date = options.date;
693
+ if (options.platforms && options.platforms.length > 0) {
694
+ params.platform = options.platforms.join(',');
695
+ }
696
+ if (options.breakdown) params.breakdown = 'true';
697
+ if (options.metrics && options.metrics.length > 0) {
698
+ params.metrics = options.metrics.join(',');
699
+ }
700
+ return this._request(`/uploadposts/total-impressions/${encodeURIComponent(profileUsername)}`, 'GET', params);
701
+ }
702
+
703
+ /**
704
+ * Get analytics for a specific post across all platforms it was published to
705
+ *
706
+ * @param {string} requestId - The request_id from the upload
707
+ * @returns {Promise<Object>} Post analytics with per-platform metrics
708
+ */
709
+ async getPostAnalytics(requestId) {
710
+ return this._request(`/uploadposts/post-analytics/${encodeURIComponent(requestId)}`, 'GET');
711
+ }
712
+
713
+ /**
714
+ * Get available metrics configuration for all supported platforms
715
+ *
716
+ * @returns {Promise<Object>} Platform metrics config (primary fields, available metrics, labels)
717
+ */
718
+ async getPlatformMetrics() {
719
+ return this._request('/uploadposts/platform-metrics', 'GET');
720
+ }
721
+
617
722
  // ==================== Scheduled Posts ====================
618
723
 
619
724
  /**
@@ -755,6 +860,30 @@ export class UploadPost {
755
860
  const params = profile ? { profile } : {};
756
861
  return this._request('/uploadposts/pinterest/boards', 'GET', params);
757
862
  }
863
+
864
+ /**
865
+ * Get Google Business Profile locations for a connected account
866
+ *
867
+ * @param {string} [profile] - Profile username
868
+ * @returns {Promise<Object>} List of Google Business locations
869
+ */
870
+ async getGoogleBusinessLocations(profile) {
871
+ const params = profile ? { profile } : {};
872
+ return this._request('/uploadposts/google-business/locations', 'GET', params);
873
+ }
874
+
875
+ /**
876
+ * Select a specific Google Business Profile location for a profile
877
+ *
878
+ * @param {string} locationId - The location ID to select (e.g. "accounts/123/locations/456")
879
+ * @param {string} [profile] - Profile username
880
+ * @returns {Promise<Object>} Selection result with google_business_id and display_name
881
+ */
882
+ async selectGoogleBusinessLocation(locationId, profile) {
883
+ const data = { location_id: locationId };
884
+ if (profile) data.profile = profile;
885
+ return this._request('/uploadposts/google-business/locations/select', 'POST', data);
886
+ }
758
887
  }
759
888
 
760
889
  // Default export for CommonJS compatibility
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "upload-post",
3
- "version": "2.0.4",
3
+ "version": "2.1.0",
4
4
  "description": "Official client library for Upload-Post API - Cross-platform social media upload for TikTok, Instagram, YouTube, LinkedIn, Facebook, Pinterest, Threads, Reddit, Bluesky, and X (Twitter)",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",