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.
- package/README.md +102 -5
- package/index.d.ts +113 -8
- package/index.js +42 -10
- 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
|
|
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?:
|
|
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<{
|
|
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
|
-
* @
|
|
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.
|
|
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",
|