upload-post 2.8.0 → 2.9.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 +102 -5
  2. package/index.d.ts +113 -8
  3. package/index.js +42 -10
  4. package/package.json +5 -2
package/README.md CHANGED
@@ -39,6 +39,38 @@ console.log(response);
39
39
  - ✅ **Analytics** - Get engagement metrics
40
40
  - ✅ **Full TypeScript Support**
41
41
 
42
+ ## Using this from an AI agent? Use the MCP server instead
43
+
44
+ If you are wiring Upload-Post into ChatGPT, Claude, Cursor, Claude Code or any
45
+ other MCP-compatible agent, you do not need to write a client on top of this
46
+ SDK. The official **[Model Context Protocol server](https://github.com/Upload-Post/upload-post-mcp)**
47
+ already wraps the whole API as 50 tools the agent can call directly.
48
+
49
+ ```jsonc
50
+ // Hosted (OAuth or API key) — nothing to install
51
+ {
52
+ "mcpServers": {
53
+ "upload-post": { "url": "https://mcp.upload-post.com/mcp" }
54
+ }
55
+ }
56
+ ```
57
+
58
+ ```jsonc
59
+ // Local stdio, built on this SDK
60
+ {
61
+ "mcpServers": {
62
+ "upload-post": {
63
+ "command": "npx",
64
+ "args": ["-y", "@upload-post/mcp"],
65
+ "env": { "UPLOAD_POST_API_KEY": "YOUR_API_KEY" }
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ See the [MCP integration guide](https://docs.upload-post.com/guides/mcp-server-integration).
72
+ Keep using this SDK when you are writing your own application code.
73
+
42
74
  ## API Reference
43
75
 
44
76
  ### Upload Video
@@ -170,9 +202,16 @@ const jwt = await client.generateJwt('my-profile', {
170
202
  redirectUrl: 'https://yourapp.com/callback',
171
203
  platforms: ['tiktok', 'instagram'],
172
204
  // Optional: force the connection page language for this profile.
173
- // Supported: 'en' | 'es' | 'de' | 'fr' | 'pt'. When omitted, the page
174
- // auto-detects the visitor's browser language and falls back to English.
205
+ // Supported: 'en' | 'es' | 'de' | 'fr' | 'pt' | 'pl' | 'tr'. When omitted, the
206
+ // page auto-detects the visitor's browser language and falls back to English.
175
207
  language: 'es',
208
+ // Optional: override individual connection-page strings. Flat object of i18n
209
+ // dot-path keys to strings. Max 100 entries, keys ^[a-zA-Z0-9_.]+$, values
210
+ // up to 300 chars. Echoed back in the `profile` object of validateJwt.
211
+ uiLabels: {
212
+ 'connect.title': 'Link your accounts',
213
+ 'connect.subtitle': 'Publish everywhere from one place',
214
+ },
176
215
  });
177
216
  ```
178
217
 
@@ -183,6 +222,34 @@ const analytics = await client.getAnalytics('my-profile', {
183
222
  platforms: ['instagram', 'tiktok'],
184
223
  });
185
224
  console.log(analytics);
225
+
226
+ // Instagram returns two audience breakdowns with the same shape
227
+ // ({ age, gender, country, city }):
228
+ console.log(analytics.analytics.instagram.follower_demographics);
229
+ console.log(analytics.analytics.instagram.engaged_audience_demographics);
230
+ ```
231
+
232
+ ### Cached Post Analytics
233
+
234
+ Replays per-post metrics already fetched, instead of calling the platforms again. Only contains posts previously fetched through a live per-post endpoint; there is no background refresh, so captured_at is the last time that post was read live. Not subject to the live
235
+ calls, so it is not subject to the live post-analytics rate limit
236
+ (100 requests / 5 minutes). Use it to page through a profile's post history.
237
+
238
+ ```javascript
239
+ let cursor;
240
+ do {
241
+ const page = await client.getCachedPostAnalytics('my-profile', {
242
+ platform: 'youtube', // optional: instagram, tiktok, youtube, facebook, linkedin, threads, pinterest, reddit
243
+ limit: 50, // default 50, max 200
244
+ since: '2026-06-01', // defaults to 30 days ago
245
+ until: '2026-07-01', // defaults to today
246
+ cursor,
247
+ });
248
+ for (const post of page.posts) {
249
+ console.log(post.platform, post.post_id, post.metrics);
250
+ }
251
+ cursor = page.next_cursor;
252
+ } while (cursor);
186
253
  ```
187
254
 
188
255
  ### Get Media
@@ -201,6 +268,22 @@ await client.getMedia('linkedin', 'my-profile', { pageUrn: 'me' });
201
268
  await client.getMedia('linkedin', 'my-profile', { pageUrn: '12345' });
202
269
  ```
203
270
 
271
+ The response carries a `pagination` object — `{ limit, next_cursor, has_more }`,
272
+ with `next_cursor: null` and `has_more: false` on the last page:
273
+
274
+ ```javascript
275
+ let cursor;
276
+ do {
277
+ const page = await client.getMedia('instagram', 'my-profile', { limit: 50, cursor });
278
+ console.log(page.media.length);
279
+ cursor = page.pagination.next_cursor;
280
+ } while (cursor);
281
+ ```
282
+
283
+ `limit` defaults to 25 and is clamped to 1-100, with per-platform caps of 20 for
284
+ TikTok and 50 for YouTube. **LinkedIn, Discord and Telegram do not support
285
+ cursors** — they accept `limit` only, and passing a `cursor` returns HTTP 400.
286
+
204
287
  ### Helper Methods
205
288
 
206
289
  ```javascript
@@ -316,9 +399,6 @@ These options work across all upload methods:
316
399
  | `addToQueue` | Add to posting queue |
317
400
  | `maxPostsPerSlot` | Max posts per queue slot (overrides profile setting) |
318
401
  | `asyncUpload` | Process asynchronously (default: true) |
319
- | `autogenerate` | If true, AI generates native per-platform title/description from the media and fills any field left empty |
320
- | `autogenerateTitle` / `autogenerateDescription` | Generate only the title or only the description with AI |
321
- | `autogenerateLanguage` | Force the AI output language (ISO code); omit to auto-detect from the media |
322
402
  | `idempotencyKey` | Collapses duplicate uploads within 24h. Reuse the same value when retrying. Alias: `requestId` |
323
403
 
324
404
  ## Google Business Profile
@@ -353,6 +433,22 @@ await client.uploadText({
353
433
 
354
434
  Also available: `gbpTopicType: 'EVENT'` with `gbpEventTitle` / `gbpEventStartDate` / `gbpEventStartTime` / `gbpEventEndDate` / `gbpEventEndTime`, a call-to-action via `gbpCtaType` + `gbpCtaUrl`, and `gbpMediaUrl` / `gbpMediaFormat`.
355
435
 
436
+ ### Gallery photos
437
+
438
+ Set `gbpPostType` to publish straight into the location's photo gallery instead of creating a Local Post:
439
+
440
+ ```javascript
441
+ await client.uploadPhotos(['storefront.jpg'], {
442
+ user: 'myprofile',
443
+ platforms: ['google_business'],
444
+ gbpLocationId: locations[0].name,
445
+ gbpPostType: 'GALLERY', // MEDIA | PHOTO | GALLERY
446
+ gbpMediaCategory: 'EXTERIOR', // defaults to ADDITIONAL
447
+ });
448
+ ```
449
+
450
+ Omitting `gbpPostType` (or sending any other value) keeps the existing Local Post behaviour. `gbpMediaCategory` accepts `COVER`, `PROFILE`, `LOGO`, `EXTERIOR`, `INTERIOR`, `PRODUCT`, `AT_WORK`, `FOOD_AND_DRINK`, `MENU`, `COMMON_AREA`, `ROOMS`, `TEAMS` and `ADDITIONAL`.
451
+
356
452
  ## Retrying an upload safely
357
453
 
358
454
  An upload that times out may still have been accepted by the API. Retrying it without an idempotency key publishes the post a second time.
@@ -410,6 +506,7 @@ try {
410
506
  - [Upload-Post Website](https://www.upload-post.com)
411
507
  - [API Documentation](https://docs.upload-post.com)
412
508
  - [Dashboard](https://app.upload-post.com)
509
+ - [MCP Server](https://www.upload-post.com/mcp) — connect ChatGPT, Claude, Cursor and any AI agent ([source](https://github.com/Upload-Post/upload-post-mcp), [npm](https://www.npmjs.com/package/@upload-post/mcp))
413
510
 
414
511
  ## License
415
512
 
package/index.d.ts CHANGED
@@ -69,13 +69,9 @@ declare module 'upload-post' {
69
69
  /** Process upload asynchronously (default: true) */
70
70
  asyncUpload?: boolean;
71
71
  /** If true, AI generates native per-platform title/description from the media and fills any field left empty */
72
- autogenerate?: boolean;
73
72
  /** Generate only the title with AI */
74
- autogenerateTitle?: boolean;
75
73
  /** Generate only the description with AI */
76
- autogenerateDescription?: boolean;
77
74
  /** Force the AI output language (ISO code); omit to auto-detect from the media */
78
- autogenerateLanguage?: string;
79
75
 
80
76
  // Platform-specific title overrides
81
77
  blueskyTitle?: string;
@@ -362,9 +358,28 @@ declare module 'upload-post' {
362
358
 
363
359
  // ==================== Combined Upload Options ====================
364
360
 
361
+ export type GoogleBusinessMediaCategory =
362
+ | 'COVER'
363
+ | 'PROFILE'
364
+ | 'LOGO'
365
+ | 'EXTERIOR'
366
+ | 'INTERIOR'
367
+ | 'PRODUCT'
368
+ | 'AT_WORK'
369
+ | 'FOOD_AND_DRINK'
370
+ | 'MENU'
371
+ | 'COMMON_AREA'
372
+ | 'ROOMS'
373
+ | 'TEAMS'
374
+ | 'ADDITIONAL';
375
+
365
376
  export interface GoogleBusinessOptions {
366
377
  /** Google Business location id, e.g. "accounts/123/locations/456". Required when the account has more than one location; the API auto-selects only for single-location accounts. List them with getGoogleBusinessLocations(). */
367
378
  gbpLocationId?: string;
379
+ /** Publish to the location's gallery instead of creating a Local Post. Omitting it (or sending any other value) keeps the Local Post behaviour. */
380
+ gbpPostType?: 'MEDIA' | 'PHOTO' | 'GALLERY';
381
+ /** Gallery category for the uploaded photo. Only used with gbpPostType. Defaults to ADDITIONAL. */
382
+ gbpMediaCategory?: GoogleBusinessMediaCategory;
368
383
  /** Post type. Defaults to STANDARD. */
369
384
  gbpTopicType?: 'STANDARD' | 'EVENT' | 'OFFER';
370
385
  /** Media URL attached to the post */
@@ -500,9 +515,31 @@ declare module 'upload-post' {
500
515
  [key: string]: any;
501
516
  }
502
517
 
518
+ /**
519
+ * Instagram audience breakdown. Each key maps a bucket (age range, gender,
520
+ * country code, city name) to a follower/engager count.
521
+ */
522
+ export interface DemographicsBreakdown {
523
+ age?: Record<string, number>;
524
+ gender?: Record<string, number>;
525
+ country?: Record<string, number>;
526
+ city?: Record<string, number>;
527
+ }
528
+
529
+ export interface InstagramAnalytics {
530
+ /** Breakdown of the account's followers. */
531
+ follower_demographics?: DemographicsBreakdown;
532
+ /** Breakdown of the accounts that engaged with the profile's content. Same shape as follower_demographics. */
533
+ engaged_audience_demographics?: DemographicsBreakdown;
534
+ [key: string]: any;
535
+ }
536
+
503
537
  export interface AnalyticsResponse {
504
538
  success: boolean;
505
- analytics?: any;
539
+ analytics?: {
540
+ instagram?: InstagramAnalytics;
541
+ [platform: string]: any;
542
+ };
506
543
  [key: string]: any;
507
544
  }
508
545
 
@@ -658,6 +695,48 @@ declare module 'upload-post' {
658
695
  }>;
659
696
  }>;
660
697
 
698
+ /**
699
+ * Replay per-post metrics already fetched, instead of querying
700
+ * the platforms live. Not subject to the live post-analytics rate limit
701
+ * (100 requests / 5 minutes), so it is the right call for paging through a
702
+ * profile's whole post history.
703
+ *
704
+ * @param user - Profile username
705
+ * @param options - Query options
706
+ */
707
+ getCachedPostAnalytics(user: string, options?: {
708
+ platform?: 'instagram' | 'tiktok' | 'youtube' | 'facebook' | 'linkedin' | 'threads' | 'pinterest' | 'reddit';
709
+ /** Posts per page. Default 50, max 200. */
710
+ limit?: number;
711
+ /** Opaque cursor from a previous response's next_cursor. */
712
+ cursor?: string;
713
+ /** Start date, YYYY-MM-DD. Defaults to 30 days ago. */
714
+ since?: string;
715
+ /** End date, YYYY-MM-DD. Defaults to today. */
716
+ until?: string;
717
+ }): Promise<{
718
+ success: boolean;
719
+ profile_username: string;
720
+ platform: string | null;
721
+ since: string;
722
+ until: string;
723
+ source: 'snapshot_cache';
724
+ posts: Array<{
725
+ post_id: string;
726
+ platform: string;
727
+ profile_username: string;
728
+ date: string;
729
+ captured_at: string;
730
+ metrics: Record<string, number>;
731
+ post_url?: string | null;
732
+ media_type?: string | null;
733
+ upload_timestamp?: string | null;
734
+ }>;
735
+ limit: number;
736
+ next_cursor: string | null;
737
+ has_more: boolean;
738
+ }>;
739
+
661
740
  /**
662
741
  * Get available metrics configuration for all supported platforms
663
742
  */
@@ -671,11 +750,13 @@ declare module 'upload-post' {
671
750
  * Get recent media from a connected social account.
672
751
  *
673
752
  * @param options.pageUrn - LinkedIn only. Numeric org ID, full org URN, or "me" to force the personal profile.
753
+ * @param options.limit - Items per page. Default 25, clamped to 1-100. Per-platform caps: TikTok 20, YouTube 50, everything else 100.
754
+ * @param options.cursor - Opaque cursor from a previous response's `pagination.next_cursor`. LinkedIn, Discord and Telegram do not support cursors: passing one there returns HTTP 400.
674
755
  */
675
756
  getMedia(
676
757
  platform: 'instagram' | 'tiktok' | 'youtube' | 'linkedin' | 'facebook' | 'x' | 'threads' | 'pinterest' | 'bluesky' | 'reddit' | string,
677
758
  user: string,
678
- options?: { pageUrn?: string }
759
+ options?: { pageUrn?: string; limit?: number; cursor?: string }
679
760
  ): Promise<{
680
761
  success: boolean;
681
762
  media: Array<{
@@ -687,6 +768,12 @@ declare module 'upload-post' {
687
768
  timestamp: string | null;
688
769
  thumbnail_url: string | null;
689
770
  }>;
771
+ pagination?: {
772
+ limit: number;
773
+ /** null on the last page. */
774
+ next_cursor: string | null;
775
+ has_more: boolean;
776
+ };
690
777
  }>;
691
778
 
692
779
  // Scheduled Posts
@@ -740,14 +827,32 @@ declare module 'upload-post' {
740
827
  readonlyCalendar?: boolean;
741
828
  connectTitle?: string;
742
829
  connectDescription?: string;
743
- language?: 'en' | 'es' | 'de' | 'fr' | 'pt';
830
+ language?: 'en' | 'es' | 'de' | 'fr' | 'pt' | 'pl' | 'tr';
831
+ /**
832
+ * Flat map of i18n dot-path keys to replacement strings, e.g.
833
+ * `{ 'connect.title': 'Link your accounts' }`. Max 100 entries; keys must
834
+ * match `^[a-zA-Z0-9_.]+$` and values are strings of up to 300 characters.
835
+ * Returned back in the `profile` object of validateJwt.
836
+ */
837
+ uiLabels?: Record<string, string>;
744
838
  }): Promise<JwtResponse>;
745
839
 
746
840
  /**
747
841
  * Validate a JWT token
748
842
  * @param jwt - JWT token to validate
749
843
  */
750
- validateJwt(jwt: string): Promise<{ success: boolean; valid?: boolean }>;
844
+ validateJwt(jwt: string): Promise<{
845
+ success: boolean;
846
+ valid?: boolean;
847
+ profile?: {
848
+ username?: string;
849
+ language?: 'en' | 'es' | 'de' | 'fr' | 'pt' | 'pl' | 'tr';
850
+ /** The ui_labels sent to generateJwt, echoed back for the connection page. */
851
+ ui_labels?: Record<string, string>;
852
+ [key: string]: any;
853
+ };
854
+ [key: string]: any;
855
+ }>;
751
856
 
752
857
  /**
753
858
  * Get user preferences (including calendar settings)
package/index.js CHANGED
@@ -89,10 +89,6 @@ export class UploadPost {
89
89
  if (options.maxPostsPerSlot !== undefined) form.append('max_posts_per_slot', String(options.maxPostsPerSlot));
90
90
  if (options.asyncUpload !== undefined) form.append('async_upload', String(options.asyncUpload));
91
91
  // AI auto-generation of native per-platform copy from the media (fills blank fields)
92
- if (options.autogenerate !== undefined) form.append('autogenerate', String(options.autogenerate));
93
- if (options.autogenerateTitle !== undefined) form.append('autogenerate_title', String(options.autogenerateTitle));
94
- if (options.autogenerateDescription !== undefined) form.append('autogenerate_description', String(options.autogenerateDescription));
95
- if (options.autogenerateLanguage) form.append('autogenerate_language', String(options.autogenerateLanguage));
96
92
 
97
93
  // Platform-specific title overrides
98
94
  if (options.blueskyTitle) form.append('bluesky_title', options.blueskyTitle);
@@ -352,10 +348,16 @@ export class UploadPost {
352
348
  *
353
349
  * gbpLocationId is required for accounts with more than one location; the API
354
350
  * only auto-selects when exactly one location exists.
351
+ *
352
+ * gbpPostType switches the call from the default Local Post to a gallery
353
+ * photo upload (MEDIA / PHOTO / GALLERY). Any other value, or omitting it,
354
+ * keeps the Local Post behaviour.
355
355
  * @private
356
356
  */
357
357
  _addGoogleBusinessParams(form, options) {
358
358
  if (options.gbpLocationId) form.append('gbp_location_id', options.gbpLocationId);
359
+ if (options.gbpPostType) form.append('gbp_post_type', String(options.gbpPostType).toUpperCase());
360
+ if (options.gbpMediaCategory) form.append('gbp_media_category', String(options.gbpMediaCategory).toUpperCase());
359
361
  if (options.gbpTopicType) form.append('gbp_topic_type', String(options.gbpTopicType).toUpperCase());
360
362
  if (options.gbpMediaUrl) form.append('gbp_media_url', options.gbpMediaUrl);
361
363
  if (options.gbpMediaFormat) form.append('gbp_media_format', String(options.gbpMediaFormat).toUpperCase());
@@ -699,10 +701,6 @@ export class UploadPost {
699
701
  if (options.maxPostsPerSlot !== undefined) form.append('max_posts_per_slot', String(options.maxPostsPerSlot));
700
702
  if (options.asyncUpload !== undefined) form.append('async_upload', String(options.asyncUpload));
701
703
  // AI auto-generation of native per-platform copy from the media (fills blank fields)
702
- if (options.autogenerate !== undefined) form.append('autogenerate', String(options.autogenerate));
703
- if (options.autogenerateTitle !== undefined) form.append('autogenerate_title', String(options.autogenerateTitle));
704
- if (options.autogenerateDescription !== undefined) form.append('autogenerate_description', String(options.autogenerateDescription));
705
- if (options.autogenerateLanguage) form.append('autogenerate_language', String(options.autogenerateLanguage));
706
704
 
707
705
  this._addLinkedinParams(form, options);
708
706
 
@@ -822,6 +820,33 @@ export class UploadPost {
822
820
  });
823
821
  }
824
822
 
823
+ /**
824
+ * Replay per-post metrics already fetched, instead of querying the
825
+ * platforms live.
826
+ *
827
+ * Because it never calls the platform APIs it is not subject to the live
828
+ * post-analytics rate limit (100 requests / 5 minutes), which makes it the
829
+ * right method for paging through a profile's whole post history.
830
+ *
831
+ * @param {string} user - Profile username
832
+ * @param {Object} [options] - Query options
833
+ * @param {string} [options.platform] - Filter by platform (instagram, tiktok, youtube, facebook, linkedin, threads, pinterest, reddit)
834
+ * @param {number} [options.limit=50] - Posts per page (max 200)
835
+ * @param {string} [options.cursor] - Opaque cursor from a previous response's next_cursor
836
+ * @param {string} [options.since] - Start date in YYYY-MM-DD format (defaults to 30 days ago)
837
+ * @param {string} [options.until] - End date in YYYY-MM-DD format (defaults to today)
838
+ * @returns {Promise<Object>} Cached post metrics with next_cursor and has_more
839
+ */
840
+ async getCachedPostAnalytics(user, options = {}) {
841
+ const params = { user };
842
+ if (options.platform) params.platform = options.platform;
843
+ if (options.limit !== undefined) params.limit = options.limit;
844
+ if (options.cursor) params.cursor = options.cursor;
845
+ if (options.since) params.since = options.since;
846
+ if (options.until) params.until = options.until;
847
+ return this._request('/uploadposts/post-analytics/cached', 'GET', params);
848
+ }
849
+
825
850
  /**
826
851
  * Get available metrics configuration for all supported platforms
827
852
  *
@@ -848,11 +873,16 @@ export class UploadPost {
848
873
  * @param {string} user - Profile username
849
874
  * @param {Object} [options]
850
875
  * @param {string} [options.pageUrn] - LinkedIn only. Numeric org ID, full URN, or "me" to force the personal profile.
851
- * @returns {Promise<Object>}
876
+ * @param {number} [options.limit=25] - Items per page, clamped to 1-100. Per-platform caps: TikTok 20, YouTube 50, everything else 100.
877
+ * @param {string} [options.cursor] - Opaque cursor from a previous response's `pagination.next_cursor`.
878
+ * LinkedIn, Discord and Telegram do not support cursors: passing one there returns HTTP 400.
879
+ * @returns {Promise<Object>} `{ success, media, pagination: { limit, next_cursor, has_more } }`
852
880
  */
853
881
  async getMedia(platform, user, options = {}) {
854
882
  const params = { platform, user };
855
883
  if (options.pageUrn) params.page_urn = options.pageUrn;
884
+ if (options.limit !== undefined) params.limit = options.limit;
885
+ if (options.cursor) params.cursor = options.cursor;
856
886
  return this._request('/uploadposts/media', 'GET', params);
857
887
  }
858
888
 
@@ -938,7 +968,8 @@ export class UploadPost {
938
968
  * @param {boolean} [options.readonlyCalendar] - Show only a read-only calendar (no editing, no account connection)
939
969
  * @param {string} [options.connectTitle] - Custom title for the connection page
940
970
  * @param {string} [options.connectDescription] - Custom description for the connection page
941
- * @param {('en'|'es'|'de'|'fr'|'pt')} [options.language] - Force the connection page language for this profile. When omitted, the page auto-detects the visitor's browser language and falls back to English.
971
+ * @param {('en'|'es'|'de'|'fr'|'pt'|'pl'|'tr')} [options.language] - Force the connection page language for this profile. When omitted, the page auto-detects the visitor's browser language and falls back to English.
972
+ * @param {Object.<string, string>} [options.uiLabels] - Flat map of i18n dot-path keys to replacement strings, e.g. `{ 'connect.title': 'Link your accounts' }`. Max 100 entries; keys must match `^[a-zA-Z0-9_.]+$` and values are strings of up to 300 characters. Returned back in the `profile` object of validateJwt.
942
973
  * @returns {Promise<Object>} JWT and connection URL
943
974
  */
944
975
  async generateJwt(username, options = {}) {
@@ -952,6 +983,7 @@ export class UploadPost {
952
983
  if (options.connectTitle) body.connect_title = options.connectTitle;
953
984
  if (options.connectDescription) body.connect_description = options.connectDescription;
954
985
  if (options.language) body.language = options.language;
986
+ if (options.uiLabels) body.ui_labels = options.uiLabels;
955
987
  return this._request('/uploadposts/users/generate-jwt', 'POST', body);
956
988
  }
957
989
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "upload-post",
3
- "version": "2.8.0",
3
+ "version": "2.9.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",
@@ -26,7 +26,10 @@
26
26
  "x",
27
27
  "api-client",
28
28
  "social-media-api",
29
- "upload-post"
29
+ "upload-post",
30
+ "mcp",
31
+ "model-context-protocol",
32
+ "ai-agent"
30
33
  ],
31
34
  "repository": {
32
35
  "type": "git",