# TikTok

Publish videos and photo carousels to TikTok with the Zernio API, with creator privacy levels, duet and stitch controls, custom covers, AI disclosure and Creator Inbox drafts.

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';

Publish videos and photo carousels to TikTok with `POST /v1/posts` and `platform: "tiktok"`. Settings go in a top-level `tiktokSettings` object.

The same account also serves post and account analytics, DMs and comments.

## Quick reference

| Property | Value |
|----------|-------|
| Character limit | 2,200 (video caption), 4,000 (photo `description`) |
| Photo title | 90 characters (auto-truncated, hashtags stripped) |
| Photos per post | 35 (carousel) |
| Videos per post | 1 |
| Photo formats | JPEG, PNG, WebP |
| Photo max size | 20 MB |
| Video formats | MP4, MOV, WebM |
| Video max size | 4 GB |
| Video duration | 3 seconds to 10 minutes |
| Post types | Video, Photo carousel |
| Scheduling | Yes |
| Video privacy | Public only on TikTok Business app connections; the creator's allowed levels on developer app connections |
| Inbox (comments) | Yes (accounts connected through TikTok's Business app) |
| Comment automations | Public reply only, comments arrive by webhook ([comment automations](/comment-automations/create-comment-automation)) |
| Inbox (DMs) | Replies only, on TikTok Business Accounts registered outside the EEA, Switzerland and the UK ([details](/platforms/tiktok/inbox#direct-messages)) |
| Analytics | Limited |

## Before you start

TikTok requires media on every post. There are no text-only posts.

Every post also needs `content_preview_confirmed: true` and `express_consent_given: true`, a legal requirement from TikTok.

`privacy_level` must be one of the values TikTok reports for that creator. Read [creator info](/platforms/tiktok/posts#creator-info) before you build the request.

On an account connected through TikTok's Business app, a video direct post is public and any other level is rejected ([connection lanes](#connection-lanes)).

Both Zernio and TikTok cap how many posts an account can create through the API per rolling 24 hours ([daily posting caps](/platforms/tiktok/posts#daily-posting-caps)).

Content moderation is stricter through the API than in the app.

<Callout type="warn">
A post from an account connected through TikTok's developer app can fail with "TikTok direct posting is at capacity right now". Nothing is wrong with the account or the media.
</Callout>

TikTok caps how many distinct developer app accounts can direct-post through the Zernio app per rolling 24 hours. The cap is shared by every Zernio customer on that lane.

Accounts connected through TikTok's Business app, which is every new connection, never hit it. See [Direct posting at capacity](/platforms/tiktok/posts#direct-posting-at-capacity).

## Connect

Call `GET /v1/connect/tiktok` with `profileId` on [Get OAuth connect URL](/connect/get-connect-url).

The [connecting accounts guide](/guides/connecting-accounts) covers the OAuth flow and [scopes](/guides/connecting-accounts#scopes) in general. [Account health](/accounts/get-all-accounts-health) reports what a connected account can do with the scopes the user granted.

### Connection lanes

TikTok connections now go through the TikTok for Business app.

An account connected earlier stays on TikTok's developer app until its owner reconnects it. The lane is decided when the account is connected.

Which lane an account is on changes three things:

- **Capacity**: developer app accounts share the app-wide [direct posting cap](/platforms/tiktok/posts#direct-posting-at-capacity), Business app accounts are exempt from it.
- **Video privacy**: Business app accounts publish videos as public only, and [Creator Inbox drafts](/platforms/tiktok/posts#draft-delivery) are the only route to a non-public video. Photo posts keep every level the creator allows on both lanes.
- **Inbox**: DMs and comment management need a Business app connection ([Inbox](/platforms/tiktok/inbox)).

Reconnecting an account is what moves it to the Business app:

- A profile can hold several TikTok accounts. Connecting a TikTok account the profile already has refreshes it instead of adding a second copy.
- Reconnecting the same account keeps it and its history.
- Authorizing a different account takes the slot and permanently deletes the previous account's analytics, inbox and DM history.
- Zernio tells “same vs different” by the last connected `@handle`; if the handle was renamed on TikTok since the last connect, it may be treated as a different account.

> **Note:** If the authorization leaves out a permission the already-connected account needs, nothing changes and the flow returns `missing_tiktok_permissions`. Connect again and accept every permission on TikTok’s consent screen.

### OAuth scopes

TikTok connects through TikTok's Accounts API, which refuses to reconnect an account without the video scopes.

So on TikTok, [`scopes`](/guides/connecting-accounts#scopes) trims only the analytics, comments and messaging scopes.

## Publish a post

This request publishes a video to TikTok right away. `tiktokSettings` must include `content_preview_confirmed` and `express_consent_given`.

<Tabs items={['Node.js', 'Python', 'curl']}>
<Tab value="Node.js">
```typescript
import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: published } = await zernio.posts.createPost({
  body: {
    content: 'New cooking tutorial #recipe #foodtok',
    mediaItems: [
      { type: 'video', url: 'https://cdn.example.com/cooking-tutorial.mp4' }
    ],
    platforms: [
      { platform: 'tiktok', accountId: '66b2e19d8c3f5a7e9d0b1c2d' }
    ],
    tiktokSettings: {
      privacy_level: 'PUBLIC_TO_EVERYONE',
      allow_comment: true,
      allow_duet: true,
      allow_stitch: true,
      content_preview_confirmed: true,
      express_consent_given: true
    },
    publishNow: true
  }
});

console.log(published.post.platforms[0].status);
```
</Tab>
<Tab value="Python">
```python
from zernio import Zernio

client = Zernio()

published = client.posts.create_post(
    content="New cooking tutorial #recipe #foodtok",
    media_items=[
        {"type": "video", "url": "https://cdn.example.com/cooking-tutorial.mp4"}
    ],
    platforms=[
        {"platform": "tiktok", "accountId": "66b2e19d8c3f5a7e9d0b1c2d"}
    ],
    tiktok_settings={
        "privacy_level": "PUBLIC_TO_EVERYONE",
        "allow_comment": True,
        "allow_duet": True,
        "allow_stitch": True,
        "content_preview_confirmed": True,
        "express_consent_given": True
    },
    publish_now=True
)

print(published["post"]["platforms"][0]["status"])
```
</Tab>
<Tab value="curl">
```bash
curl -X POST https://zernio.com/api/v1/posts \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "New cooking tutorial #recipe #foodtok",
    "mediaItems": [
      {"type": "video", "url": "https://cdn.example.com/cooking-tutorial.mp4"}
    ],
    "platforms": [
      {"platform": "tiktok", "accountId": "66b2e19d8c3f5a7e9d0b1c2d"}
    ],
    "tiktokSettings": {
      "privacy_level": "PUBLIC_TO_EVERYONE",
      "allow_comment": true,
      "allow_duet": true,
      "allow_stitch": true,
      "content_preview_confirmed": true,
      "express_consent_given": true
    },
    "publishNow": true
  }'
```
</Tab>
</Tabs>

Every option is on [Posts](/platforms/tiktok/posts).

## In this section

<PlatformPages platform="tiktok" />

## Related

- [Connecting accounts](/guides/connecting-accounts): the OAuth flow.
- [Create post](/posts/create-post): every field of the request.
- [Media uploads](/guides/media-uploads): upload videos and photos instead of hosting them.
- [Analytics](/analytics/get-analytics) and [Account insights](/analytics/get-tiktok-account-insights): post and account metrics.
- [Rate limits](/guides/rate-limits#tiktok-caps-direct-posting-per-application): how Zernio handles TikTok's quotas.
- [Pricing](/pricing): what a connected account and analytics cost.