Posts

Publish TikTok videos and photo carousels with privacy controls, AI disclosure, and scheduling.

Publish to TikTok through the shared posts surface via Direct Post. Every TikTok destination must set privacyLevel and use media hosted on Onepostly's CDN.

Quick Start

Publish a video in under a minute:

import { Configuration, PostsApi } from "@onepostly/sdk";

const postsApi = new PostsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await postsApi.createPost({
  createPostBody: {
    "text": "Ship day, the video cut",
    "mediaUrls": [
      "https://cdn.onepostly.com/media/demo.mp4"
    ],
    "mediaKind": "video",
    "destinations": [
      {
        "accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
        "privacyLevel": "PUBLIC_TO_EVERYONE"
      }
    ]
  },
});
console.log(response);
{
  "post": {
    "id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
    "text": "Ship day, the video cut",
    "mediaUrls": [
      "https://cdn.onepostly.com/media/demo.mp4"
    ],
    "mediaKind": "video",
    "status": "queued",
    "scheduledFor": null,
    "timezone": null,
    "destinations": [
      {
        "id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
        "accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
        "platform": "tiktok",
        "status": "draft",
        "externalPostId": "17895695668004550",
        "externalUrl": "https://www.tiktok.com/@you/video/17895695668004550",
        "errorCode": null,
        "errorMessage": null,
        "publishedAt": "2026-08-25T10:00:00.000Z",
        "metrics": null
      }
    ],
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  }
}

The response is 202 Accepted with status: "queued". Poll GET /v1/posts/:id until the destination is published or failed, or receive webhooks instead of polling. When published, public videos carry the TikTok post_id. Private posts carry a publish_id. A destination can reach published before its post link exists; externalUrl is filled in once TikTok makes the post publicly viewable.

Content Types

TikTok destinations accept two content types. You cannot mix photos and videos in the same post.

mediaKindContentMedia countFormats
videoSingle videoExactly 1 URLMP4, MOV, WebM
multi-imagePhoto carousel2 to 35 URLsJPEG, WebP
imageSingle photo1 URLJPEG, WebP
text / storiesNoNoNot supported on TikTok

Video Post

9:16 vertical (1080 × 1920, H.264, 30 fps) works well on TikTok. Duets and stitches are enabled by default; pass true to disable either one:

import { Configuration, PostsApi } from "@onepostly/sdk";

const postsApi = new PostsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await postsApi.createPost({
  createPostBody: {
    "text": "New cooking tutorial #recipe #foodtok",
    "mediaUrls": [
      "https://cdn.onepostly.com/media/cooking.mp4"
    ],
    "mediaKind": "video",
    "destinations": [
      {
        "accountId": "YOUR_TIKTOK_ACCOUNT_ID",
        "privacyLevel": "PUBLIC_TO_EVERYONE"
      }
    ]
  },
});
console.log(response);
{
  "post": {
    "id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
    "text": "New cooking tutorial #recipe #foodtok",
    "mediaUrls": [
      "https://cdn.onepostly.com/media/cooking.mp4"
    ],
    "mediaKind": "video",
    "status": "queued",
    "scheduledFor": null,
    "timezone": null,
    "destinations": [
      {
        "id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
        "accountId": "YOUR_TIKTOK_ACCOUNT_ID",
        "platform": "tiktok",
        "status": "draft",
        "externalPostId": "17895695668004550",
        "externalUrl": "https://www.tiktok.com/@you/video/17895695668004550",
        "errorCode": null,
        "errorMessage": null,
        "publishedAt": "2026-08-25T10:00:00.000Z",
        "metrics": null
      }
    ],
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  }
}

Up to 35 images. text becomes the photo title (90 characters, auto-truncated). Use description for a full caption up to 4,000 characters. It falls back to text when omitted:

import { Configuration, PostsApi } from "@onepostly/sdk";

