n8n-nodes-upload-post 0.1.55 → 0.2.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 CHANGED
@@ -77,6 +77,11 @@ The node provides the following operations grouped for clarity:
77
77
  - Parameters: Request ID, Platform (optional filter).
78
78
  - **Get Post Analytics by Platform ID**: Per-post metrics for a post published outside Upload-Post.
79
79
  - Parameters: Platform Post ID, Platform, Profile Username.
80
+ - **Get Cached Post Analytics**: Replays per-post metrics already fetched, instead of querying the platforms again, so it is not subject to the live analytics rate limit (100 requests / 5 minutes). 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.
81
+ - Parameters: Profile Username (required), Platform (optional filter), Limit (default 50, max 200), Cursor, Since / Until (`YYYY-MM-DD`, defaults to the last 30 days).
82
+ - Values are only as fresh as your last live read — check `captured_at` on each post. Use **Get Post Analytics** for a just-published post and this operation for bulk or historical reads.
83
+ - Paginate by passing the `next_cursor` from the previous response as `Cursor` while `has_more` is true.
84
+ - Supported: Facebook, Instagram, LinkedIn, Pinterest, Reddit, Threads, TikTok, YouTube. X (Twitter) is not cached.
80
85
  - **Get Total Impressions**: Impressions aggregated across connected platforms.
81
86
  - Parameters: Profile Username, Period or Start/End Date, Platforms, Breakdown.
82
87
  - **Get Platform Metrics**: Lists which metrics each platform exposes.
@@ -102,9 +107,12 @@ The node provides the following operations grouped for clarity:
102
107
  - **Delete User**: Delete an existing user profile.
103
108
  - Parameters: Username to delete.
104
109
  - **Generate JWT (for platform integration)**: Generate a connection URL (JWT) for a given profile so the user can link social accounts.
105
- - Parameters: User Identifier, optional redirect URL, logo image URL, redirect button text, platform restrictions.
110
+ - Parameters: User Identifier, optional redirect URL, logo image URL, redirect button text, platform restrictions, calendar options, connect title/description, language, UI labels.
111
+ - **Language**: forces the connection page language. Supported: English, Spanish, German, French, Portuguese, Polish, Turkish. Leave on Auto-Detect to use the visitor's browser language (falls back to English).
112
+ - **UI Labels**: white-label overrides for individual connection page texts, as key/value pairs (sent as `ui_labels`). The key is an i18n dot-path such as `connect.connectButton`, the value the replacement string. The API accepts up to 100 entries, keys matching `^[a-zA-Z0-9_.]+$` and values of up to 300 characters, and rejects the request otherwise.
106
113
  - **Validate JWT (for platform integration)**: Validate a connection token from your backend.
107
114
  - Parameters: JWT token (password field).
115
+ - The API response is returned as-is; its `profile` object also carries the `ui_labels` map configured for that profile.
108
116
 
