# TikTok

Publish TikTok videos and photo carousels with the right privacy and disclosure settings, send drafts to the Creator Inbox, and read post and account insights.

Publish videos and photo carousels to TikTok, or hand them to the creator's TikTok inbox as drafts to finish in the app. The same account gives you post and account insights, comment management and DM replies. The platform id is `tiktok`.

## What you can do

| Capability | Supported | Where |
| --- | --- | --- |
| Publish | Videos, photo carousels (up to 35 photos), Creator Inbox drafts | [Create a post](https://pozy.ai/docs/publishing) |
| Schedule | Yes | [Scheduling](https://pozy.ai/docs/publishing/scheduling) |
| Analytics | Likes, comments, shares, views; watch time, completion, traffic sources and retention on Business connections | [Platform insights](https://pozy.ai/docs/analytics/platform-insights) |
| DMs | Replies only, on eligible Business Accounts | [Messages](https://pozy.ai/docs/inbox/messages) |
| Comments | List, post, reply, like, hide, pin, delete your own | [Comments](https://pozy.ai/docs/inbox/comments) |
| Automations | Public reply to keyword comments | [Comment to DM](https://pozy.ai/docs/automations/comment-to-dm) |
| Ads | Through a TikTok Ads connection, including Spark Ads | [TikTok Ads](https://pozy.ai/docs/ads/tiktok-ads) |

## Connect

Start with [`GET /v1/connect/tiktok`](https://pozy.ai/docs/api/getConnectUrl) and a `profileId`, then send the user to the returned `authUrl`. TikTok connects through its business login, which works with both personal and business TikTok accounts. The account type comes back on the account as `tiktokAccountType` (`business`, `personal` or `unknown`).

A few behaviours worth knowing before you build the flow:

- TikTok will not grant a connection without the video permissions, so `scopes` can only trim the analytics, comment and messaging permissions.
- If the user leaves out a permission an already connected account needs, nothing changes and the flow returns `missing_tiktok_permissions`. Ask them to connect again and accept every permission.
- A profile can hold several TikTok accounts. Connecting an account the profile already has refreshes it. Authorizing a different account into a reconnect replaces the old one and its history, and the match is made on the last known `@handle`, so a renamed handle can look like a new account.

> **Older connections:**
> 
> Accounts connected before TikTok's business login was available may still sit on the older connection type. They can publish but get fewer analytics fields, no inbox, and they share a TikTok-wide cap on how many accounts can publish per day. Reconnecting the account moves it to the current connection type.

## Post types

TikTok needs media on every post. All TikTok settings go in a `tiktokSettings` object at the top level of the request. It applies to every TikTok entry; to give two TikTok accounts different settings, put the object in each entry's `platformSpecificData.tiktokSettings` instead (the entry wins).

Four settings are always required, because TikTok requires an explicit choice for each one:

| Field | Required | Notes |
| --- | --- | --- |
| `privacyLevel` | Yes | One of the values from [creator info](#check-creator-info-first): `PUBLIC_TO_EVERYONE`, `MUTUAL_FOLLOW_FRIENDS`, `FOLLOWER_OF_CREATOR`, `SELF_ONLY`. |
| `allowComment` | Yes | Comments on or off. |
| `allowDuet`, `allowStitch` | Yes, for videos | Ignored on photo posts. |
| `contentPreviewConfirmed`, `expressConsentGiven` | Yes, both `true` | TikTok's legal requirement: the user saw a preview and agreed to post. Collect this consent in your own UI. |

Snake case (`privacy_level`, `allow_comment`) is accepted too.

### Video

One video, 3 seconds to 10 minutes. Vertical 9:16 is the only format that looks right on TikTok.

```bash cURL
curl -X POST https://api.pozy.ai/v1/posts \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Three ways to fold a dumpling #cooking",
    "mediaItems": [{ "type": "video", "url": "https://cdn.example.com/dumplings.mp4" }],
    "platforms": [{ "platform": "tiktok", "accountId": "6720c1a94f3b2e0018d4a7f2" }],
    "tiktokSettings": {
      "privacyLevel": "PUBLIC_TO_EVERYONE",
      "allowComment": true,
      "allowDuet": false,
      "allowStitch": true,
      "contentPreviewConfirmed": true,
      "expressConsentGiven": true
    },
    "publishNow": true
  }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/posts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    content: "Three ways to fold a dumpling #cooking",
    mediaItems: [{ type: "video", url: "https://cdn.example.com/dumplings.mp4" }],
    platforms: [{ platform: "tiktok", accountId: "6720c1a94f3b2e0018d4a7f2" }],
    tiktokSettings: {
      privacyLevel: "PUBLIC_TO_EVERYONE",
      allowComment: true,
      allowDuet: false,
      allowStitch: true,
      contentPreviewConfirmed: true,
      expressConsentGiven: true,
    },
    publishNow: true,
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);

console.log(data.post.status);
```
```python Python
import os, requests

resp = requests.post(
    "https://api.pozy.ai/v1/posts",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    json={
        "content": "Three ways to fold a dumpling #cooking",
        "mediaItems": [{"type": "video", "url": "https://cdn.example.com/dumplings.mp4"}],
        "platforms": [{"platform": "tiktok", "accountId": "6720c1a94f3b2e0018d4a7f2"}],
        "tiktokSettings": {
            "privacyLevel": "PUBLIC_TO_EVERYONE",
            "allowComment": True,
            "allowDuet": False,
            "allowStitch": True,
            "contentPreviewConfirmed": True,
            "expressConsentGiven": True,
        },
        "publishNow": True,
    },
)
print(resp.json()["post"]["status"])
```

TikTok assigns the video id a few minutes after publishing, so `platformPostUrl` may be `null` in the response. Pozy sends the [`post.tiktok.url_resolved`](https://pozy.ai/docs/webhooks/events#post-tiktok-url-resolved) webhook when the URL is known.

**Covers.** `videoCoverTimestampMs` picks a frame (default 1000 ms). `videoCoverImageUrl` uses your own image instead (JPG, PNG or WebP, up to 20 MB) and wins when both are set.

> **Video privacy on business connections:**
> 
> Accounts on TikTok's business connection publish videos as public only. Any other `privacyLevel` on a direct video post is rejected. To get a private video, send it as a [draft](#draft-to-the-creator-inbox) and let the creator choose privacy in the app. Photo posts keep every privacy level the creator allows.

### Photo carousel

Up to 35 images, no videos in the same post. Set `mediaType: "photo"`. On photo posts `content` becomes the title (90 characters, hashtags and URLs removed), so put the real caption in `description` (up to 4,000 characters).

```json
"tiktokSettings": {
  "mediaType": "photo",
  "photoCoverIndex": 0,
  "description": "Our weekend on the coast, start to finish. #travel #roadtrip",
  "autoAddMusic": true,
  "privacyLevel": "PUBLIC_TO_EVERYONE",
  "allowComment": true,
  "contentPreviewConfirmed": true,
  "expressConsentGiven": true
}
```

### Draft to the Creator Inbox

`draft: true` uploads the media to the creator's TikTok inbox instead of publishing. They get a notification, open TikTok's editor, and set caption, cover, sound and privacy there. Nothing is public until they tap Post.

- Pozy marks the entry `published` when TikTok accepts the upload. That is the hand-over, not the moment it goes live. The entry has `platformSpecificData.isDraft: true` and no URL, and `post.tiktok.url_resolved` never fires.
- TikTok allows 5 pending drafts per account in any 24 hours. A sixth fails until the creator finishes or discards one in the app.
- Location, commercial music and other post details are ignored on drafts, because the creator sets them in TikTok.
- The creator needs a recent version of the TikTok app.

### Check creator info first

Call [Get TikTok creator info](https://pozy.ai/docs/api/getTikTokCreatorInfo) before you show a TikTok composer. It returns the privacy levels this creator may use, the maximum video length, which interaction toggles they switched off in the app (`enabled: false`), and which commercial content types they can disclose. Pass `mediaType=photo` for photo posts.

### Location, music and disclosures

| Field | What it does | Notes |
| --- | --- | --- |
| `locationId` + `locationName` | Tags a place | Search with [TikTok locations](https://pozy.ai/docs/api/searchTikTokLocations). Video posts on business connections only. |
| `musicSoundInfo` | Adds a Commercial Music Library track | `musicSoundId` from [trending commercial music](https://pozy.ai/docs/api/listTikTokCommercialMusic) (prefer the track's `clip.id`), plus `musicSoundVolume`, `musicSoundStart`, `musicSoundEnd`. |
| `videoOriginalSoundVolume` | Keeps the video's own sound under the track | 0 to 100. Leave it out and the original sound is muted. |
| `autoAddMusic` | Lets TikTok add music | Photo carousels only. |
| `commercialContentType` | Discloses commercial content | `none`, `brand_organic` (your own brand) or `brand_content` (paid partnership). Branded content cannot be `SELF_ONLY`. |
| `videoMadeWithAi` | AI-generated content label | On business connections, video posts only. |
| `isAdsOnly` | Publishes as an ads-only post for Spark Ads | Video posts on business connections. |

### Dry run

Send the same request with `dryRun: true` to check whether each TikTok account can publish right now. The response has `canPublish` and one verdict per TikTok entry with a `reason`. Nothing is saved and no allowance is used, so you can call it as often as you like.

## Media requirements

A post is one video or up to 35 photos, never both. Sharing links from cloud drives return a web page, not the file, so use direct URLs or [upload the file](https://pozy.ai/docs/publishing/media).

| | Photos | Video |
| --- | --- | --- |
| Formats | JPEG, PNG, WebP | MP4, MOV, WebM (H.264) |
| Max size | 20 MB each | 4 GB |
| Count | Up to 35 | 1 |
| Duration | | 3 seconds to 10 minutes |
| Aspect ratio | 9:16 recommended | 9:16 |
| Resolution | Scaled down to fit 1080 x 1920 | 1080 x 1920 recommended, 30 fps |

## Limits

| Limit | Value |
| --- | --- |
| Video caption | 2,200 characters |
| Photo title / description | 90 / 4,000 characters |
| Posts per account (business connection) | 15 videos and 15 photo posts per rolling 24 hours, counted separately |
| Pending drafts | 5 per account per 24 hours |
| Posts per account per hour | 25 |
| DM replies | Up to 10 messages within 48 hours of the user's last message |

When an account goes over the 15-a-day allowance, the post is not failed: Pozy holds it and publishes it as soon as the rolling window frees a slot. TikTok also enforces its own daily API limit per account. A post rejected for that fails with "You have created too many posts in the last 24 hours via the API" and can go out once TikTok's window rolls.

TikTok moderates API posts more strictly than posts made in the app. The API cannot create duets or stitches, edit or delete a published post, go live, add effects, or start a DM conversation.

## Analytics

[`GET /v1/analytics?platform=tiktok`](https://pozy.ai/docs/api/getAnalytics) returns `likes`, `comments`, `shares` and `views` for every post. On business connections you also get `saves`, `reach`, watch time, `completionRate` (0 to 1), `profileViews`, `follows`, `clicks`, `websiteClicks`, `impressionSources` (For You, Follow, Search and others as shares of 1), `audienceTypes`, `audienceCountries` and a per-second `retentionCurve`. TikTok fills these 24 to 48 hours after publishing and only for posts active in the last 7 days, so a fresh post reports zeros at first. The [post timeline](https://pozy.ai/docs/api/getPostTimeline) has the same fields per day.

[TikTok account insights](https://pozy.ai/docs/api/getTikTokAccountInsights) adds follower, following, like and video counts, with `followers_gained` and `followers_lost` from daily snapshots. TikTok does not expose account-level impressions or reach on any API.

## Inbox

**Comments** work on any connected account on the current connection type. Read a video's comments with [Get post comments](https://pozy.ai/docs/api/getInboxPostComments) using the TikTok video id; each comment includes up to three replies, and passing `commentId` pages through the rest. You can post, reply, like, hide and unhide any comment, delete your own, and [pin](https://pozy.ai/docs/api/pinInboxComment) a top-level comment. Comment webhooks carry the author id only, without a username or picture.

**DMs** are reply-only. TikTok lets a business answer a conversation but never start one, and only when:

- the account is a TikTok Business Account,
- it was registered outside the EEA, Switzerland and the UK,
- it accepts messages (from everyone, or by accepting requests).

A reply is either text (up to 6,000 characters) or one JPG or PNG image up to 3 MB, never both in one message. After the user's last message TikTok accepts up to 10 messages within 48 hours.

## Errors you may see

| Message | Cause | Fix |
| --- | --- | --- |
| Selected privacy level is not available for this creator | `privacyLevel` not in the creator's allowed list | Read [creator info](https://pozy.ai/docs/api/getTikTokCreatorInfo) and use one of its values. |
| Too many posts in the last 24 hours via the API | TikTok's own daily limit for the account | Wait for the rolling window, or post in the TikTok app. |
| TikTok direct posting is at capacity right now | Older connection type sharing TikTok's daily account cap | Retry later, send as a draft, or reconnect the account. |
| TikTok flagged this post as potentially risky | TikTok's moderation | Review the content. |
| Video upload failed: download failed | TikTok could not fetch the video URL | Use a direct, public file URL. |
| Missing required TikTok permissions | The user declined a permission | Reconnect and accept every permission. |
| Timeout waiting for platform response | Large videos take TikTok a while to process | Check the post status after a few minutes before retrying. |

A rejected `publishNow: true` post returns `207` with `post.status: "failed"` and the reason in `platforms[].errorMessage`.

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/accounts/{accountId}/tiktok/creator-info`](https://pozy.ai/docs/api/getTikTokCreatorInfo) | Get TikTok creator info |
| [`GET /v1/accounts/{accountId}/tiktok/locations`](https://pozy.ai/docs/api/searchTikTokLocations) | Search TikTok location tags |
| [`GET /v1/accounts/{accountId}/tiktok/commercial-music`](https://pozy.ai/docs/api/listTikTokCommercialMusic) | List trending commercial music |
| [`GET /v1/analytics/tiktok/account-insights`](https://pozy.ai/docs/api/getTikTokAccountInsights) | Get TikTok account-level insights |
| [`GET /v1/inbox/comments/{postId}`](https://pozy.ai/docs/api/getInboxPostComments) | Get post comments |
| [`POST /v1/inbox/comments/{postId}/{commentId}/pin`](https://pozy.ai/docs/api/pinInboxComment) | Pin comment |
| [`DELETE /v1/inbox/comments/{postId}/{commentId}/pin`](https://pozy.ai/docs/api/unpinInboxComment) | Unpin comment |

## Webhook events

| Event | When it fires |
| --- | --- |
| [`post.published`](https://pozy.ai/docs/webhooks/events#post-published) | Post published. |
| [`post.failed`](https://pozy.ai/docs/webhooks/events#post-failed) | Post failed. |
| [`post.platform.published`](https://pozy.ai/docs/webhooks/events#post-platform-published) | Post platform published. |
| [`post.platform.failed`](https://pozy.ai/docs/webhooks/events#post-platform-failed) | Post platform failed. |
| [`post.platform.deleted`](https://pozy.ai/docs/webhooks/events#post-platform-deleted) | Post platform deleted. |
| [`post.tiktok.url_resolved`](https://pozy.ai/docs/webhooks/events#post-tiktok-url-resolved) | TikTok post URL resolved. |
| [`account.disconnected`](https://pozy.ai/docs/webhooks/events#account-disconnected) | Account disconnected. |
| [`message.received`](https://pozy.ai/docs/webhooks/events#message-received) | Message received. |
| [`message.sent`](https://pozy.ai/docs/webhooks/events#message-sent) | Message sent. |
| [`comment.received`](https://pozy.ai/docs/webhooks/events#comment-received) | Comment received. |