const postsApi = new PostsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await postsApi.createPost({
  createPostBody: {
    "text": "My travel highlights",
    "mediaUrls": [
      "https://cdn.onepostly.com/media/photo1.jpg",
      "https://cdn.onepostly.com/media/photo2.jpg",
      "https://cdn.onepostly.com/media/photo3.jpg"
    ],
    "mediaKind": "multi-image",
    "destinations": [
      {
        "accountId": "YOUR_TIKTOK_ACCOUNT_ID",
        "privacyLevel": "PUBLIC_TO_EVERYONE",
        "description": "Full trip recap from our weekend adventure across the coast. #travel #roadtrip"
      }
    ]
  },
});
console.log(response);
{
  "post": {
    "id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
    "text": "My travel highlights",
    "mediaUrls": [
      "https://cdn.onepostly.com/media/photo1.jpg",
      "https://cdn.onepostly.com/media/photo2.jpg",
      "https://cdn.onepostly.com/media/photo3.jpg"
    ],
    "mediaKind": "multi-image",
    "status": "queued",
    "scheduledFor": null,
    "timezone": null,
    "destinations": [
      {
        "id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
        "accountId": "YOUR_TIKTOK_ACCOUNT_ID",
        "platform": "tiktok",
        "status": "draft",
        "externalPostId": "17895695668004550",
        "externalUrl": "https://www.tiktok.com/@you/video/17895695668004550",
        "errorCode": null,
        "errorMessage": null,
        "publishedAt": "2026-08-25T10:00:00.000Z",
        "metrics": null
      }
    ],
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  }
}

Single Photo

mediaKind: "image" publishes one photo with the same title and description rules as a carousel:

import { Configuration, PostsApi } from "@onepostly/sdk";

const postsApi = new PostsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await postsApi.createPost({
  createPostBody: {
    "text": "Sunset from the studio",
    "mediaUrls": [
      "https://cdn.onepostly.com/media/sunset.jpg"
    ],
    "mediaKind": "image",
    "destinations": [
      {
        "accountId": "YOUR_TIKTOK_ACCOUNT_ID",
        "privacyLevel": "PUBLIC_TO_EVERYONE",
        "description": "Shot on our last studio day. #travel"
      }
    ]
  },
});
console.log(response);
{
  "post": {
    "id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
    "text": "Sunset from the studio",
    "mediaUrls": [
      "https://cdn.onepostly.com/media/sunset.jpg"
    ],
    "mediaKind": "image",
    "status": "queued",
    "scheduledFor": null,
    "timezone": null,
    "destinations": [
      {
        "id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
        "accountId": "YOUR_TIKTOK_ACCOUNT_ID",
        "platform": "tiktok",
        "status": "draft",
        "externalPostId": "17895695668004550",
        "externalUrl": "https://www.tiktok.com/@you/video/17895695668004550",
        "errorCode": null,
        "errorMessage": null,
        "publishedAt": "2026-08-25T10:00:00.000Z",
        "metrics": null
      }
    ],
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  }
}

Media Requirements

Videos

PropertyRequirement
Max videos1 per post
FormatsMP4, MOV, WebM
Max file size4 GB
Max durationAccount-specific, up to 10 minutes
Recommended9:16 vertical, 1080 × 1920, H.264, 30 fps

The exact duration a creator may upload is returned by creator info as maxVideoPostDurationSec.

Photos

PropertyRequirement
Max photos35 per carousel (1 for a single photo)
FormatsJPEG, WebP
Recommended1080 × 1920 (9:16)

Media Hosting

TikTok rejects media hosted anywhere else. Google Drive, Dropbox, OneDrive, iCloud, and your own domain all fail, because TikTok's PULL_FROM_URL only accepts hosts verified in the developer app. Onepostly's CDN is the verified host.

The flow is always the same:

  1. Upload the file through the Media API
  2. Pass the returned cdn.onepostly.com URL in mediaUrls
  3. TikTok fetches the asset directly at publish time

Destination Parameters

Every TikTok destination accepts the following parameters. All of them except privacyLevel are optional.

Prop

Type

Privacy Levels

Every TikTok destination must set privacyLevel:

ValueMeaning
PUBLIC_TO_EVERYONEEveryone
MUTUAL_FOLLOW_FRIENDSFriends only
FOLLOWER_OF_CREATORFollowers only
SELF_ONLYOnly the creator

Which values a creator may use depends on their account settings. A private account, for example, cannot post PUBLIC_TO_EVERYONE. Values outside the creator's allowed set are rejected with INVALID_PRIVACY_LEVEL.

