Skip to main content

Quick reference

FieldValue
Character limit2,200 (caption)
Photo title90 chars, photo posts only
Photos per post1–35 (slideshow)
Videos per post1
Video formatsMP4, MOV, WebM
Photo formatsJPEG, PNG, WebP
Max video size500 MB
Max photo size20 MB per image
Video duration3–600 seconds
Photo aspect ratio9:16 recommended (auto-cropped otherwise)
Post typesVideo, Photo Carousel
Scheduling✅ Schedule + post now
First comment❌ Not exposed by TikTok’s API
Branded content✅ Disclosure toggles

Before you start

TikTok has a strict review process before a TikTok App can publish on behalf of arbitrary creators. While in sandbox mode, every post is forced to privacy: "SELF_ONLY" regardless of what your request sends. The full set of privacy values unlocks after TikTok approves the app.If you’re seeing your scheduled posts appear “only to me” on TikTok, your Postbreeze server’s TikTok credentials are still in sandbox.
Before scheduling, the connected account must:
  • Have completed the OAuth connect flow at least once.
  • Have granted video.publish, video.upload, and user.info.basic scopes.
  • Be on a region/account type TikTok permits API publishing for — Personal, Creator, and Business accounts all work; advertiser-only sub-accounts do not.
TikTok rejects posts whose privacy doesn’t match the per-creator allow-list returned by the creator-info API. Postbreeze validates this when you connect the account — if your account can’t post PUBLIC_TO_EVERYONE, the field’s enum will be filtered down for you. See also: Platform settings — TikTok and Media uploads.

Quick start

Workspace is inferred from your API key — no workspaceId argument. Use the flat shape (content + platforms) for the common case; drop to the nested shape when you’d rather group every per-target field under targets[].
TikTok requires privacy on every post — there is no default. Pass one of PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, or SELF_ONLY on every TikTok target.
import Postbreeze from "@postbreeze/node";

const postbreeze = new Postbreeze({ apiKey: process.env.POSTBREEZE_API_KEY });

const post = await postbreeze.posts.create({
  content: "Behind the scenes from launch week ✨",
  scheduledFor: new Date(Date.now() + 60_000).toISOString(),
  mediaIds: ["med_video_…"],
  platforms: [
    {
      accountId: "soc_tiktok_…",
      platformOptions: {
        platform: "TIKTOK_PERSONAL",
        privacy: "PUBLIC_TO_EVERYONE",
        allowComments: true,
        allowDuet: false,
        allowStitch: false,
        autoAddMusic: false,
        brandOrganicToggle: false,
        brandContentToggle: false,
      },
    },
  ],
});

console.log("Scheduled:", post.id);

Content types

Video post

A single video. The publisher uploads the file to TikTok, polls publish/status/fetch until the platform reports PUBLISH_COMPLETE, then returns the TikTok video id on the PostTarget.externalPostId.
const post = await postbreeze.posts.create({
  content: "Quick tutorial #1",
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  mediaIds: ["med_video_…"],
  platforms: [{
    accountId: "soc_tiktok_…",
    platformOptions: {
      platform: "TIKTOK_PERSONAL",
      privacy: "PUBLIC_TO_EVERYONE",
      allowComments: true,
      allowDuet: true,
      allowStitch: true,
      autoAddMusic: false,
      brandOrganicToggle: false,
      brandContentToggle: false,
    },
  }],
});
Up to 35 images stitched into a TikTok slideshow. The first item in mediaIds becomes the cover image — reorder via the media tray in compose, or by sending the array in the order you want.
const post = await postbreeze.posts.create({
  content: "Trip recap — best moments from the weekend",
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  mediaIds: ["med_photo_1", "med_photo_2", "med_photo_3"],
  platforms: [{
    accountId: "soc_tiktok_…",
    platformOptions: {
      platform: "TIKTOK_PERSONAL",
      privacy: "PUBLIC_TO_EVERYONE",
      allowComments: true,
      allowDuet: false,
      allowStitch: false,
      autoAddMusic: true,
      brandOrganicToggle: false,
      brandContentToggle: false,
      photoTitle: "Trip recap",
    },
  }],
});