109
117
  Refer to the [Upload Post API Documentation](https://docs.upload-post.com) for detailed information on parameters and platform requirements.
110
118
 
@@ -160,6 +168,45 @@ If you saw duplicate posts on an older version of this node, upgrade: versions b
160
168
  - **Video Options**: Privacy level (Public/Mutual Friends/Followers/Self), disable duet/stitch/comments, cover timestamp (ms), brand content toggles, AI-generated content flag, post mode (Direct Post/Media Upload).
161
169
  - **Brand Content**: Separate toggles for paid partnerships and own business promotion.
162
170
 
171
+ #### TikTok Advanced Video Options
172
+
173
+ > **Capability-gated.** `GET /api/uploadposts/users` returns a `capabilities`
174
+ > array on each TikTok account (`music`, `location`, `cover_image`,
175
+ > `cover_timestamp`, `draft`, `photo_privacy`, `video_privacy`,
176
+ > `inbox_fallback`, `profile_analytics`). If the connection does not declare the capability an option
177
+ > needs, the API ignores that field, the post still publishes and the response
178
+ > includes a per-field warning — reconnect the TikTok account to enable it.
179
+ > These options are only sent when actually filled in, so leaving them at their
180
+ > defaults keeps the previous behaviour.
181
+
182
+ - **Music** (`music`): TikTok Music ID — the `id` of a track returned by `GET /api/uploadposts/tiktok/music/trending` or `GET /api/uploadposts/tiktok/music/search` (not its `commercial_music_id`) — plus music volume (0-100) and music start/end offsets in ms. The Music ID works on **video and photo** posts; the volume and the offsets are video-only, because TikTok's photo contract takes the track id alone.
183
+
184
+ To pick a track by name inside a workflow, put an **HTTP Request** node before
185
+ this one calling
186
+ `GET https://api.upload-post.com/api/uploadposts/tiktok/music/search?profile=<profile>&q=<song or artist>`
187
+ with the header `Authorization: Apikey <your key>`, and feed `tracks[0].id`
188
+ into TikTok Music ID. It also accepts `genre`, `country_code`, `date_range`
189
+ and `limit`. Note that TikTok has **no music search endpoint**: the search
190
+ runs over the trending charts Upload-Post caches per genre/country/period, so
191
+ it finds trending tracks, not TikTok's entire catalogue.
192
+ - **Original Sound Volume**: Volume (0-100) of the video's own audio when music is added. Keep it above 0 so the original audio is not muted. Music volume and original sound volume are only sent when a Music ID is set.
193
+ - **Location** (`location`): TikTok Location ID plus TikTok Location Name — TikTok requires both together. Works on video and photo posts.
194
+ - **Cover Image URL** (`cover_image`): Custom cover image, takes priority over the cover timestamp. Video only.
195
+ - **Is AI Generated**: AI-generated content disclosure. Works on video and photo posts.
196
+ - **Upload to Draft** (`draft`): Sends the video to TikTok drafts instead of publishing; TikTok then ignores the rest of the post settings. Video only.
197
+
198
+ > **Privacy level on TikTok.** `privacy_level` is accepted on video and photo
199
+ > uploads alike (capabilities `video_privacy` / `photo_privacy`), but **TikTok
200
+ > decides per account which values are available**: a private account is offered
201
+ > Followers / Mutual Friends / Self Only and has no Public. Asking for one the
202
+ > account does not have fails with `tiktok_privacy_unavailable`, and the error
203
+ > lists the ones it does have. The **Account Default** option on the TikTok
204
+ > Privacy Level (Video) field sends nothing and lets TikTok apply the account's
205
+ > own setting — the only choice guaranteed to work on every account, and the
206
+ > default for new nodes. Photo posts default to Public. To pick a value up front,
207
+ > query `GET /api/uploadposts/tiktok/settings?profile=<profile>` from an HTTP
208
+ > Request node and read `privacy_level_options`.
209
+
163
210
  #### YouTube
164
211
  - **Video Metadata**: Tags (comma-separated), category ID, privacy status (public/unlisted/private), embeddable, license, public stats viewable.
165
212
  - **Compliance**: Made for kids declaration, self-declared made for kids (COPPA), contains synthetic media (AI transparency), has paid product placement (FTC).
@@ -394,6 +394,20 @@ const applyFacebookOptions = (ctx, operation, formData, isManualPlatform = false
394
394
  }
395
395
  }
396
396
  };
