upload-post 2.9.0 → 2.12.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 +282 -0
- package/index.d.ts +367 -11
- package/index.js +319 -29
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -37,6 +37,8 @@ console.log(response);
|
|
|
37
37
|
- ✅ **Posting Queue** - Add posts to your configured queue
|
|
38
38
|
- ✅ **First Comments** - Auto-post first comment after publishing
|
|
39
39
|
- ✅ **Analytics** - Get engagement metrics
|
|
40
|
+
- ✅ **Audience** - Who follows a profile, per platform
|
|
41
|
+
- ✅ **Suggestions** - Hashtags and searches to post about, per platform
|
|
40
42
|
- ✅ **Full TypeScript Support**
|
|
41
43
|
|
|
42
44
|
## Using this from an AI agent? Use the MCP server instead
|
|
@@ -295,8 +297,268 @@ const liPages = await client.getLinkedinPages('my-profile');
|
|
|
295
297
|
|
|
296
298
|
// Get Pinterest boards for a profile
|
|
297
299
|
const boards = await client.getPinterestBoards('my-profile');
|
|
300
|
+
|
|
301
|
+
// TikTok: trending Commercial Music Library tracks
|
|
302
|
+
const music = await client.getTiktokTrendingMusic('my-profile', {
|
|
303
|
+
genre: 'POP',
|
|
304
|
+
countryCode: 'ES',
|
|
305
|
+
dateRange: '7DAY', // 1DAY, 7DAY, 30DAY, 90DAY
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// TikTok: find a track by song or artist
|
|
309
|
+
const found = await client.searchTiktokMusic('my-profile', {
|
|
310
|
+
q: 'bad bunny',
|
|
311
|
+
countryCode: 'ES',
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// TikTok: search locations to tag
|
|
315
|
+
const locations = await client.getTiktokLocations('my-profile', 'Madrid');
|
|
298
316
|
```
|
|
299
317
|
|
|
318
|
+
### Comments
|
|
319
|
+
|
|
320
|
+
The same methods cover every platform that has comments — Instagram, Facebook,
|
|
321
|
+
YouTube, LinkedIn and TikTok. There is no per-network method: the endpoint
|
|
322
|
+
answers one question and `platform` says who to ask.
|
|
323
|
+
|
|
324
|
+
```javascript
|
|
325
|
+
// Read the comments on a post
|
|
326
|
+
const { comments } = await client.getPostComments({
|
|
327
|
+
user: 'my-profile',
|
|
328
|
+
platform: 'tiktok',
|
|
329
|
+
postId: '7412345678901234567', // TikTok has no post-URL lookup, pass the video id
|
|
330
|
+
limit: 20,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Read the replies hanging from one of them — same question, one more parameter
|
|
334
|
+
const { comments: replies } = await client.getPostComments({
|
|
335
|
+
user: 'my-profile',
|
|
336
|
+
platform: 'tiktok',
|
|
337
|
+
postId: '7412345678901234567',
|
|
338
|
+
commentId: comments[0].id,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Comment on the post, or reply to a comment
|
|
342
|
+
await client.createComment({
|
|
343
|
+
user: 'my-profile',
|
|
344
|
+
platform: 'tiktok',
|
|
345
|
+
postId: '7412345678901234567',
|
|
346
|
+
message: 'Thanks for watching!',
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
await client.createComment({
|
|
350
|
+
user: 'my-profile',
|
|
351
|
+
platform: 'tiktok',
|
|
352
|
+
commentId: comments[0].id,
|
|
353
|
+
message: 'Glad you liked it',
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Delete one
|
|
357
|
+
await client.deleteComment({
|
|
358
|
+
user: 'my-profile',
|
|
359
|
+
platform: 'tiktok',
|
|
360
|
+
commentId: comments[0].id,
|
|
361
|
+
});
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
You can also have the first comment posted for you right after publishing, with
|
|
365
|
+
`firstComment` for every platform or `tiktokFirstComment` for TikTok alone.
|
|
366
|
+
|
|
367
|
+
On TikTok all of this needs the `comments` capability, which the account grants
|
|
368
|
+
when it connects — see
|
|
369
|
+
[What a TikTok connection can do](#what-a-tiktok-connection-can-do-capabilities).
|
|
370
|
+
|
|
371
|
+
#### Moderating a comment: hide, like, pin
|
|
372
|
+
|
|
373
|
+
`commentAction()` does the three and undoes them, on any platform that supports
|
|
374
|
+
it. Each action carries its own inverse, and `postId` is only sent when the
|
|
375
|
+
platform needs it:
|
|
376
|
+
|
|
377
|
+
```javascript
|
|
378
|
+
await client.commentAction({
|
|
379
|
+
user: 'my-profile',
|
|
380
|
+
platform: 'tiktok',
|
|
381
|
+
action: 'hide',
|
|
382
|
+
commentId: '7412345678909999999',
|
|
383
|
+
postId: '7412345678901234567',
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
await client.commentAction({
|
|
387
|
+
user: 'my-profile',
|
|
388
|
+
platform: 'tiktok',
|
|
389
|
+
action: 'like',
|
|
390
|
+
commentId: '7412345678909999999',
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
await client.commentAction({
|
|
394
|
+
user: 'my-profile',
|
|
395
|
+
platform: 'tiktok',
|
|
396
|
+
action: 'unpin',
|
|
397
|
+
commentId: '7412345678909999999',
|
|
398
|
+
postId: '7412345678901234567',
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
| `action` | Undo | `postId` |
|
|
403
|
+
| --- | --- | --- |
|
|
404
|
+
| `hide` | `unhide` | required |
|
|
405
|
+
| `like` | `unlike` | not sent |
|
|
406
|
+
| `pin` | `unpin` | required |
|
|
407
|
+
|
|
408
|
+
### Audience
|
|
409
|
+
|
|
410
|
+
Where the analytics methods answer *how did my posts do*, `getAudience()`
|
|
411
|
+
answers *who is following me*. One endpoint, one `platform` parameter, like
|
|
412
|
+
every other question in the API.
|
|
413
|
+
|
|
414
|
+
```javascript
|
|
415
|
+
const audience = await client.getAudience({
|
|
416
|
+
user: 'my-profile',
|
|
417
|
+
platform: 'tiktok',
|
|
418
|
+
startDate: '2026-07-01',
|
|
419
|
+
endDate: '2026-07-30',
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
console.log(audience.range); // the window actually used
|
|
423
|
+
console.log(audience.audience.countries); // and .cities, .ages, .genders
|
|
424
|
+
console.log(audience.activity_by_hour); // [{ hour: '14', followers_online: 1494 }, ...]
|
|
425
|
+
console.log(audience.followers_daily); // [{ date, total, new, lost }, ...]
|
|
426
|
+
console.log(audience.profile_actions); // bio link, address, email, phone, leads
|
|
427
|
+
console.log(audience.bio_description);
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
The window is clamped on the server: at most 60 days, and `endDate` always
|
|
431
|
+
before today. A wider window is trimmed to what the platform accepts instead of
|
|
432
|
+
failing.
|
|
433
|
+
|
|
434
|
+
Ask for a `benchmarkCategory` and the same call also returns how the account
|
|
435
|
+
compares with the average of that category. The accepted categories come back in
|
|
436
|
+
`benchmark_categories` on every response, so a picker needs no second call:
|
|
437
|
+
|
|
438
|
+
```javascript
|
|
439
|
+
const { benchmark_categories } = await client.getAudience({
|
|
440
|
+
user: 'my-profile', platform: 'tiktok',
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const { benchmark } = await client.getAudience({
|
|
444
|
+
user: 'my-profile',
|
|
445
|
+
platform: 'tiktok',
|
|
446
|
+
benchmarkCategory: 'SOFTWARE_AND_APPS',
|
|
447
|
+
});
|
|
448
|
+
console.log(benchmark.average_engagement_rate, benchmark.average_video_views);
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Suggestions
|
|
452
|
+
|
|
453
|
+
`getSuggestions()` answers *what is worth posting about*: the hashtags or the
|
|
454
|
+
searches a platform suggests around a keyword. One endpoint for both, told apart
|
|
455
|
+
by `type`.
|
|
456
|
+
|
|
457
|
+
```javascript
|
|
458
|
+
const { hashtags } = await client.getSuggestions({
|
|
459
|
+
user: 'my-profile',
|
|
460
|
+
platform: 'tiktok',
|
|
461
|
+
type: 'hashtags',
|
|
462
|
+
q: 'pilates',
|
|
463
|
+
countryCode: 'ES',
|
|
464
|
+
language: 'es',
|
|
465
|
+
});
|
|
466
|
+
console.log(hashtags); // [{ name, view_count }, ...]
|
|
467
|
+
|
|
468
|
+
const { keywords } = await client.getSuggestions({
|
|
469
|
+
user: 'my-profile',
|
|
470
|
+
platform: 'tiktok',
|
|
471
|
+
type: 'keywords',
|
|
472
|
+
q: 'pilates',
|
|
473
|
+
});
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Per-post numbers stay in `getPostAnalytics()`. On TikTok that response carries
|
|
477
|
+
more than the usual counters: `retention` (the curve, second by second),
|
|
478
|
+
`impression_sources` (For You, search, profile...), `audience_types` (followers
|
|
479
|
+
vs non-followers), `new_followers`, `reach` and the watch times.
|
|
480
|
+
|
|
481
|
+
Asking a platform a question it cannot answer fails with
|
|
482
|
+
`platform_not_supported` and the list of the ones that can.
|
|
483
|
+
|
|
484
|
+
## What a TikTok connection can do (`capabilities`)
|
|
485
|
+
|
|
486
|
+
Not every TikTok connection can do the same things. `listUsers()`
|
|
487
|
+
(`GET /api/uploadposts/users`) returns a `capabilities` array on each profile's
|
|
488
|
+
TikTok account; check it before offering a feature.
|
|
489
|
+
|
|
490
|
+
| Capability | What it unlocks |
|
|
491
|
+
| --- | --- |
|
|
492
|
+
| `music` | `tiktokMusicId` and the volume/trim fields, plus `getTiktokTrendingMusic()` and `searchTiktokMusic()` |
|
|
493
|
+
| `location` | `tiktokLocationId` / `tiktokLocationName`, plus `getTiktokLocations()` |
|
|
494
|
+
| `cover_image` | `tiktokCoverImageUrl` |
|
|
495
|
+
| `cover_timestamp` | `tiktokCoverTimestamp` |
|
|
496
|
+
| `draft` | `tiktokUploadToDraft` |
|
|
497
|
+
| `video_privacy` | `tiktokPrivacyLevel` on video |
|
|
498
|
+
| `photo_privacy` | `tiktokPrivacyLevel` on photo posts |
|
|
499
|
+
| `profile_analytics` | `getAudience()` and `getSuggestions({ type: 'hashtags' })` with `platform: 'tiktok'` |
|
|
500
|
+
| `comments` | Comments on TikTok: `getPostComments()` (top-level and replies), `createComment()`, `deleteComment()`, `commentAction()` and `tiktokFirstComment` |
|
|
501
|
+
| `trend_search` | `getSuggestions({ type: 'keywords' })` with `platform: 'tiktok'` |
|
|
502
|
+
|
|
503
|
+
> **`comments` and `trend_search` need the account to be reconnected.** TikTok
|
|
504
|
+
> grants them at connect time, so an account linked before they existed keeps
|
|
505
|
+
> working for everything else but will not list them — reconnect it from Manage
|
|
506
|
+
> Users to enable them.
|
|
507
|
+
|
|
508
|
+
If a connection lacks a capability the upload field is simply ignored: the post
|
|
509
|
+
still publishes and the response carries a per-field `warnings` entry. The
|
|
510
|
+
methods above answer with an error asking for a reconnection.
|
|
511
|
+
|
|
512
|
+
## TikTok music, location, cover and drafts
|
|
513
|
+
|
|
514
|
+
> Needs the `music`, `location`, `cover_image` or `draft` capability — see
|
|
515
|
+
> [What a TikTok connection can do](#what-a-tiktok-connection-can-do-capabilities).
|
|
516
|
+
|
|
517
|
+
```javascript
|
|
518
|
+
// 1. Pick a track and a place
|
|
519
|
+
const { tracks } = await client.getTiktokTrendingMusic('my-profile', { countryCode: 'ES' });
|
|
520
|
+
// ...or find one by name. TikTok has no music search endpoint, so this searches
|
|
521
|
+
// the trending charts Upload-Post caches, not TikTok's whole catalogue.
|
|
522
|
+
// const { tracks } = await client.searchTiktokMusic('my-profile', { q: 'bossa', countryCode: 'ES' });
|
|
523
|
+
const { locations } = await client.getTiktokLocations('my-profile', 'Madrid');
|
|
524
|
+
|
|
525
|
+
// 2. Publish with them
|
|
526
|
+
await client.upload('./video.mp4', {
|
|
527
|
+
title: 'Shot in Madrid',
|
|
528
|
+
user: 'my-profile',
|
|
529
|
+
platforms: ['tiktok'],
|
|
530
|
+
|
|
531
|
+
tiktokMusicId: tracks[0].id,
|
|
532
|
+
tiktokMusicVolume: 70, // 0-100, defaults to 50 when music is set
|
|
533
|
+
tiktokMusicStart: 0, // ms
|
|
534
|
+
tiktokMusicEnd: 15000, // ms
|
|
535
|
+
tiktokOriginalSoundVolume: 30, // 0-100, defaults to 50 so the original audio is not muted
|
|
536
|
+
|
|
537
|
+
tiktokLocationId: locations[0].location_id,
|
|
538
|
+
tiktokLocationName: locations[0].location_name, // required together with the id
|
|
539
|
+
|
|
540
|
+
tiktokCoverImageUrl: 'https://example.com/cover.jpg',
|
|
541
|
+
tiktokIsAiGenerated: false,
|
|
542
|
+
tiktokUploadToDraft: false, // true sends it to drafts and ignores the rest
|
|
543
|
+
});
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Options
|
|
547
|
+
|
|
548
|
+
| Option | Type | Notes |
|
|
549
|
+
| --- | --- | --- |
|
|
550
|
+
| `tiktokMusicId` | string | The track `id` from `getTiktokTrendingMusic()` or `searchTiktokMusic()` (not `commercial_music_id`) |
|
|
551
|
+
| `tiktokMusicVolume` | number | 0-100. Defaults to 50 when music is set |
|
|
552
|
+
| `tiktokMusicStart` | number | Music start offset in ms |
|
|
553
|
+
| `tiktokMusicEnd` | number | Music end offset in ms |
|
|
554
|
+
| `tiktokOriginalSoundVolume` | number | 0-100. Defaults to 50 when music is set, so the original audio is not muted |
|
|
555
|
+
| `tiktokLocationId` | string | `location_id` from `getTiktokLocations()` |
|
|
556
|
+
| `tiktokLocationName` | string | Required whenever `tiktokLocationId` is set |
|
|
557
|
+
| `tiktokCoverImageUrl` | string | Custom cover image URL |
|
|
558
|
+
| `tiktokIsAiGenerated` | boolean | AI-generated content disclosure |
|
|
559
|
+
| `tiktokUploadToDraft` | boolean | Publish to drafts; TikTok ignores the rest of the post settings |
|
|
560
|
+
| `tiktokPhotoCoverIndex` | number | Cover photo index for photo posts (0-based) |
|
|
561
|
+
|
|
300
562
|
## Platform-Specific Options
|
|
301
563
|
|
|
302
564
|
### TikTok (Video)
|
|
@@ -310,10 +572,29 @@ const boards = await client.getPinterestBoards('my-profile');
|
|
|
310
572
|
- `brandContentToggle` - Branded content toggle
|
|
311
573
|
- `brandOrganicToggle` - Brand organic toggle
|
|
312
574
|
|
|
575
|
+
> **Which privacy levels are available is decided by TikTok per account.** A
|
|
576
|
+
> private account, for example, is offered `FOLLOWER_OF_CREATOR`,
|
|
577
|
+
> `MUTUAL_FOLLOW_FRIENDS` and `SELF_ONLY` and has no `PUBLIC_TO_EVERYONE`.
|
|
578
|
+
> Asking for one the account does not have fails with
|
|
579
|
+
> `error_code: "tiktok_privacy_unavailable"` and an error listing the ones it
|
|
580
|
+
> does have. Omit `tiktokPrivacyLevel` on video and TikTok keeps the account's
|
|
581
|
+
> own default; on **photo** posts it defaults to `PUBLIC_TO_EVERYONE`.
|
|
582
|
+
|
|
583
|
+
See [TikTok music, location, cover and drafts](#tiktok-music-location-cover-and-drafts)
|
|
584
|
+
for those options.
|
|
585
|
+
|
|
313
586
|
### TikTok (Photos)
|
|
314
587
|
- `tiktokAutoAddMusic` - Auto add music
|
|
315
588
|
- `tiktokPhotoCoverIndex` - Index of photo for cover (0-based)
|
|
316
589
|
- `tiktokDisableComment` - Disable comments
|
|
590
|
+
- `tiktokPrivacyLevel` - PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY (same field and same per-account limits as video)
|
|
591
|
+
- `tiktokMusicId` - Commercial Music Library track id (see `getTiktokTrendingMusic`)
|
|
592
|
+
- `tiktokLocationId` / `tiktokLocationName` - Location tag, both required together
|
|
593
|
+
- `tiktokIsAiGenerated` - AI-generated content disclosure
|
|
594
|
+
|
|
595
|
+
> TikTok's photo contract takes the music track id alone: `tiktokMusicVolume`,
|
|
596
|
+
> `tiktokMusicStart`, `tiktokMusicEnd`, `tiktokOriginalSoundVolume`,
|
|
597
|
+
> `tiktokCoverImageUrl` and `tiktokUploadToDraft` are video-only.
|
|
317
598
|
|
|
318
599
|
### Instagram
|
|
319
600
|
- `instagramMediaType` - REELS, STORIES, IMAGE
|
|
@@ -393,6 +674,7 @@ These options work across all upload methods:
|
|
|
393
674
|
| `user` | Profile name (required) |
|
|
394
675
|
| `platforms` | Target platforms array (required) |
|
|
395
676
|
| `firstComment` | First comment to post |
|
|
677
|
+
| `tiktokFirstComment` | First comment for TikTok only (needs the `comments` capability) |
|
|
396
678
|
| `altText` | Alt text for accessibility |
|
|
397
679
|
| `scheduledDate` | ISO date for scheduling |
|
|
398
680
|
| `timezone` | Timezone for scheduled date |
|
package/index.d.ts
CHANGED
|
@@ -109,12 +109,22 @@ declare module 'upload-post' {
|
|
|
109
109
|
youtubeFirstComment?: string;
|
|
110
110
|
redditFirstComment?: string;
|
|
111
111
|
blueskyFirstComment?: string;
|
|
112
|
+
/**
|
|
113
|
+
* Needs the `comments` capability on the connection: without it the post
|
|
114
|
+
* still publishes and the response carries a warning instead of the comment.
|
|
115
|
+
*/
|
|
116
|
+
tiktokFirstComment?: string;
|
|
112
117
|
}
|
|
113
118
|
|
|
114
119
|
// ==================== TikTok Options ====================
|
|
115
120
|
|
|
116
121
|
export interface TikTokVideoOptions {
|
|
117
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Privacy setting. TikTok decides per account which levels are available (a
|
|
124
|
+
* private account has no `PUBLIC_TO_EVERYONE`); asking for another one fails
|
|
125
|
+
* with `error_code: "tiktok_privacy_unavailable"` listing the allowed ones.
|
|
126
|
+
* Omit it to keep the account's own default.
|
|
127
|
+
*/
|
|
118
128
|
tiktokPrivacyLevel?: TikTokPrivacyLevel;
|
|
119
129
|
/** Disable duet */
|
|
120
130
|
tiktokDisableDuet?: boolean;
|
|
@@ -132,6 +142,35 @@ declare module 'upload-post' {
|
|
|
132
142
|
brandContentToggle?: boolean;
|
|
133
143
|
/** Brand organic toggle */
|
|
134
144
|
brandOrganicToggle?: boolean;
|
|
145
|
+
|
|
146
|
+
// ---- Music, location, cover and draft ----
|
|
147
|
+
// Available on connections that declare the matching capability (`music`,
|
|
148
|
+
// `location`, `cover_image`, `draft`) — see `capabilities` on the TikTok
|
|
149
|
+
// account returned by listUsers() / GET /api/uploadposts/users. If your
|
|
150
|
+
// connection does not have it, the field is ignored, the post still
|
|
151
|
+
// publishes, and the response includes a per-field `warnings` entry —
|
|
152
|
+
// reconnect the TikTok account to enable it.
|
|
153
|
+
|
|
154
|
+
/** Commercial Music Library track id (see getTiktokTrendingMusic) */
|
|
155
|
+
tiktokMusicId?: string;
|
|
156
|
+
/** Music volume, 0-100. Defaults to 50 when music is set */
|
|
157
|
+
tiktokMusicVolume?: number;
|
|
158
|
+
/** Music start offset in ms */
|
|
159
|
+
tiktokMusicStart?: number;
|
|
160
|
+
/** Music end offset in ms */
|
|
161
|
+
tiktokMusicEnd?: number;
|
|
162
|
+
/** Original video audio volume, 0-100. Defaults to 50 when music is set, so the original audio is not muted */
|
|
163
|
+
tiktokOriginalSoundVolume?: number;
|
|
164
|
+
/** Location id to tag (see getTiktokLocations) */
|
|
165
|
+
tiktokLocationId?: string;
|
|
166
|
+
/** Location name. Required whenever tiktokLocationId is set */
|
|
167
|
+
tiktokLocationName?: string;
|
|
168
|
+
/** Custom cover image URL */
|
|
169
|
+
tiktokCoverImageUrl?: string;
|
|
170
|
+
/** AI-generated content disclosure */
|
|
171
|
+
tiktokIsAiGenerated?: boolean;
|
|
172
|
+
/** Publish to drafts. When true TikTok ignores the rest of the post settings */
|
|
173
|
+
tiktokUploadToDraft?: boolean;
|
|
135
174
|
}
|
|
136
175
|
|
|
137
176
|
export interface TikTokPhotoOptions {
|
|
@@ -139,16 +178,39 @@ declare module 'upload-post' {
|
|
|
139
178
|
tiktokAutoAddMusic?: boolean;
|
|
140
179
|
/** Disable comments */
|
|
141
180
|
tiktokDisableComment?: boolean;
|
|
142
|
-
/** Index of photo for cover (0-based) */
|
|
181
|
+
/** Index of photo for cover (0-based). Sent as `photo_cover_index` */
|
|
143
182
|
tiktokPhotoCoverIndex?: number;
|
|
144
|
-
/**
|
|
145
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Privacy setting. TikTok REQUIRES one on photo posts, so it defaults to
|
|
185
|
+
* `PUBLIC_TO_EVERYONE`. Which values the account may use is decided by TikTok
|
|
186
|
+
* (a private account has no `PUBLIC_TO_EVERYONE`); asking for another one
|
|
187
|
+
* fails with `error_code: "tiktok_privacy_unavailable"` listing the allowed
|
|
188
|
+
* ones.
|
|
189
|
+
*/
|
|
190
|
+
tiktokPrivacyLevel?: TikTokPrivacyLevel;
|
|
146
191
|
/** Post mode, e.g. DIRECT_POST or MEDIA_UPLOAD (inbox) */
|
|
147
192
|
tiktokPostMode?: string;
|
|
148
193
|
/** Branded content toggle */
|
|
149
194
|
brandContentToggle?: boolean;
|
|
150
195
|
/** Brand organic toggle */
|
|
151
196
|
brandOrganicToggle?: boolean;
|
|
197
|
+
|
|
198
|
+
// ---- Music, location and AI disclosure ----
|
|
199
|
+
// Photo posts accept the track id, the location pair and the AI disclosure —
|
|
200
|
+
// not the volume/trim, cover-image or draft fields, which are video-only.
|
|
201
|
+
// Available on connections that declare the matching capability (`music`,
|
|
202
|
+
// `location`) — see `capabilities` on the TikTok account returned by
|
|
203
|
+
// listUsers(). Without it the field is ignored, the post still publishes, and
|
|
204
|
+
// the response includes a per-field `warnings` entry.
|
|
205
|
+
|
|
206
|
+
/** Commercial Music Library track id (see getTiktokTrendingMusic) */
|
|
207
|
+
tiktokMusicId?: string;
|
|
208
|
+
/** Location id to tag (see getTiktokLocations) */
|
|
209
|
+
tiktokLocationId?: string;
|
|
210
|
+
/** Location name. Required whenever tiktokLocationId is set */
|
|
211
|
+
tiktokLocationName?: string;
|
|
212
|
+
/** AI-generated content disclosure */
|
|
213
|
+
tiktokIsAiGenerated?: boolean;
|
|
152
214
|
}
|
|
153
215
|
|
|
154
216
|
// ==================== Instagram Options ====================
|
|
@@ -580,6 +642,156 @@ declare module 'upload-post' {
|
|
|
580
642
|
[key: string]: any;
|
|
581
643
|
}
|
|
582
644
|
|
|
645
|
+
/** Trending window accepted by the TikTok Commercial Music Library. */
|
|
646
|
+
export type TikTokMusicDateRange = '1DAY' | '7DAY' | '30DAY' | '90DAY';
|
|
647
|
+
|
|
648
|
+
export interface TikTokMusicTrack {
|
|
649
|
+
/** Pass this as `tiktokMusicId` on an upload. */
|
|
650
|
+
id: string;
|
|
651
|
+
/**
|
|
652
|
+
* TikTok's catalogue id for the same track, returned for reference only.
|
|
653
|
+
* Do NOT send it as `tiktokMusicId`: TikTok rejects it on public posts.
|
|
654
|
+
*/
|
|
655
|
+
commercial_music_id?: string;
|
|
656
|
+
title?: string;
|
|
657
|
+
artist?: string;
|
|
658
|
+
/** Track duration in seconds. */
|
|
659
|
+
duration?: number;
|
|
660
|
+
genres?: string[];
|
|
661
|
+
/** Artwork image URL. */
|
|
662
|
+
cover_url?: string;
|
|
663
|
+
/** Audio preview URL. */
|
|
664
|
+
preview_url?: string;
|
|
665
|
+
rank?: number;
|
|
666
|
+
[key: string]: any;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
export interface TikTokTrendingMusicResponse {
|
|
670
|
+
success: boolean;
|
|
671
|
+
tracks?: TikTokMusicTrack[];
|
|
672
|
+
[key: string]: any;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
export interface TikTokMusicSearchResponse {
|
|
676
|
+
success: boolean;
|
|
677
|
+
/** Matches, ranked by relevance; same shape as the trending endpoint. */
|
|
678
|
+
tracks?: TikTokMusicTrack[];
|
|
679
|
+
/** How many tracks matched before `limit` was applied. */
|
|
680
|
+
total?: number;
|
|
681
|
+
query?: string;
|
|
682
|
+
/** What the search actually ran against. */
|
|
683
|
+
catalog?: {
|
|
684
|
+
/** Number of tracks in the searched corpus. */
|
|
685
|
+
tracks_indexed?: number;
|
|
686
|
+
/** Genre charts currently loaded for this country and period. */
|
|
687
|
+
genres_indexed?: string[];
|
|
688
|
+
/** False when this request had to load a chart from TikTok. */
|
|
689
|
+
cached?: boolean;
|
|
690
|
+
};
|
|
691
|
+
[key: string]: any;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
export interface TikTokLocation {
|
|
695
|
+
location_id: string;
|
|
696
|
+
location_name?: string;
|
|
697
|
+
location_address?: string;
|
|
698
|
+
[key: string]: any;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
export interface TikTokLocationsResponse {
|
|
702
|
+
success: boolean;
|
|
703
|
+
locations?: TikTokLocation[];
|
|
704
|
+
[key: string]: any;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
export interface TikTokPublishingSettingsResponse {
|
|
708
|
+
success: boolean;
|
|
709
|
+
/** The privacy levels THIS account may use — a subset of the four. */
|
|
710
|
+
privacy_level_options?: TikTokPrivacyLevel[];
|
|
711
|
+
/** Longest video the account can publish, in seconds. */
|
|
712
|
+
max_video_post_duration_sec?: number;
|
|
713
|
+
comment_disabled?: boolean;
|
|
714
|
+
duet_disabled?: boolean;
|
|
715
|
+
stitch_disabled?: boolean;
|
|
716
|
+
[key: string]: any;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// ==================== Audience, Suggestions and Comment Actions ====================
|
|
720
|
+
|
|
721
|
+
/** One row of an audience breakdown: a bucket and its share. */
|
|
722
|
+
export interface AudienceSlice {
|
|
723
|
+
name?: string;
|
|
724
|
+
value?: number;
|
|
725
|
+
percentage?: number;
|
|
726
|
+
[key: string]: any;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/** Category averages returned when `benchmarkCategory` is asked for. */
|
|
730
|
+
export interface AudienceBenchmark {
|
|
731
|
+
category?: string;
|
|
732
|
+
average_comments?: number;
|
|
733
|
+
average_engagement_rate?: number;
|
|
734
|
+
average_follower_count?: number;
|
|
735
|
+
average_follower_growth?: number;
|
|
736
|
+
average_likes?: number;
|
|
737
|
+
average_shares?: number;
|
|
738
|
+
average_video_count?: number;
|
|
739
|
+
average_video_views?: number;
|
|
740
|
+
[key: string]: any;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
export interface AudienceResponse {
|
|
744
|
+
success: boolean;
|
|
745
|
+
platform?: string;
|
|
746
|
+
/** The window actually used after the 60-day / not-today clamps. */
|
|
747
|
+
range?: { start_date?: string; end_date?: string };
|
|
748
|
+
audience?: {
|
|
749
|
+
countries?: AudienceSlice[];
|
|
750
|
+
cities?: AudienceSlice[];
|
|
751
|
+
ages?: AudienceSlice[];
|
|
752
|
+
genders?: AudienceSlice[];
|
|
753
|
+
};
|
|
754
|
+
/** Followers online per hour of the day, e.g. `{ hour: '14', followers_online: 1494 }`. */
|
|
755
|
+
activity_by_hour?: Array<{ hour?: string; followers_online?: number; [key: string]: any }>;
|
|
756
|
+
/** Follower count per day inside the window. */
|
|
757
|
+
followers_daily?: Array<{ date?: string; total?: number; new?: number; lost?: number; [key: string]: any }>;
|
|
758
|
+
/** Taps on the profile: bio link, address, app download, email, phone, leads. */
|
|
759
|
+
profile_actions?: Record<string, number>;
|
|
760
|
+
bio_description?: string | null;
|
|
761
|
+
/** Every category `benchmarkCategory` accepts, so a picker needs no extra call. */
|
|
762
|
+
benchmark_categories?: string[];
|
|
763
|
+
/** Only when a `benchmarkCategory` was asked for. */
|
|
764
|
+
benchmark?: AudienceBenchmark;
|
|
765
|
+
[key: string]: any;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/** Which kind of suggestion `getSuggestions` asks for. */
|
|
769
|
+
export type SuggestionType = 'hashtags' | 'keywords';
|
|
770
|
+
|
|
771
|
+
export interface SuggestionsResponse {
|
|
772
|
+
success: boolean;
|
|
773
|
+
platform?: string;
|
|
774
|
+
type?: SuggestionType;
|
|
775
|
+
query?: string | null;
|
|
776
|
+
/** With `type: 'hashtags'`. */
|
|
777
|
+
hashtags?: Array<{ name?: string; view_count?: number; [key: string]: any }>;
|
|
778
|
+
/** With `type: 'keywords'`. */
|
|
779
|
+
keywords?: Array<Record<string, any>>;
|
|
780
|
+
[key: string]: any;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/** What `commentAction` does to a comment. Each action carries its own inverse. */
|
|
784
|
+
export type CommentActionValue = 'hide' | 'unhide' | 'like' | 'unlike' | 'pin' | 'unpin';
|
|
785
|
+
|
|
786
|
+
export interface CommentActionResponse {
|
|
787
|
+
success: boolean;
|
|
788
|
+
platform?: string;
|
|
789
|
+
action?: CommentActionValue;
|
|
790
|
+
comment_id?: string;
|
|
791
|
+
result?: Record<string, any>;
|
|
792
|
+
[key: string]: any;
|
|
793
|
+
}
|
|
794
|
+
|
|
583
795
|
// ==================== Client Class ====================
|
|
584
796
|
|
|
585
797
|
/**
|
|
@@ -675,7 +887,15 @@ declare module 'upload-post' {
|
|
|
675
887
|
}>;
|
|
676
888
|
|
|
677
889
|
/**
|
|
678
|
-
* Get analytics for a specific post across all platforms
|
|
890
|
+
* Get analytics for a specific post across all platforms.
|
|
891
|
+
*
|
|
892
|
+
* `post_metrics` carries whatever the platform reports, so it is not the
|
|
893
|
+
* same shape everywhere: TikTok adds `retention` (the curve, second by
|
|
894
|
+
* second), `impression_sources` (For You, search, profile...),
|
|
895
|
+
* `audience_types` (followers vs non-followers), `new_followers`, `reach`
|
|
896
|
+
* and the watch times (`average_time_watched`, `total_time_watched`,
|
|
897
|
+
* `full_video_watched_rate`) on top of the usual counters.
|
|
898
|
+
*
|
|
679
899
|
* @param requestId - The request_id from the upload
|
|
680
900
|
*/
|
|
681
901
|
getPostAnalytics(requestId: string): Promise<{
|
|
@@ -685,7 +905,8 @@ declare module 'upload-post' {
|
|
|
685
905
|
success: boolean;
|
|
686
906
|
platform_post_id?: string;
|
|
687
907
|
post_url?: string;
|
|
688
|
-
|
|
908
|
+
/** Counters plus, on TikTok, retention / impression_sources / audience_types and watch times. */
|
|
909
|
+
post_metrics?: Record<string, any>;
|
|
689
910
|
post_metrics_source?: string;
|
|
690
911
|
post_metrics_error?: string;
|
|
691
912
|
profile_snapshot_at_post_date?: Record<string, number>;
|
|
@@ -737,6 +958,47 @@ declare module 'upload-post' {
|
|
|
737
958
|
has_more: boolean;
|
|
738
959
|
}>;
|
|
739
960
|
|
|
961
|
+
/**
|
|
962
|
+
* Get who the audience is: where they are, how old they are, when they are
|
|
963
|
+
* online and what they tap on the profile.
|
|
964
|
+
*
|
|
965
|
+
* One endpoint for every platform, chosen with `platform`; a platform that
|
|
966
|
+
* cannot answer it returns `platform_not_supported` with the list of the
|
|
967
|
+
* ones that can. The window is clamped server-side to at most 60 days
|
|
968
|
+
* ending before today.
|
|
969
|
+
*
|
|
970
|
+
* @param options - Query options
|
|
971
|
+
*/
|
|
972
|
+
getAudience(options: {
|
|
973
|
+
user: string;
|
|
974
|
+
platform: 'tiktok';
|
|
975
|
+
/** Window start, ISO `YYYY-MM-DD`. */
|
|
976
|
+
startDate?: string;
|
|
977
|
+
/** Window end, ISO `YYYY-MM-DD`. Always clamped to before today. */
|
|
978
|
+
endDate?: string;
|
|
979
|
+
/** Compare the account against this category's averages. */
|
|
980
|
+
benchmarkCategory?: string;
|
|
981
|
+
}): Promise<AudienceResponse>;
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Get what to write about: the hashtags or the searches a platform
|
|
985
|
+
* suggests around a keyword.
|
|
986
|
+
*
|
|
987
|
+
* One endpoint for both questions, told apart by `type`, and one endpoint
|
|
988
|
+
* for every platform, chosen with `platform`.
|
|
989
|
+
*
|
|
990
|
+
* @param options - Query options
|
|
991
|
+
*/
|
|
992
|
+
getSuggestions(options: {
|
|
993
|
+
user: string;
|
|
994
|
+
platform: 'tiktok';
|
|
995
|
+
type: SuggestionType;
|
|
996
|
+
/** Keyword to get suggestions around. */
|
|
997
|
+
q?: string;
|
|
998
|
+
countryCode?: string;
|
|
999
|
+
language?: string;
|
|
1000
|
+
}): Promise<SuggestionsResponse>;
|
|
1001
|
+
|
|
740
1002
|
/**
|
|
741
1003
|
* Get available metrics configuration for all supported platforms
|
|
742
1004
|
*/
|
|
@@ -888,17 +1150,23 @@ declare module 'upload-post' {
|
|
|
888
1150
|
*/
|
|
889
1151
|
testNotifications(): Promise<{ success: boolean; [key: string]: any }>;
|
|
890
1152
|
|
|
891
|
-
//
|
|
1153
|
+
// Comments
|
|
892
1154
|
|
|
893
1155
|
/**
|
|
894
|
-
* Get comments on a post
|
|
1156
|
+
* Get comments on a post, or the replies hanging from one of them.
|
|
1157
|
+
*
|
|
1158
|
+
* Pass `commentId` to read that comment's replies instead of the post's
|
|
1159
|
+
* top-level comments.
|
|
1160
|
+
*
|
|
895
1161
|
* @param options - Query options
|
|
896
1162
|
*/
|
|
897
1163
|
getPostComments(options: {
|
|
898
1164
|
user: string;
|
|
899
|
-
platform?: 'instagram' | 'facebook' | 'youtube' | 'linkedin';
|
|
1165
|
+
platform?: 'instagram' | 'facebook' | 'youtube' | 'linkedin' | 'tiktok';
|
|
900
1166
|
postId?: string;
|
|
901
1167
|
postUrl?: string;
|
|
1168
|
+
/** Read the replies to this comment instead of the post's top-level comments. */
|
|
1169
|
+
commentId?: string;
|
|
902
1170
|
limit?: number;
|
|
903
1171
|
after?: string;
|
|
904
1172
|
}): Promise<{
|
|
@@ -955,7 +1223,7 @@ declare module 'upload-post' {
|
|
|
955
1223
|
*/
|
|
956
1224
|
createComment(options: {
|
|
957
1225
|
user: string;
|
|
958
|
-
platform: 'instagram' | 'facebook' | 'youtube' | 'linkedin';
|
|
1226
|
+
platform: 'instagram' | 'facebook' | 'youtube' | 'linkedin' | 'tiktok';
|
|
959
1227
|
message: string;
|
|
960
1228
|
postId?: string;
|
|
961
1229
|
postUrl?: string;
|
|
@@ -973,7 +1241,7 @@ declare module 'upload-post' {
|
|
|
973
1241
|
*/
|
|
974
1242
|
deleteComment(options: {
|
|
975
1243
|
user: string;
|
|
976
|
-
platform: 'instagram' | 'facebook' | 'youtube' | 'linkedin';
|
|
1244
|
+
platform: 'instagram' | 'facebook' | 'youtube' | 'linkedin' | 'tiktok';
|
|
977
1245
|
commentId: string;
|
|
978
1246
|
/** Post identifier (the post URN for LinkedIn) */
|
|
979
1247
|
postId?: string;
|
|
@@ -983,6 +1251,24 @@ declare module 'upload-post' {
|
|
|
983
1251
|
error?: string;
|
|
984
1252
|
}>;
|
|
985
1253
|
|
|
1254
|
+
/**
|
|
1255
|
+
* Moderate a comment: hide, like or pin it — and undo any of the three.
|
|
1256
|
+
*
|
|
1257
|
+
* One method for every platform, chosen with `platform`. `postId` is
|
|
1258
|
+
* required for hide/unhide and pin/unpin; like/unlike take the comment
|
|
1259
|
+
* alone and never send it.
|
|
1260
|
+
*
|
|
1261
|
+
* @param options - Action options
|
|
1262
|
+
*/
|
|
1263
|
+
commentAction(options: {
|
|
1264
|
+
user: string;
|
|
1265
|
+
platform: 'tiktok';
|
|
1266
|
+
commentId: string;
|
|
1267
|
+
action: CommentActionValue;
|
|
1268
|
+
/** Required for hide/unhide and pin/unpin; never sent for like/unlike. */
|
|
1269
|
+
postId?: string;
|
|
1270
|
+
}): Promise<CommentActionResponse>;
|
|
1271
|
+
|
|
986
1272
|
// Post Management
|
|
987
1273
|
/**
|
|
988
1274
|
* Retry a failed post. Provide requestId or jobId.
|
|
@@ -1048,6 +1334,76 @@ declare module 'upload-post' {
|
|
|
1048
1334
|
* @param profile - Profile username
|
|
1049
1335
|
*/
|
|
1050
1336
|
getGoogleBusinessLocations(profile?: string): Promise<Record<string, unknown>>;
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* Get trending tracks from the TikTok Commercial Music Library.
|
|
1340
|
+
*
|
|
1341
|
+
* Available on connections that declare the `music` capability (see
|
|
1342
|
+
* `capabilities` on the TikTok account returned by listUsers() /
|
|
1343
|
+
* GET /api/uploadposts/users). Pass the returned `id` as `tiktokMusicId`
|
|
1344
|
+
* on an upload (NOT `commercial_music_id` — TikTok rejects that one on
|
|
1345
|
+
* public posts).
|
|
1346
|
+
*
|
|
1347
|
+
* @param profile - Profile username
|
|
1348
|
+
* @param options - Query options
|
|
1349
|
+
*/
|
|
1350
|
+
getTiktokTrendingMusic(
|
|
1351
|
+
profile: string,
|
|
1352
|
+
options?: {
|
|
1353
|
+
genre?: string;
|
|
1354
|
+
countryCode?: string;
|
|
1355
|
+
dateRange?: TikTokMusicDateRange;
|
|
1356
|
+
}
|
|
1357
|
+
): Promise<TikTokTrendingMusicResponse>;
|
|
1358
|
+
|
|
1359
|
+
/**
|
|
1360
|
+
* Search the TikTok Commercial Music Library by song title or artist.
|
|
1361
|
+
*
|
|
1362
|
+
* TikTok has no music search endpoint, so this searches the trending charts
|
|
1363
|
+
* Upload-Post caches (per genre / country / period), not TikTok's whole
|
|
1364
|
+
* catalogue. Matching is case- and accent-insensitive and every word must
|
|
1365
|
+
* match. Returns the same track objects as getTiktokTrendingMusic().
|
|
1366
|
+
*
|
|
1367
|
+
* @param profile - Profile username
|
|
1368
|
+
* @param options - Query options
|
|
1369
|
+
*/
|
|
1370
|
+
searchTiktokMusic(
|
|
1371
|
+
profile: string,
|
|
1372
|
+
options?: {
|
|
1373
|
+
q?: string;
|
|
1374
|
+
genre?: string;
|
|
1375
|
+
countryCode?: string;
|
|
1376
|
+
dateRange?: TikTokMusicDateRange;
|
|
1377
|
+
limit?: number;
|
|
1378
|
+
}
|
|
1379
|
+
): Promise<TikTokMusicSearchResponse>;
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* Search TikTok locations (places) to tag on a post.
|
|
1383
|
+
*
|
|
1384
|
+
* Available on connections that declare the `location` capability (see
|
|
1385
|
+
* `capabilities` on the TikTok account returned by listUsers() /
|
|
1386
|
+
* GET /api/uploadposts/users). TikTok requires both the id and the name, so
|
|
1387
|
+
* pass `location_id` as `tiktokLocationId` and `location_name` as
|
|
1388
|
+
* `tiktokLocationName`.
|
|
1389
|
+
*
|
|
1390
|
+
* @param profile - Profile username
|
|
1391
|
+
* @param query - Search query (max 100 characters)
|
|
1392
|
+
*/
|
|
1393
|
+
getTiktokLocations(profile: string, query: string): Promise<TikTokLocationsResponse>;
|
|
1394
|
+
|
|
1395
|
+
/**
|
|
1396
|
+
* Get what the connected TikTok account is allowed to publish.
|
|
1397
|
+
*
|
|
1398
|
+
* The point of this call is `privacy_level_options`: TikTok narrows the four
|
|
1399
|
+
* privacy values per account (a private account has no `PUBLIC_TO_EVERYONE`),
|
|
1400
|
+
* and sending one the account does not have fails the upload with
|
|
1401
|
+
* `error_code: "tiktok_privacy_unavailable"`. Ask here to offer only the
|
|
1402
|
+
* values that will work, instead of the full enum.
|
|
1403
|
+
*
|
|
1404
|
+
* @param profile - Profile username
|
|
1405
|
+
*/
|
|
1406
|
+
getTiktokPublishingSettings(profile: string): Promise<TikTokPublishingSettingsResponse>;
|
|
1051
1407
|
}
|
|
1052
1408
|
|
|
1053
1409
|
export default UploadPost;
|
package/index.js
CHANGED
|
@@ -128,6 +128,10 @@ export class UploadPost {
|
|
|
128
128
|
if (options.redditFirstComment) form.append('reddit_first_comment', options.redditFirstComment);
|
|
129
129
|
if (options.blueskyFirstComment) form.append('bluesky_first_comment', options.blueskyFirstComment);
|
|
130
130
|
if (options.linkedinFirstComment) form.append('linkedin_first_comment', options.linkedinFirstComment);
|
|
131
|
+
// TikTok takes a first comment like everyone else. It needs the `comments`
|
|
132
|
+
// capability on the connection: without it the post still publishes and the
|
|
133
|
+
// response carries a warning instead of the comment.
|
|
134
|
+
if (options.tiktokFirstComment) form.append('tiktok_first_comment', options.tiktokFirstComment);
|
|
131
135
|
|
|
132
136
|
if (options.firstCommentMedia) {
|
|
133
137
|
const mediaItems = Array.isArray(options.firstCommentMedia) ? options.firstCommentMedia : [options.firstCommentMedia];
|
|
@@ -152,25 +156,54 @@ export class UploadPost {
|
|
|
152
156
|
* @private
|
|
153
157
|
*/
|
|
154
158
|
_addTiktokParams(form, options, isVideo = true) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
// photo carousels always publish as
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
159
|
+
// [form field, option key, "flag" when the value is a boolean/number sent as
|
|
160
|
+
// soon as it is defined, "text" when an empty value means "not set"].
|
|
161
|
+
//
|
|
162
|
+
// The shared block is shared on purpose: the backend reads privacy_level and
|
|
163
|
+
// post_mode on both /upload and /upload_photos, and TikTok's photo endpoint
|
|
164
|
+
// also accepts the music track id, the location pair and is_ai_generated.
|
|
165
|
+
// Gating them behind isVideo made photo carousels always publish as
|
|
166
|
+
// PUBLIC_TO_EVERYONE / DIRECT_POST and unable to carry music or a location.
|
|
167
|
+
//
|
|
168
|
+
// The music/location/cover/draft fields need the matching capability on the
|
|
169
|
+
// connection (see `capabilities` on the TikTok account returned by
|
|
170
|
+
// listUsers() / GET /api/uploadposts/users). Without it the field is ignored,
|
|
171
|
+
// the post still publishes, and the response carries a per-field `warnings`
|
|
172
|
+
// entry.
|
|
173
|
+
const shared = [
|
|
174
|
+
['disable_comment', 'tiktokDisableComment', 'flag'],
|
|
175
|
+
['brand_content_toggle', 'brandContentToggle', 'flag'],
|
|
176
|
+
['brand_organic_toggle', 'brandOrganicToggle', 'flag'],
|
|
177
|
+
['privacy_level', 'tiktokPrivacyLevel', 'text'],
|
|
178
|
+
['post_mode', 'tiktokPostMode', 'text'],
|
|
179
|
+
['tiktok_music_id', 'tiktokMusicId', 'text'],
|
|
180
|
+
['tiktok_location_id', 'tiktokLocationId', 'text'],
|
|
181
|
+
['tiktok_location_name', 'tiktokLocationName', 'text'],
|
|
182
|
+
['tiktok_is_ai_generated', 'tiktokIsAiGenerated', 'flag'],
|
|
183
|
+
];
|
|
184
|
+
// Video-only: TikTok's photo contract takes the track id alone, with no
|
|
185
|
+
// volume or trim, and has no custom cover or draft switch.
|
|
186
|
+
const videoOnly = [
|
|
187
|
+
['disable_duet', 'tiktokDisableDuet', 'flag'],
|
|
188
|
+
['disable_stitch', 'tiktokDisableStitch', 'flag'],
|
|
189
|
+
['cover_timestamp', 'tiktokCoverTimestamp', 'flag'],
|
|
190
|
+
['is_aigc', 'tiktokIsAigc', 'flag'],
|
|
191
|
+
['tiktok_music_volume', 'tiktokMusicVolume', 'flag'],
|
|
192
|
+
['tiktok_music_start', 'tiktokMusicStart', 'flag'],
|
|
193
|
+
['tiktok_music_end', 'tiktokMusicEnd', 'flag'],
|
|
194
|
+
['tiktok_original_sound_volume', 'tiktokOriginalSoundVolume', 'flag'],
|
|
195
|
+
['tiktok_cover_image_url', 'tiktokCoverImageUrl', 'text'],
|
|
196
|
+
['tiktok_upload_to_draft', 'tiktokUploadToDraft', 'flag'],
|
|
197
|
+
];
|
|
198
|
+
const photoOnly = [
|
|
199
|
+
['auto_add_music', 'tiktokAutoAddMusic', 'flag'],
|
|
200
|
+
['photo_cover_index', 'tiktokPhotoCoverIndex', 'flag'],
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
for (const [field, key, kind] of shared.concat(isVideo ? videoOnly : photoOnly)) {
|
|
204
|
+
const value = options[key];
|
|
205
|
+
if (kind === 'flag' ? value === undefined : !value) continue;
|
|
206
|
+
form.append(field, String(value));
|
|
174
207
|
}
|
|
175
208
|
}
|
|
176
209
|
|
|
@@ -401,7 +434,7 @@ export class UploadPost {
|
|
|
401
434
|
* @param {boolean} [options.asyncUpload=true] - Process upload asynchronously
|
|
402
435
|
*
|
|
403
436
|
* TikTok options:
|
|
404
|
-
* @param {string} [options.tiktokPrivacyLevel] - PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY
|
|
437
|
+
* @param {string} [options.tiktokPrivacyLevel] - PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY. TikTok decides per account which of these are available (a private account has no PUBLIC_TO_EVERYONE); asking for another one fails with error_code tiktok_privacy_unavailable listing the allowed ones. Omit it to keep the account's own default.
|
|
405
438
|
* @param {boolean} [options.tiktokDisableDuet] - Disable duet
|
|
406
439
|
* @param {boolean} [options.tiktokDisableComment] - Disable comments
|
|
407
440
|
* @param {boolean} [options.tiktokDisableStitch] - Disable stitch
|
|
@@ -410,7 +443,24 @@ export class UploadPost {
|
|
|
410
443
|
* @param {string} [options.tiktokPostMode] - DIRECT_POST or MEDIA_UPLOAD
|
|
411
444
|
* @param {boolean} [options.brandContentToggle] - Branded content toggle
|
|
412
445
|
* @param {boolean} [options.brandOrganicToggle] - Brand organic toggle
|
|
413
|
-
*
|
|
446
|
+
*
|
|
447
|
+
* TikTok music, location, cover and draft options. Available on connections that
|
|
448
|
+
* declare the matching `capabilities` (music, location, cover_image, draft) on the
|
|
449
|
+
* TikTok account returned by listUsers() / GET /api/uploadposts/users. If your
|
|
450
|
+
* connection does not have it, the field is ignored, the post still publishes, and
|
|
451
|
+
* the response includes a per-field `warnings` entry — reconnect the TikTok account
|
|
452
|
+
* to enable it.
|
|
453
|
+
* @param {string} [options.tiktokMusicId] - Commercial Music Library track id (see getTiktokTrendingMusic)
|
|
454
|
+
* @param {number} [options.tiktokMusicVolume] - Music volume 0-100 (defaults to 50 when music is set)
|
|
455
|
+
* @param {number} [options.tiktokMusicStart] - Music start offset in ms
|
|
456
|
+
* @param {number} [options.tiktokMusicEnd] - Music end offset in ms
|
|
457
|
+
* @param {number} [options.tiktokOriginalSoundVolume] - Original video audio volume 0-100 (defaults to 50 when music is set, so the original audio is not muted)
|
|
458
|
+
* @param {string} [options.tiktokLocationId] - Location id (see getTiktokLocations)
|
|
459
|
+
* @param {string} [options.tiktokLocationName] - Location name, required whenever tiktokLocationId is set
|
|
460
|
+
* @param {string} [options.tiktokCoverImageUrl] - Custom cover image URL
|
|
461
|
+
* @param {boolean} [options.tiktokIsAiGenerated] - AI-generated content disclosure
|
|
462
|
+
* @param {boolean} [options.tiktokUploadToDraft] - Publish to drafts. When true TikTok ignores the rest of the post settings
|
|
463
|
+
*
|
|
414
464
|
* Instagram options:
|
|
415
465
|
* @param {string} [options.instagramMediaType] - REELS or STORIES
|
|
416
466
|
* @param {boolean} [options.instagramShareToFeed] - Share to feed
|
|
@@ -521,10 +571,22 @@ export class UploadPost {
|
|
|
521
571
|
* TikTok options:
|
|
522
572
|
* @param {boolean} [options.tiktokAutoAddMusic] - Auto add music
|
|
523
573
|
* @param {boolean} [options.tiktokDisableComment] - Disable comments
|
|
524
|
-
* @param {number} [options.tiktokPhotoCoverIndex] - Index of photo for cover (0-based)
|
|
574
|
+
* @param {number} [options.tiktokPhotoCoverIndex] - Index of photo for cover (0-based). Sent as `photo_cover_index`
|
|
575
|
+
* @param {string} [options.tiktokPrivacyLevel] - PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY. TikTok requires one on photo posts, so it defaults to PUBLIC_TO_EVERYONE; asking for one the account does not have fails with error_code tiktok_privacy_unavailable listing the allowed ones.
|
|
525
576
|
* @param {boolean} [options.brandContentToggle] - Branded content toggle
|
|
526
577
|
* @param {boolean} [options.brandOrganicToggle] - Brand organic toggle
|
|
527
|
-
*
|
|
578
|
+
*
|
|
579
|
+
* TikTok music, location and AI disclosure on photo posts. TikTok's photo contract
|
|
580
|
+
* takes the track id alone — no volume/trim, no custom cover, no draft (those are
|
|
581
|
+
* video-only). Available on connections that declare the matching `capabilities`
|
|
582
|
+
* (music, location) on the TikTok account returned by listUsers(); otherwise the
|
|
583
|
+
* field is ignored, the post still publishes, and the response includes a
|
|
584
|
+
* per-field `warnings` entry.
|
|
585
|
+
* @param {string} [options.tiktokMusicId] - Commercial Music Library track id (see getTiktokTrendingMusic)
|
|
586
|
+
* @param {string} [options.tiktokLocationId] - Location id (see getTiktokLocations)
|
|
587
|
+
* @param {string} [options.tiktokLocationName] - Location name, required whenever tiktokLocationId is set
|
|
588
|
+
* @param {boolean} [options.tiktokIsAiGenerated] - AI-generated content disclosure
|
|
589
|
+
*
|
|
528
590
|
* Instagram options:
|
|
529
591
|
* @param {string} [options.instagramMediaType] - IMAGE or STORIES
|
|
530
592
|
* @param {string} [options.instagramCollaborators] - Comma-separated collaborator usernames
|
|
@@ -797,6 +859,13 @@ export class UploadPost {
|
|
|
797
859
|
/**
|
|
798
860
|
* Get analytics for a specific post across all platforms it was published to
|
|
799
861
|
*
|
|
862
|
+
* `post_metrics` carries whatever the platform reports, so it is not the same
|
|
863
|
+
* shape everywhere: on TikTok it also brings `retention` (the curve, second
|
|
864
|
+
* by second), `impression_sources` (For You, search, profile...),
|
|
865
|
+
* `audience_types` (followers vs non-followers), `new_followers`, `reach` and
|
|
866
|
+
* the watch times (`average_time_watched`, `total_time_watched`,
|
|
867
|
+
* `full_video_watched_rate`).
|
|
868
|
+
*
|
|
800
869
|
* @param {string} requestId - The request_id from the upload
|
|
801
870
|
* @returns {Promise<Object>} Post analytics with per-platform metrics
|
|
802
871
|
*/
|
|
@@ -847,6 +916,73 @@ export class UploadPost {
|
|
|
847
916
|
return this._request('/uploadposts/post-analytics/cached', 'GET', params);
|
|
848
917
|
}
|
|
849
918
|
|
|
919
|
+
/**
|
|
920
|
+
* Get who the audience is: where they are, how old they are, when they are
|
|
921
|
+
* online and what they tap on the profile.
|
|
922
|
+
*
|
|
923
|
+
* Where the post-analytics methods answer *how did my posts do*, this one
|
|
924
|
+
* answers *who is following me*. It is one endpoint for every platform,
|
|
925
|
+
* chosen with `platform` — a platform that cannot answer it comes back with
|
|
926
|
+
* `platform_not_supported` and the list of the ones that can.
|
|
927
|
+
*
|
|
928
|
+
* The window is clamped server-side: at most 60 days, and `endDate` always
|
|
929
|
+
* before today. A wider window is trimmed rather than rejected.
|
|
930
|
+
*
|
|
931
|
+
* Pass `benchmarkCategory` to also get the averages of that category next to
|
|
932
|
+
* the account's own numbers; the accepted values come back in
|
|
933
|
+
* `benchmark_categories` on every response, so a picker needs no extra call.
|
|
934
|
+
*
|
|
935
|
+
* @param {Object} options - Query options
|
|
936
|
+
* @param {string} options.user - Profile username
|
|
937
|
+
* @param {string} options.platform - Platform to ask (tiktok)
|
|
938
|
+
* @param {string} [options.startDate] - Window start, ISO `YYYY-MM-DD`
|
|
939
|
+
* @param {string} [options.endDate] - Window end, ISO `YYYY-MM-DD`
|
|
940
|
+
* @param {string} [options.benchmarkCategory] - Category to compare the account
|
|
941
|
+
* against, e.g. `SOFTWARE_AND_APPS`
|
|
942
|
+
* @returns {Promise<Object>} `range`, `audience` (countries, cities, ages, genders),
|
|
943
|
+
* `activity_by_hour`, `followers_daily`, `profile_actions`, `bio_description`,
|
|
944
|
+
* `benchmark_categories` and, with a category, `benchmark`
|
|
945
|
+
*/
|
|
946
|
+
async getAudience(options = {}) {
|
|
947
|
+
const params = { user: options.user, platform: options.platform };
|
|
948
|
+
if (options.startDate) params.start_date = options.startDate;
|
|
949
|
+
if (options.endDate) params.end_date = options.endDate;
|
|
950
|
+
if (options.benchmarkCategory) params.benchmark_category = options.benchmarkCategory;
|
|
951
|
+
return this._request('/uploadposts/audience', 'GET', params);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Get what to write about: the hashtags or the searches a platform suggests
|
|
956
|
+
* around a keyword.
|
|
957
|
+
*
|
|
958
|
+
* One endpoint for both questions, told apart by `type`:
|
|
959
|
+
*
|
|
960
|
+
* type answers
|
|
961
|
+
* hashtags `{ hashtags: [{ name, view_count }] }`
|
|
962
|
+
* keywords `{ keywords: [...] }`
|
|
963
|
+
*
|
|
964
|
+
* Chosen with `platform`, like every other question in the API — a platform
|
|
965
|
+
* that cannot answer it comes back with `platform_not_supported` and the list
|
|
966
|
+
* of the ones that can.
|
|
967
|
+
*
|
|
968
|
+
* @param {Object} options - Query options
|
|
969
|
+
* @param {string} options.user - Profile username
|
|
970
|
+
* @param {string} options.platform - Platform to ask (tiktok)
|
|
971
|
+
* @param {('hashtags'|'keywords')} options.type - Which suggestions you want
|
|
972
|
+
* @param {string} [options.q] - Keyword to get suggestions around
|
|
973
|
+
* @param {string} [options.countryCode] - ISO country code to bias the suggestions
|
|
974
|
+
* @param {string} [options.language] - Language code to bias the suggestions
|
|
975
|
+
* @returns {Promise<Object>} `{ success, platform, type, hashtags }` or
|
|
976
|
+
* `{ success, platform, type, keywords }`
|
|
977
|
+
*/
|
|
978
|
+
async getSuggestions(options = {}) {
|
|
979
|
+
const params = { user: options.user, platform: options.platform, type: options.type };
|
|
980
|
+
if (options.q) params.q = options.q;
|
|
981
|
+
if (options.countryCode) params.country_code = options.countryCode;
|
|
982
|
+
if (options.language) params.language = options.language;
|
|
983
|
+
return this._request('/uploadposts/suggestions', 'GET', params);
|
|
984
|
+
}
|
|
985
|
+
|
|
850
986
|
/**
|
|
851
987
|
* Get available metrics configuration for all supported platforms
|
|
852
988
|
*
|
|
@@ -1056,16 +1192,28 @@ export class UploadPost {
|
|
|
1056
1192
|
return this._request('/uploadposts/users/notifications/test', 'POST', {});
|
|
1057
1193
|
}
|
|
1058
1194
|
|
|
1059
|
-
// ====================
|
|
1195
|
+
// ==================== Comments ====================
|
|
1060
1196
|
|
|
1061
1197
|
/**
|
|
1062
|
-
* Get comments on a post
|
|
1198
|
+
* Get comments on a post, or the replies hanging from one of them
|
|
1199
|
+
*
|
|
1200
|
+
* Pass `commentId` to read the replies to that comment instead of the
|
|
1201
|
+
* post's top-level comments. It is the same question — *what was said
|
|
1202
|
+
* here* — so it is the same endpoint with one more parameter, not a
|
|
1203
|
+
* separate method per platform.
|
|
1204
|
+
*
|
|
1205
|
+
* On TikTok this needs the `comments` capability on the connection (see
|
|
1206
|
+
* `capabilities` on the TikTok account returned by listUsers()). TikTok grants
|
|
1207
|
+
* it at connect time, so an account connected earlier has to be reconnected.
|
|
1063
1208
|
*
|
|
1064
1209
|
* @param {Object} options - Query options
|
|
1065
1210
|
* @param {string} options.user - Profile username
|
|
1066
|
-
* @param {string} [options.platform='instagram'] - Platform (instagram, facebook, youtube, linkedin)
|
|
1067
|
-
* @param {string} [options.postId] - Native post/media ID (provide postId or postUrl)
|
|
1211
|
+
* @param {string} [options.platform='instagram'] - Platform (instagram, facebook, youtube, linkedin, tiktok)
|
|
1212
|
+
* @param {string} [options.postId] - Native post/media ID (provide postId or postUrl).
|
|
1213
|
+
* Required on TikTok, which has no post URL lookup.
|
|
1068
1214
|
* @param {string} [options.postUrl] - Full post URL (provide postId or postUrl)
|
|
1215
|
+
* @param {string} [options.commentId] - Read the replies to this comment instead
|
|
1216
|
+
* of the post's top-level comments
|
|
1069
1217
|
* @param {number} [options.limit] - Max number of comments to return
|
|
1070
1218
|
* @param {string} [options.after] - Pagination cursor for the next page
|
|
1071
1219
|
* @returns {Promise<Object>} Comments data
|
|
@@ -1078,6 +1226,7 @@ export class UploadPost {
|
|
|
1078
1226
|
const params = { platform: options.platform || 'instagram', user: options.user };
|
|
1079
1227
|
if (options.postId) params.post_id = options.postId;
|
|
1080
1228
|
if (options.postUrl) params.post_url = options.postUrl;
|
|
1229
|
+
if (options.commentId) params.comment_id = options.commentId;
|
|
1081
1230
|
if (options.limit !== undefined) params.limit = options.limit;
|
|
1082
1231
|
if (options.after) params.after = options.after;
|
|
1083
1232
|
return this._request('/uploadposts/comments', 'GET', params);
|
|
@@ -1129,9 +1278,13 @@ export class UploadPost {
|
|
|
1129
1278
|
* One of postId, postUrl, or commentId is required. Pass commentId to reply
|
|
1130
1279
|
* to an existing comment; pass postId/postUrl to comment on the post itself.
|
|
1131
1280
|
*
|
|
1281
|
+
* On TikTok this needs the `comments` capability on the connection (see
|
|
1282
|
+
* `capabilities` on the TikTok account returned by listUsers()). TikTok grants
|
|
1283
|
+
* it at connect time, so an account connected earlier has to be reconnected.
|
|
1284
|
+
*
|
|
1132
1285
|
* @param {Object} options - Comment options
|
|
1133
1286
|
* @param {string} options.user - Profile username
|
|
1134
|
-
* @param {string} options.platform - Platform (instagram, facebook, youtube, linkedin)
|
|
1287
|
+
* @param {string} options.platform - Platform (instagram, facebook, youtube, linkedin, tiktok)
|
|
1135
1288
|
* @param {string} options.message - Comment text
|
|
1136
1289
|
* @param {string} [options.postId] - Native post/media ID to comment on
|
|
1137
1290
|
* @param {string} [options.postUrl] - Full post URL to comment on
|
|
@@ -1153,9 +1306,13 @@ export class UploadPost {
|
|
|
1153
1306
|
/**
|
|
1154
1307
|
* Delete a comment on a post
|
|
1155
1308
|
*
|
|
1309
|
+
* On TikTok this needs the `comments` capability on the connection (see
|
|
1310
|
+
* `capabilities` on the TikTok account returned by listUsers()). TikTok grants
|
|
1311
|
+
* it at connect time, so an account connected earlier has to be reconnected.
|
|
1312
|
+
*
|
|
1156
1313
|
* @param {Object} options - Delete options
|
|
1157
1314
|
* @param {string} options.user - Profile username
|
|
1158
|
-
* @param {string} options.platform - Platform (instagram, facebook, youtube, linkedin)
|
|
1315
|
+
* @param {string} options.platform - Platform (instagram, facebook, youtube, linkedin, tiktok)
|
|
1159
1316
|
* @param {string} options.commentId - Comment ID to delete
|
|
1160
1317
|
* @param {string} [options.postId] - Post identifier (the post URN for LinkedIn)
|
|
1161
1318
|
* @returns {Promise<Object>} Deletion result
|
|
@@ -1170,6 +1327,43 @@ export class UploadPost {
|
|
|
1170
1327
|
return this._request('/uploadposts/comments/delete', 'DELETE', body);
|
|
1171
1328
|
}
|
|
1172
1329
|
|
|
1330
|
+
/**
|
|
1331
|
+
* Moderate a comment: hide, like or pin it — and undo any of the three
|
|
1332
|
+
*
|
|
1333
|
+
* One method for every platform, because it is one question: *do this to
|
|
1334
|
+
* that comment*. Each action carries its own inverse:
|
|
1335
|
+
*
|
|
1336
|
+
* action post_id
|
|
1337
|
+
* hide / unhide required
|
|
1338
|
+
* like / unlike not sent (the platform likes the comment on its own)
|
|
1339
|
+
* pin / unpin required
|
|
1340
|
+
*
|
|
1341
|
+
* On TikTok this needs the `comments` capability on the connection (see
|
|
1342
|
+
* `capabilities` on the TikTok account returned by listUsers()). TikTok grants
|
|
1343
|
+
* it at connect time, so an account connected earlier has to be reconnected.
|
|
1344
|
+
*
|
|
1345
|
+
* @param {Object} options - Action options
|
|
1346
|
+
* @param {string} options.user - Profile username
|
|
1347
|
+
* @param {string} options.platform - Platform (tiktok)
|
|
1348
|
+
* @param {string} options.commentId - Comment to act on
|
|
1349
|
+
* @param {('hide'|'unhide'|'like'|'unlike'|'pin'|'unpin')} options.action - What to do
|
|
1350
|
+
* @param {string} [options.postId] - Native post ID. Required for hide/unhide and
|
|
1351
|
+
* pin/unpin; never sent for like/unlike.
|
|
1352
|
+
* @returns {Promise<Object>} `{ success, platform, action, comment_id, result }`
|
|
1353
|
+
*/
|
|
1354
|
+
async commentAction(options) {
|
|
1355
|
+
const body = {
|
|
1356
|
+
platform: options.platform,
|
|
1357
|
+
user: options.user,
|
|
1358
|
+
comment_id: options.commentId,
|
|
1359
|
+
action: options.action
|
|
1360
|
+
};
|
|
1361
|
+
// Liking takes the comment alone; hiding and pinning need the post too.
|
|
1362
|
+
const likes = options.action === 'like' || options.action === 'unlike';
|
|
1363
|
+
if (!likes && options.postId) body.post_id = options.postId;
|
|
1364
|
+
return this._request('/uploadposts/comments/action', 'POST', body);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1173
1367
|
// ==================== Post Management ====================
|
|
1174
1368
|
|
|
1175
1369
|
/**
|
|
@@ -1242,6 +1436,102 @@ export class UploadPost {
|
|
|
1242
1436
|
return this._request('/uploadposts/pinterest/boards', 'GET', params);
|
|
1243
1437
|
}
|
|
1244
1438
|
|
|
1439
|
+
/**
|
|
1440
|
+
* Catalogue filters shared by the trending and search endpoints, so both
|
|
1441
|
+
* address the same cached chart slice.
|
|
1442
|
+
* @private
|
|
1443
|
+
*/
|
|
1444
|
+
_tiktokMusicParams(profile, options = {}) {
|
|
1445
|
+
const params = { profile };
|
|
1446
|
+
if (options.genre) params.genre = options.genre;
|
|
1447
|
+
if (options.countryCode) params.country_code = options.countryCode;
|
|
1448
|
+
if (options.dateRange) params.date_range = options.dateRange;
|
|
1449
|
+
return params;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
/**
|
|
1453
|
+
* Get trending tracks from the TikTok Commercial Music Library.
|
|
1454
|
+
*
|
|
1455
|
+
* Available on connections that declare the `music` capability (see
|
|
1456
|
+
* `capabilities` on the TikTok account returned by listUsers() /
|
|
1457
|
+
* GET /api/uploadposts/users). The returned `id` is what you pass as
|
|
1458
|
+
* `tiktokMusicId` on an upload — not `commercial_music_id`, which TikTok
|
|
1459
|
+
* rejects on public posts.
|
|
1460
|
+
*
|
|
1461
|
+
* @param {string} profile - Profile username
|
|
1462
|
+
* @param {Object} [options] - Query options
|
|
1463
|
+
* @param {string} [options.genre] - Genre filter (e.g. ALL, POP). Defaults to ALL upstream.
|
|
1464
|
+
* @param {string} [options.countryCode] - ISO country code. Defaults to US upstream.
|
|
1465
|
+
* @param {('1DAY'|'7DAY'|'30DAY'|'90DAY')} [options.dateRange] - Trending window. Defaults to 7DAY upstream.
|
|
1466
|
+
* @returns {Promise<Object>} Trending tracks
|
|
1467
|
+
*/
|
|
1468
|
+
async getTiktokTrendingMusic(profile, options = {}) {
|
|
1469
|
+
return this._request('/uploadposts/tiktok/music/trending', 'GET',
|
|
1470
|
+
this._tiktokMusicParams(profile, options));
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
/**
|
|
1474
|
+
* Search the TikTok Commercial Music Library by song title or artist.
|
|
1475
|
+
*
|
|
1476
|
+
* TikTok itself has no music search endpoint — its only catalogue read is the
|
|
1477
|
+
* trending chart for a genre/country/period. Upload-Post caches those charts
|
|
1478
|
+
* and matches your text against them, so this searches the trending charts,
|
|
1479
|
+
* not TikTok's whole catalogue. Matching is case- and accent-insensitive and
|
|
1480
|
+
* every word must match.
|
|
1481
|
+
*
|
|
1482
|
+
* Returns the same track objects as getTiktokTrendingMusic(), so the `id` is
|
|
1483
|
+
* again what you pass as `tiktokMusicId` on an upload.
|
|
1484
|
+
*
|
|
1485
|
+
* @param {string} profile - Profile username
|
|
1486
|
+
* @param {Object} [options] - Query options
|
|
1487
|
+
* @param {string} [options.q] - Text matched against titles and artists (max 80 chars). Omit for the chart in trending order.
|
|
1488
|
+
* @param {string} [options.genre] - Genre filter (e.g. ALL, POP). Defaults to ALL upstream.
|
|
1489
|
+
* @param {string} [options.countryCode] - ISO country code choosing which chart is searched. Defaults to US upstream.
|
|
1490
|
+
* @param {('1DAY'|'7DAY'|'30DAY'|'90DAY')} [options.dateRange] - Chart window. Defaults to 7DAY upstream.
|
|
1491
|
+
* @param {number} [options.limit] - Maximum tracks to return (capped at 100 upstream).
|
|
1492
|
+
* @returns {Promise<Object>} Matching tracks plus a `catalog` block describing the corpus searched
|
|
1493
|
+
*/
|
|
1494
|
+
async searchTiktokMusic(profile, options = {}) {
|
|
1495
|
+
const params = this._tiktokMusicParams(profile, options);
|
|
1496
|
+
if (options.q) params.q = options.q;
|
|
1497
|
+
if (options.limit !== undefined && options.limit !== null) params.limit = options.limit;
|
|
1498
|
+
return this._request('/uploadposts/tiktok/music/search', 'GET', params);
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
/**
|
|
1502
|
+
* Search TikTok locations (places) to tag on a post.
|
|
1503
|
+
*
|
|
1504
|
+
* Available on connections that declare the `location` capability (see
|
|
1505
|
+
* `capabilities` on the TikTok account returned by listUsers() /
|
|
1506
|
+
* GET /api/uploadposts/users). Pass the returned `location_id` as
|
|
1507
|
+
* `tiktokLocationId` and `location_name` as `tiktokLocationName` on an upload;
|
|
1508
|
+
* TikTok requires both together.
|
|
1509
|
+
*
|
|
1510
|
+
* @param {string} profile - Profile username
|
|
1511
|
+
* @param {string} query - Search query (max 100 characters)
|
|
1512
|
+
* @returns {Promise<Object>} Matching locations (up to 20)
|
|
1513
|
+
*/
|
|
1514
|
+
async getTiktokLocations(profile, query) {
|
|
1515
|
+
return this._request('/uploadposts/tiktok/locations', 'GET', { profile, q: query });
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* Get what the connected TikTok account is allowed to publish.
|
|
1520
|
+
*
|
|
1521
|
+
* The point of this call is `privacy_level_options`: TikTok narrows the four
|
|
1522
|
+
* privacy values per account (a private account has no `PUBLIC_TO_EVERYONE`),
|
|
1523
|
+
* and sending one the account does not have fails the upload with
|
|
1524
|
+
* `error_code: "tiktok_privacy_unavailable"`. Ask here to offer only the
|
|
1525
|
+
* values that will work, instead of the full enum.
|
|
1526
|
+
*
|
|
1527
|
+
* @param {string} profile - Profile username
|
|
1528
|
+
* @returns {Promise<Object>} privacy_level_options, max_video_post_duration_sec
|
|
1529
|
+
* and the account's comment/duet/stitch switches
|
|
1530
|
+
*/
|
|
1531
|
+
async getTiktokPublishingSettings(profile) {
|
|
1532
|
+
return this._request('/uploadposts/tiktok/settings', 'GET', { profile });
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1245
1535
|
/**
|
|
1246
1536
|
* Get Google Business Profile locations for a connected account
|
|
1247
1537
|
*
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "upload-post",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.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",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"test": "
|
|
9
|
+
"test": "node --test test/"
|
|
10
10
|
},
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"keywords": [
|