Branded content disclosure

Branded content (paid partnerships) requires the brandContentToggle: true flag. TikTok enforces that branded posts must be privacy: "PUBLIC_TO_EVERYONE" — sending any other privacy value alongside brandContentToggle: true is rejected by Postbreeze’s validator before the request reaches TikTok.
Node.js
await postbreeze.posts.create({
  content: "Loving the new gear from @brand — link in bio 🎒 #ad",
  scheduledFor: new Date(Date.now() + 60_000).toISOString(),
  mediaIds: ["med_video_…"],
  platforms: [{
    accountId: "soc_tiktok_…",
    platformOptions: {
      platform: "TIKTOK_PERSONAL",
      privacy: "PUBLIC_TO_EVERYONE", // required when brandContentToggle is true
      allowComments: true,
      allowDuet: true,
      allowStitch: true,
      autoAddMusic: false,
      brandOrganicToggle: false,
      brandContentToggle: true, // "Branded content" disclosure
    },
  }],
});

Media requirements

Images

PropertyRequirement
Max photos35 per carousel
FormatsJPEG, PNG, WebP
Max file size20 MB per image
Aspect ratio9:16 recommended
ResolutionAuto-resized to 1080 × 1920 px

Videos

PropertyRequirement
Videos per post1
FormatsMP4, MOV, WebM
Max file size500 MB
Min duration3 seconds
Max duration10 minutes
Aspect ratio9:16 (any other ratio is letter-boxed by TikTok)
Resolution1080 × 1920 recommended
CodecsH.264
Frame rate30 fps recommended
You can’t mix photos and videos in the same post. Use either all-photos (carousel) or one video. See Media uploads for the presign + upload flow that produces mediaIds.

Platform-specific fields

TikTok settings go in platformOptions on each entry of platforms[]. The platform discriminator is required on every TikTok target — use "TIKTOK_PERSONAL" for Personal/Creator accounts and "TIKTOK_BUSINESS" for Business accounts. Both branches share the same field shape.
FieldTypeRequiredDescription
platform"TIKTOK_PERSONAL" | "TIKTOK_BUSINESS"YesDiscriminator. Picks the schema branch.
privacyenumYesPUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, or SELF_ONLY. No default — must be set explicitly to honor TikTok’s Content Sharing Guidelines.
allowCommentsbooleanNo (default false)Enable or disable comments.
allowDuetbooleanNo (default false)Enable or disable Duets on this post.
allowStitchbooleanNo (default false)Enable or disable Stitches on this post.
autoAddMusicbooleanNo (default false)Photo posts only. TikTok auto-adds a popular track when true.
brandOrganicTogglebooleanNo (default false)“Your Brand” disclosure — content promotes the creator’s own brand. Surfaced to TikTok as brand_organic_toggle.
brandContentTogglebooleanNo (default false)“Branded Content” disclosure — paid partnership. Surfaced as brand_content_toggle. Forces privacy: "PUBLIC_TO_EVERYONE" — any other value is rejected.
photoTitlestringNoPhoto posts only. Short metadata title (≤ 90 UTF-16 runes). Ignored for video posts.
firstComment is not accepted on TikTok targets — TikTok’s API has no comment-reply endpoint, so Postbreeze can’t auto-post a follow-up reply. The content field becomes the photo’s TikTok description. Limited to 2,200 characters and URLs are stripped if you include them — TikTok’s photo post API doesn’t honor inline URLs. Use the photoTitle field for the slideshow’s display title (90 chars max).

Media URL requirements

These do not work as media URLs:
  • Google Drive — returns an HTML download page, not the file
  • Dropbox — returns an HTML preview page
  • OneDrive / SharePoint — returns HTML
  • iCloud — returns HTML