397
+ const applySharedTiktokFields = (ctx, formData) => {
398
+ const musicId = ctx.node.getNodeParameter('tiktokMusicId', ctx.itemIndex, '');
399
+ if (musicId)
400
+ formData.tiktok_music_id = musicId;
401
+ const locationId = ctx.node.getNodeParameter('tiktokLocationId', ctx.itemIndex, '');
402
+ const locationName = ctx.node.getNodeParameter('tiktokLocationName', ctx.itemIndex, '');
403
+ if (locationId && locationName) {
404
+ formData.tiktok_location_id = locationId;
405
+ formData.tiktok_location_name = locationName;
406
+ }
407
+ const isAiGenerated = ctx.node.getNodeParameter('tiktokIsAiGenerated', ctx.itemIndex, false);
408
+ if (isAiGenerated)
409
+ formData.tiktok_is_ai_generated = 'true';
410
+ };
397
411
  const applyTiktokOptions = (ctx, operation, formData) => {
398
412
  if (operation === 'uploadPhotos') {
399
413
  const autoAddMusic = ctx.node.getNodeParameter('tiktokAutoAddMusic', ctx.itemIndex, false);
@@ -410,6 +424,7 @@ const applyTiktokOptions = (ctx, operation, formData) => {
410
424
  if (photoDescription && formData.description === undefined) {
411
425
  formData.description = photoDescription;
412
426
  }
427
+ applySharedTiktokFields(ctx, formData);
413
428
  }
414
429
  else if (operation === 'uploadVideo') {
415
430
  const privacyLevel = ctx.node.getNodeParameter('tiktokPrivacyLevel', ctx.itemIndex, '');
@@ -432,6 +447,23 @@ const applyTiktokOptions = (ctx, operation, formData) => {
432
447
  formData.is_aigc = String(isAigc);
433
448
  if (postMode)
434
449
  formData.post_mode = postMode;
450
+ applySharedTiktokFields(ctx, formData);
451
+ if (formData.tiktok_music_id) {
452
+ formData.tiktok_music_volume = ctx.node.getNodeParameter('tiktokMusicVolume', ctx.itemIndex, 50);
453
+ formData.tiktok_original_sound_volume = ctx.node.getNodeParameter('tiktokOriginalSoundVolume', ctx.itemIndex, 50);
454
+ const musicStart = ctx.node.getNodeParameter('tiktokMusicStart', ctx.itemIndex, 0);
455
+ const musicEnd = ctx.node.getNodeParameter('tiktokMusicEnd', ctx.itemIndex, 0);
456
+ if (musicStart > 0)
457
+ formData.tiktok_music_start = musicStart;
458
+ if (musicEnd > 0)
459
+ formData.tiktok_music_end = musicEnd;
460
+ }
461
+ const coverImageUrl = ctx.node.getNodeParameter('tiktokCoverImageUrl', ctx.itemIndex, '');
462
+ if (coverImageUrl)
463
+ formData.tiktok_cover_image_url = coverImageUrl;
464
+ const uploadToDraft = ctx.node.getNodeParameter('tiktokUploadToDraft', ctx.itemIndex, false);
465
+ if (uploadToDraft)
466
+ formData.tiktok_upload_to_draft = 'true';
435
467
  }
436
468
  };
437
469
  const applyInstagramOptions = async (ctx, operation, formData) => {
@@ -694,6 +726,13 @@ const applyGoogleBusinessOptions = (ctx, _operation, formData) => {
694
726
  const mediaFormat = ctx.node.getNodeParameter('gbpMediaFormat', ctx.itemIndex, '');
695
727
  if (mediaFormat)
696
728
  formData.gbp_media_format = mediaFormat;
729
+ const postType = ctx.node.getNodeParameter('gbpPostType', ctx.itemIndex, '');
730
+ if (postType) {
731
+ formData.gbp_post_type = postType;
732
+ const mediaCategory = ctx.node.getNodeParameter('gbpMediaCategory', ctx.itemIndex, '');
733
+ if (mediaCategory)
734
+ formData.gbp_media_category = mediaCategory;
735
+ }
697
736
  const ctaType = ctx.node.getNodeParameter('gbpCtaType', ctx.itemIndex, '');
698
737
  if (ctaType)
699
738
  formData.gbp_cta_type = ctaType;
@@ -965,6 +1004,33 @@ const buildMonitoringRequest = (ctx) => {
965
1004
  waitForCompletion: false,
966
1005
  };
967
1006
  }
1007
+ case 'getCachedPostAnalytics': {
1008
+ const qs = {
1009
+ user: ctx.node.getNodeParameter('cachedAnalyticsProfileUsername', ctx.itemIndex),
1010
+ };
1011
+ const platform = ctx.node.getNodeParameter('cachedAnalyticsPlatform', ctx.itemIndex, '');
1012
+ if (platform)
1013
+ qs.platform = platform;
1014
+ const limit = ctx.node.getNodeParameter('cachedAnalyticsLimit', ctx.itemIndex, 50);
1015
+ if (limit)
1016
+ qs.limit = limit;
1017
+ const cursor = ctx.node.getNodeParameter('cachedAnalyticsCursor', ctx.itemIndex, '');
1018
+ if (cursor)
1019
+ qs.cursor = cursor;
1020
+ const since = ctx.node.getNodeParameter('cachedAnalyticsSince', ctx.itemIndex, '');
1021
+ if (since)
1022
+ qs.since = since;
1023
+ const until = ctx.node.getNodeParameter('cachedAnalyticsUntil', ctx.itemIndex, '');
1024
+ if (until)
1025
+ qs.until = until;
1026
+ return {
1027
+ endpoint: '/uploadposts/post-analytics/cached',
1028
+ method: 'GET',
1029
+ qs,
1030
+ isUploadOperation: false,
1031
+ waitForCompletion: false,
1032
+ };
1033
+ }
968
1034
  case 'getPlatformMetrics': {
969
1035
  return {
970
1036
  endpoint: '/uploadposts/platform-metrics',
@@ -1051,6 +1117,19 @@ const buildMonitoringRequest = (ctx) => {
1051
1117
  });
1052
1118
  }
1053
1119
  };
1120
+ const collectUiLabels = (ctx) => {
1121
+ var _a, _b;
1122
+ const raw = ctx.node.getNodeParameter('uiLabels', ctx.itemIndex, {});
1123
+ const entries = Array.isArray(raw.label) ? raw.label : [];
1124
+ const uiLabels = {};
1125
+ for (const entry of entries) {
1126
+ const key = String((_a = entry.key) !== null && _a !== void 0 ? _a : '').trim();
1127
+ if (!key)
1128
+ continue;
1129
+ uiLabels[key] = String((_b = entry.value) !== null && _b !== void 0 ? _b : '');
1130
+ }
1131
+ return uiLabels;
1132
+ };
1054
1133
  const buildUserRequest = async (ctx) => {
1055
1134
  switch (ctx.operation) {
1056
1135
  case 'listUsers':
@@ -1108,6 +1187,9 @@ const buildUserRequest = async (ctx) => {
1108
1187
  body.connect_description = connectDescription;
1109
1188
  if (language)
1110
1189
  body.language = language;
1190
+ const uiLabels = collectUiLabels(ctx);
1191
+ if (Object.keys(uiLabels).length > 0)
1192
+ body.ui_labels = uiLabels;
1111
1193
  return {
1112
1194
  endpoint: '/uploadposts/users/generate-jwt',
1113
1195
  method: 'POST',
@@ -1384,6 +1466,7 @@ class UploadPost {
1384
1466
  { name: 'Cancel Scheduled Post', value: 'cancelScheduled', action: 'Cancel scheduled post', description: 'Cancel a scheduled post by its job ID' },
1385
1467
  { name: 'Edit Scheduled Post', value: 'editScheduled', action: 'Edit scheduled post', description: 'Edit schedule details (like date/time) by job ID' },
1386
1468
  { name: 'Get Analytics', value: 'getAnalytics', action: 'Get analytics', description: 'Retrieve aggregated analytics for uploads' },
1469
+ { name: 'Get Cached Post Analytics', value: 'getCachedPostAnalytics', action: 'Get cached post analytics', description: 'Replay per-post metrics already fetched for a profile instead of querying the platforms again, so it is not subject to the live analytics rate limit (100 requests / 5 minutes). Only contains posts previously fetched through Get Post Analytics; there is no background refresh, so captured_at is the last time that post was read live. Use Get Post Analytics to refresh a post, and this operation for bulk re-reads.' },
1387
1470
  { name: 'Get Job Status', value: 'getJobStatus', action: 'Get job status', description: 'Check the status of a scheduled or queued post using the job_id' },
1388
1471
  { name: 'Get Platform Metrics', value: 'getPlatformMetrics', action: 'Get platform metrics', description: 'List the analytics metrics available for each platform' },
1389
1472
  { name: 'Get Post Analytics', value: 'getPostAnalytics', action: 'Get post analytics', description: 'Retrieve per-post analytics for an upload using its request_id' },
@@ -2156,6 +2239,69 @@ class UploadPost {
2156
2239
  description: 'Upload-Post profile that owns the connected account',
2157
2240
  displayOptions: { show: { operation: ['getPostAnalyticsByPlatformId'] } },
2158
2241
  },
2242
+ {
2243
+ displayName: 'Profile Username',
2244
+ name: 'cachedAnalyticsProfileUsername',
2245
+ type: 'string',
2246
+ required: true,
2247
+ default: '',
2248
+ description: 'Profile username whose cached post metrics you want',
2249
+ displayOptions: { show: { operation: ['getCachedPostAnalytics'] } },
2250
+ },
2251
+ {
2252
+ displayName: 'Platform',
2253
+ name: 'cachedAnalyticsPlatform',
2254
+ type: 'options',
2255
+ options: [
2256
+ { name: 'All Platforms', value: '' },
2257
+ { name: 'Facebook', value: 'facebook' },
2258
+ { name: 'Instagram', value: 'instagram' },
2259
+ { name: 'LinkedIn', value: 'linkedin' },
2260
+ { name: 'Pinterest', value: 'pinterest' },
2261
+ { name: 'Reddit', value: 'reddit' },
2262
+ { name: 'Threads', value: 'threads' },
2263
+ { name: 'TikTok', value: 'tiktok' },
2264
+ { name: 'YouTube', value: 'youtube' },
2265
+ ],
2266
+ default: '',
2267
+ description: 'Restrict the result to a single platform. X (Twitter) is not available: the snapshot cache does not store it.',
2268
+ displayOptions: { show: { operation: ['getCachedPostAnalytics'] } },
2269
+ },
2270
+ {
2271
+ displayName: 'Limit',
2272
+ name: 'cachedAnalyticsLimit',
2273
+ type: 'number',
2274
+ typeOptions: { minValue: 1, maxValue: 200 },
2275
+ default: 50,
2276
+ description: 'Max number of results to return',
2277
+ displayOptions: { show: { operation: ['getCachedPostAnalytics'] } },
2278
+ },
2279
+ {
2280
+ displayName: 'Cursor',
2281
+ name: 'cachedAnalyticsCursor',
2282
+ type: 'string',
2283
+ default: '',
2284
+ description: 'Opaque pagination cursor. Pass the next_cursor returned by the previous call to fetch the following page.',
2285
+ displayOptions: { show: { operation: ['getCachedPostAnalytics'] } },
2286
+ },
2287
+ {
2288
+ displayName: 'Since',
2289
+ name: 'cachedAnalyticsSince',
2290
+ type: 'string',
2291
+ default: '',
2292
+ placeholder: 'YYYY-MM-DD',
2293
+ description: 'Start of the snapshot date range. Defaults to 30 days ago.',
2294
+ displayOptions: { show: { operation: ['getCachedPostAnalytics'] } },
2295
+ },
2296
+ {
2297
+ displayName: 'Until',
2298
+ name: 'cachedAnalyticsUntil',
2299
+ type: 'string',
2300
+ default: '',
2301
+ placeholder: 'YYYY-MM-DD',
2302
+ description: 'End of the snapshot date range. Defaults to today.',
2303
+ displayOptions: { show: { operation: ['getCachedPostAnalytics'] } },
2304
+ },
2159
2305
  {
2160
2306
  displayName: 'Profile Username',
2161
2307
  name: 'impressionsProfileUsername',
@@ -2343,8 +2489,43 @@ class UploadPost {
2343
2489
  { name: 'English', value: 'en' },
2344
2490
  { name: 'French', value: 'fr' },
2345
2491
  { name: 'German', value: 'de' },
2492
+ { name: 'Polish', value: 'pl' },
2346
2493
  { name: 'Portuguese', value: 'pt' },
2347
2494
  { name: 'Spanish', value: 'es' },
2495
+ { name: 'Turkish', value: 'tr' },
2496
+ ],
2497
+ displayOptions: { show: { operation: ['generateJwt'] } },
2498
+ },
2499
+ {
2500
+ displayName: 'UI Labels',
2501
+ name: 'uiLabels',
2502
+ type: 'fixedCollection',
2503
+ typeOptions: { multipleValues: true },
2504
+ placeholder: 'Add UI Label',
2505
+ default: {},
2506
+ description: 'Override individual texts of the connection page, for white-label integrations. Each entry is an i18n dot-path key (for example connect.connectButton) plus the replacement string, and is sent as ui_labels. The API accepts at most 100 entries, keys matching ^[a-zA-Z0-9_.]+$ and values of up to 300 characters, and returns an error otherwise.',
2507
+ options: [
2508
+ {
2509
+ displayName: 'Label',
2510
+ name: 'label',
2511
+ values: [
2512
+ {
2513
+ displayName: 'Key',
2514
+ name: 'key',
2515
+ type: 'string',
2516
+ default: '',
2517
+ placeholder: 'connect.connectButton',
2518
+ description: 'I18n dot-path key of the connection page text to override',
2519
+ },
2520
+ {
2521
+ displayName: 'Value',
2522
+ name: 'value',
2523
+ type: 'string',
2524
+ default: '',
2525
+ description: 'Replacement text shown on the connection page',
2526
+ },
2527
+ ],
2528
+ },
2348
2529
  ],
2349
2530
  displayOptions: { show: { operation: ['generateJwt'] } },
2350
2531
  },
@@ -2693,13 +2874,14 @@ class UploadPost {
2693
2874
  name: 'tiktokPrivacyLevel',
2694
2875
  type: 'options',
2695
2876
  options: [
2696
- { name: 'Public to Everyone', value: 'PUBLIC_TO_EVERYONE' },
2697
- { name: 'Mutual Follow Friends', value: 'MUTUAL_FOLLOW_FRIENDS' },
2877
+ { name: 'Account Default', value: '' },
2698
2878
  { name: 'Follower of Creator', value: 'FOLLOWER_OF_CREATOR' },
2879
+ { name: 'Mutual Follow Friends', value: 'MUTUAL_FOLLOW_FRIENDS' },
2880
+ { name: 'Public to Everyone', value: 'PUBLIC_TO_EVERYONE' },
2699
2881
  { name: 'Self Only', value: 'SELF_ONLY' },
2700
2882
  ],
2701
- default: 'PUBLIC_TO_EVERYONE',
2702
- description: 'Privacy setting for TikTok video (PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, etc.). Only for Upload Video.',
2883
+ default: '',
2884
+ description: 'Privacy setting for the TikTok video. TikTok decides per account which levels are available: a private account has no Public to Everyone, and asking for one the account does not have fails with tiktok_privacy_unavailable listing the allowed ones. Account Default sends nothing and lets TikTok apply the account\'s own setting, which is the only value guaranteed to work on every account. Only for Upload Video.',
2703
2885
  displayOptions: {
2704
2886
  show: {
2705
2887
  operation: ['uploadVideo'],
@@ -2802,6 +2984,140 @@ class UploadPost {
2802
2984
  }
2803
2985
  },
2804
2986
  },
2987
+ {
2988
+ displayName: 'TikTok Music ID',
2989
+ name: 'tiktokMusicId',
2990
+ type: 'string',
2991
+ default: '',
2992
+ description: 'Commercial Music Library track ID to add to the post: the ID field returned by the TikTok trending-music or music-search endpoint, not its commercial_music_id. To find a track by song or artist, call GET /api/uploadposts/tiktok/music/search from an HTTP Request node first. Works on video and photo posts alike, but a photo post takes the ID alone: the volume and trim options below are video-only. Needs the music capability on the TikTok connection (see capabilities in the user-profiles response); without it the field is ignored with a warning and the post still publishes. Leave empty to skip.',
2993
+ displayOptions: {
2994
+ show: {
2995
+ operation: ['uploadVideo', 'uploadPhotos'],
2996
+ platform: ['tiktok', '__manual_platform__']
2997
+ }
2998
+ },
2999
+ },
3000
+ {
3001
+ displayName: 'TikTok Music Volume',
3002
+ name: 'tiktokMusicVolume',
3003
+ type: 'number',
3004
+ default: 50,
3005
+ typeOptions: { minValue: 0, maxValue: 100 },
3006
+ description: 'Volume (0-100) of the Commercial Music Library track. Only sent when a TikTok Music ID is set.',
3007
+ displayOptions: {
3008
+ show: {
3009
+ operation: ['uploadVideo'],
3010
+ platform: ['tiktok', '__manual_platform__']
3011
+ }
3012
+ },
3013
+ },
3014
+ {
3015
+ displayName: 'TikTok Music Start (Ms)',
3016
+ name: 'tiktokMusicStart',
3017
+ type: 'number',
3018
+ default: 0,
3019
+ typeOptions: { minValue: 0 },
3020
+ description: 'Start offset (ms) of the music track. Only sent when a TikTok Music ID is set and this is above 0.',
3021
+ displayOptions: {
3022
+ show: {
3023
+ operation: ['uploadVideo'],
3024
+ platform: ['tiktok', '__manual_platform__']
3025
+ }
3026
+ },
3027
+ },
3028
+ {
3029
+ displayName: 'TikTok Music End (Ms)',
3030
+ name: 'tiktokMusicEnd',
3031
+ type: 'number',
3032
+ default: 0,
3033
+ typeOptions: { minValue: 0 },
3034
+ description: 'End offset (ms) of the music track. Only sent when a TikTok Music ID is set and this is above 0.',
3035
+ displayOptions: {
3036
+ show: {
3037
+ operation: ['uploadVideo'],
3038
+ platform: ['tiktok', '__manual_platform__']
3039
+ }
3040
+ },
3041
+ },
3042
+ {
3043
+ displayName: 'TikTok Original Sound Volume',
3044
+ name: 'tiktokOriginalSoundVolume',
3045
+ type: 'number',
3046
+ default: 50,
3047
+ typeOptions: { minValue: 0, maxValue: 100 },
3048
+ description: 'Volume (0-100) of the original video audio when music is added. Only sent when a TikTok Music ID is set. Keep it above 0 so the original audio is not muted.',
3049
+ displayOptions: {
3050
+ show: {
3051
+ operation: ['uploadVideo'],
3052
+ platform: ['tiktok', '__manual_platform__']
3053
+ }
3054
+ },
3055
+ },
3056
+ {
3057
+ displayName: 'TikTok Location ID',
3058
+ name: 'tiktokLocationId',
3059
+ type: 'string',
3060
+ default: '',
3061
+ description: 'Location ID to tag on the post (video or photos). Needs the location capability on the TikTok connection (see capabilities in the user-profiles response) and a TikTok Location Name — the tag is only sent when both are filled in. Without the capability the field is ignored with a warning and the post still publishes. Leave empty to skip.',
3062
+ displayOptions: {
3063
+ show: {
3064
+ operation: ['uploadVideo', 'uploadPhotos'],
3065
+ platform: ['tiktok', '__manual_platform__']
3066
+ }
3067
+ },
3068
+ },
3069
+ {
3070
+ displayName: 'TikTok Location Name',
3071
+ name: 'tiktokLocationName',
3072
+ type: 'string',
3073
+ default: '',
3074
+ description: 'Location name matching the TikTok Location ID. TikTok requires both together, so the location tag is only sent when this and the ID are both filled in.',
3075
+ displayOptions: {
3076
+ show: {
3077
+ operation: ['uploadVideo', 'uploadPhotos'],
3078
+ platform: ['tiktok', '__manual_platform__']
3079
+ }
3080
+ },
3081
+ },
3082
+ {
3083
+ displayName: 'TikTok Cover Image URL',
3084
+ name: 'tiktokCoverImageUrl',
3085
+ type: 'string',
3086
+ default: '',
3087
+ description: 'Custom cover image URL for the video. Needs the cover_image capability on the TikTok connection (see capabilities in the user-profiles response); without it the field is ignored with a warning and the post still publishes. Takes priority over TikTok Cover Timestamp.',
3088
+ displayOptions: {
3089
+ show: {
3090
+ operation: ['uploadVideo'],
3091
+ platform: ['tiktok', '__manual_platform__']
3092
+ }
3093
+ },
3094
+ },
3095
+ {
3096
+ displayName: 'TikTok Is AI Generated',
3097
+ name: 'tiktokIsAiGenerated',
3098
+ type: 'boolean',
3099
+ default: false,
3100
+ description: 'Whether to disclose the post as AI-generated on TikTok. Works on video and photo posts. Only sent when enabled.',
3101
+ displayOptions: {
3102
+ show: {
3103
+ operation: ['uploadVideo', 'uploadPhotos'],
3104
+ platform: ['tiktok', '__manual_platform__']
3105
+ }
3106
+ },
3107
+ },
3108
+ {
3109
+ displayName: 'TikTok Upload to Draft',
3110
+ name: 'tiktokUploadToDraft',
3111
+ type: 'boolean',
3112
+ default: false,
3113
+ description: 'Whether to send the video to TikTok drafts instead of publishing it. Needs the draft capability on the TikTok connection (see capabilities in the user-profiles response); without it the field is ignored with a warning and the post still publishes. When enabled TikTok ignores the rest of the post settings.',
3114
+ displayOptions: {
3115
+ show: {
3116
+ operation: ['uploadVideo'],
3117
+ platform: ['tiktok', '__manual_platform__']
3118
+ }
3119
+ },
3120
+ },
2805
3121
  {
2806
3122
  displayName: 'Instagram Media Type',
2807
3123
  name: 'instagramMediaType',
@@ -2995,6 +3311,43 @@ class UploadPost {
2995
3311
  description: 'Format of the attached media',
2996
3312
  displayOptions: { show: { operation: ['uploadPhotos', 'uploadVideo', 'uploadText'], platform: ['google_business', '__manual_platform__'] } },
2997
3313
  },
3314
+ {
3315
+ displayName: 'Google Business Publish As',
3316
+ name: 'gbpPostType',
3317
+ type: 'options',
3318
+ options: [
3319
+ { name: 'Gallery', value: 'GALLERY' },
3320
+ { name: 'Local Post (Default)', value: '' },
3321
+ { name: 'Media', value: 'MEDIA' },
3322
+ { name: 'Photo', value: 'PHOTO' },
3323
+ ],
3324
+ default: '',
3325
+ description: 'Where the upload lands on the Google Business Profile. Local Post keeps the default behaviour and creates a post on the timeline. Media, Photo and Gallery all publish the image to the location Media/Gallery tab instead, without creating a Local Post.',
3326
+ displayOptions: { show: { operation: ['uploadPhotos', 'uploadVideo', 'uploadText'], platform: ['google_business', '__manual_platform__'] } },
3327
+ },
3328
+ {
3329
+ displayName: 'Google Business Media Category',
3330
+ name: 'gbpMediaCategory',
3331
+ type: 'options',
3332
+ options: [
3333
+ { name: 'Additional', value: 'ADDITIONAL' },
3334
+ { name: 'At Work', value: 'AT_WORK' },
3335
+ { name: 'Common Area', value: 'COMMON_AREA' },
3336
+ { name: 'Cover', value: 'COVER' },
3337
+ { name: 'Exterior', value: 'EXTERIOR' },
3338
+ { name: 'Food and Drink', value: 'FOOD_AND_DRINK' },
3339
+ { name: 'Interior', value: 'INTERIOR' },
3340
+ { name: 'Logo', value: 'LOGO' },
3341
+ { name: 'Menu', value: 'MENU' },
3342
+ { name: 'Product', value: 'PRODUCT' },
3343
+ { name: 'Profile', value: 'PROFILE' },
3344
+ { name: 'Rooms', value: 'ROOMS' },
3345
+ { name: 'Teams', value: 'TEAMS' },
3346
+ ],
3347
+ default: 'ADDITIONAL',
3348
+ description: 'Category the photo is filed under in the Media/Gallery tab. Only used when Publish As is Media, Photo or Gallery.',
3349
+ displayOptions: { show: { operation: ['uploadPhotos', 'uploadVideo', 'uploadText'], platform: ['google_business', '__manual_platform__'], gbpPostType: ['GALLERY', 'MEDIA', 'PHOTO'] } },
3350
+ },
2998
3351
  {
2999
3352
  displayName: 'Google Business Call to Action',
3000
3353
  name: 'gbpCtaType',