Creator Info

Before posting, check which privacy levels and interactions the creator allows:

import { Configuration, ConnectionsApi } from "@onepostly/sdk";

const connectionsApi = new ConnectionsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await connectionsApi.getTikTokCreatorInfo({
  id: "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
});
console.log(response);
{
  "accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
  "platform": "tiktok",
  "creatorInfo": {
    "creatorAvatarUrl": "https://cdn.onepostly.com/avatars/onepostly.jpg",
    "creatorUsername": "onepostly",
    "creatorNickname": "Onepostly",
    "privacyLevelOptions": [
      "PUBLIC_TO_EVERYONE"
    ],
    "commentDisabled": false,
    "duetDisabled": false,
    "stitchDisabled": false,
    "maxVideoPostDurationSec": 600
  },
  "fetchedAt": "2026-08-25T10:00:00.000Z"
}

Prop

Type

Schedule

Add scheduledFor (local wall time) and timezone to the same call. The request shape is otherwise identical to an immediate post.

  • timezone defaults to UTC. Use an IANA name like Europe/Istanbul
  • Do not append Z or an offset to scheduledFor. It is local wall time
  • Scheduled posts return 201 Created and publish automatically at the scheduled moment
  • Max 1 year ahead
  • Cancel with posts.cancelPost({ id }) / posts.cancel_post(id=…), or DELETE /v1/posts/:id, any time before then

Direct Posting at Capacity

A TikTok post can fail with CAPACITY_LIMITED even though nothing is wrong with the account or the media. TikTok caps how many distinct accounts can direct-post through one developer app in a rolling 24-hour window (TikTok's own error for it is reached_active_user_cap). Every Onepostly customer publishes through the same TikTok app, so the budget is app-wide: it is not tied to your account, your API key, or how many posts you have made. An account that has already direct-posted inside the window keeps posting; the cap only blocks accounts that would need a new slot.

The window is rolling, so slots free up continuously as activity from 24 hours ago ages out. There is no midnight reset to wait for.

The post is marked failed and is not retried automatically. Retry it a few hours later, or reschedule it. The posting velocity limits (25 posts per hour, 50 per day for TikTok) apply on top, per account.

Publish Errors

CodeCauseFix
PRIVACY_LEVEL_REQUIREDDestination has no privacyLevelSet privacyLevel on every TikTok destination
INVALID_PRIVACY_LEVELValue outside the creator's allowed setFetch creator info and use one of privacyLevelOptions
INVALID_MEDIAUnsupported format, size over 4 GB, or a URL not on Onepostly's CDNUpload via the Media API and use its URLs
MEDIA_UPLOAD_FAILEDTikTok rejected the uploadCheck the error message for the TikTok reason
PUBLISH_FAILEDContent moderation flagged the post, or duplicate content detectedReview the content, modify the caption or media, then retry
SPAM_RISKTikTok flagged this post as potentially riskyReview content. TikTok's API moderation is stricter than the native app
POSTING_LIMIT_REACHEDThe account has posted too many times in a short windowWait for the window to pass, then post again
ACCOUNT_RESTRICTEDTikTok restricted the account from postingResolve it in the TikTok app before posting again
CAPACITY_LIMITEDTikTok's app-wide cap on distinct posting accounts per rolling 24hRetry a few hours later or reschedule. See Direct posting at capacity
TOKEN_INVALIDAccess token expired or revokedReconnect the account
PERMISSION_DENIEDConnection lacks the video.publish scopeReconnect the account and grant publish permission
UNSUPPORTED_MEDIA_KINDmediaKind: "text" or "stories"Use video, image, or multi-image
UNSUPPORTED_FEATUREThreads, quotes, or reply chains sent to TikTokThese are not TikTok concepts. Remove them
RATE_LIMITEDTikTok throttled the requestRetry later
PLATFORM_UNAVAILABLETikTok reported a server-side failureTry again later

Large videos take time

TikTok can take minutes to process large videos. The destination stays queued until the video is ready. If TikTok is still processing after several minutes the publish fails with MEDIA_PROCESSING_TIMEOUT.

See Also