Test your URL in an incognito browser window. If you see a webpage instead of the raw video or image, it will not work.
Media URLs must be:
  • Publicly accessible (no authentication required).
  • Returning actual media bytes with the correct Content-Type header.
  • Served from a domain TikTok permits — see TikTok’s verified-domain requirement below.
  • Hosted on a fast CDN.
Large videos are auto-rejected during upload (5–10 MB per chunk). Photos must resolve to 1080 × 1920.

Verified domain requirement

TikTok requires the host of video_url / image_url to be on your TikTok-developer-portal verified-domain list. Add your CDN origin (or Postbreeze’s R2_PUBLIC_URL) to that list before scheduling — uploads from unverified hosts return url_ownership_unverified and Temporal won’t retry.

Analytics

Available metrics via the Analytics API:
MetricAvailable
Views
Likes
Comments
Shares
Followers (account-level)
Watch time (seconds)
Avg view duration (seconds)
Profile visits❌ Not exposed by TikTok’s API
Reach❌ Not exposed by TikTok’s API
Refresh cadence: every 12 hours. TikTok requires platform-side data older than 7 days to be re-fetched on demand, so historical analytics older than a week may show stale values until the next refresh tick.

Common errors

ErrorMeaningFix
TT_PRIVACY_REQUIREDprivacy was not set on the platformOptionsPass one of the four enum values. There is no default — TikTok requires explicit consent.
TT_PRIVACY_NOT_ALLOWEDThe creator’s account can’t post to the requested privacy (e.g. their account is private but you sent PUBLIC_TO_EVERYONE)Pick a more restrictive value, or reconnect once the creator changes their default.
TT_BRANDED_PRIVATEbrandContentToggle: true combined with non-public privacyBranded content must be PUBLIC_TO_EVERYONE.
TT_DOMAIN_UNVERIFIEDThe media URL’s host isn’t on the verified-domain listAdd the CDN host to your TikTok developer portal.
TT_RATE_LIMITEDYou’ve hit TikTok’s per-creator publish rate limitWait — the publisher retries with backoff up to 15 minutes.
TT_MEDIA_TOO_LARGEVideo > 500 MB or photo > 20 MBCompress or transcode before uploading.
TT_MIXED_MEDIABoth photos and videos in mediaIdsTikTok publishes either a photo carousel or a single video; pick one.
TT_DURATION_OUT_OF_RANGEVideo < 3s or > 10 minTrim before uploading.

What you can’t do

These features are not exposed by TikTok’s Content Posting API today:
  • ❌ First comment (auto-post a follow-up reply)
  • ❌ Add hashtags as separate metadata
  • ❌ Add links (URLs in caption are stripped)
  • ❌ Edit a post after publish
  • ❌ Save as draft from API
  • ❌ Schedule via TikTok’s native scheduler (we use our own queue)
  • ❌ Post Stories or LIVE content
  • ❌ Apply effects, filters, or sounds programmatically

Full control (nested shape)

The flat shape above covers every TikTok use case. If you’d rather group every per-target field under targets[] — for example because you’re fanning the same post out to multiple platforms and want each target’s caption, media list, and options in one object — use the nested shape. Replace contentcaption, scheduledForscheduledAt, platformstargets, and accountIdsocialAccountId. Everything else is identical.
Node.js
const post = await postbreeze.posts.create({
  caption: "Behind the scenes from launch week ✨",
  scheduledAt: new Date(Date.now() + 60_000).toISOString(),
  mediaIds: ["med_video_…"],
  targets: [
    {
      socialAccountId: "soc_tiktok_…",
      platformOptions: {
        platform: "TIKTOK_PERSONAL",
        privacy: "PUBLIC_TO_EVERYONE",
        allowComments: true,
        allowDuet: false,
        allowStitch: false,
        autoAddMusic: false,
        brandOrganicToggle: false,
        brandContentToggle: false,
      },
    },
  ],
});