TikTok API for Developers in 2026: Content Posting, Audit, Scopes, and Limits
How to post to TikTok through the API in 2026: Login Kit scopes, Direct Post vs Upload, the audit that lifts private-only posting, media specs, rate limits, and error codes.
TikTok’s Content Posting API lets you publish videos and photo carousels from your app. Before you can make those posts public through Direct Post, though, TikTok has to approve your integration. Until then, you can test private posts with up to five creators per day.
Getting PostZen through that audit took us six weeks and more than a dozen submissions. Blocked reviewers accounted for most of the rejections. After approval, we also hit a media restriction in production: TikTok rejected a photo post because it was a PNG. This guide covers the audit requirements and publishing flow, with Node and Python examples you can adapt for your integration.
The API details in this post were checked against TikTok’s developer documentation on September 5, 2026.
Choosing the right TikTok API
TikTok offers several APIs. Most are on developers.tiktok.com; the Marketing and Shop APIs have separate developer portals.
| API | What it is for |
|---|---|
| Login Kit | OAuth sign-in with a TikTok account for publishing and reading creator data. |
| Display API | Read a creator’s profile and public videos after they authorise your app. |
| Content Posting API | Publish videos and photo posts to a creator’s account, directly or as inbox drafts. |
| Research API | Public content and account data for qualified academic researchers. |
| Commercial Content API | Ad transparency data, currently focused on the EU. |
| Data Portability API | Lets EEA and UK users transfer their TikTok data archive to your app. |
| Marketing API | Ads, campaigns, and Business Center, on the separate TikTok for Business portal. |
| Shop API | E-commerce, on the TikTok Shop Partner Center, a separate developer program. |
For a publishing integration, you need Login Kit and the Content Posting API. Add the Display API if you also need the creator’s public video list and stats.
Direct Post or Upload?
The Content Posting API lets you publish a finished post or send media to TikTok for the creator to edit. Each mode needs its own permission scope.
- Direct Post (
post_mode: DIRECT_POST) publishes straight to the creator’s profile with the caption, privacy level, and interaction settings you send. It needs thevideo.publishscope. Use it for scheduled posts or publishing from your app. - Upload (
post_mode: MEDIA_UPLOAD) sends the media to TikTok, and the creator gets an inbox notification to finish the post in TikTok’s editor: add sounds, effects, and text, then publish. It needs thevideo.uploadscope. TikTok documents this flow in its photo post reference.
Upload is useful for creators who want to add sounds or effects in TikTok before publishing. You can also offer it while waiting for your app’s audit: inbox drafts are exempt from Direct Post’s private-only restriction. A product can support both modes, letting the creator choose where to finish the post.
Authentication and token refresh
Login Kit is OAuth 2.0 with short-lived tokens. The authorisation URL is https://www.tiktok.com/v2/auth/authorize/; the token endpoint is POST https://open.tiktokapis.com/v2/oauth/token/, used both for the initial exchange and for refreshes. PKCE is required for mobile and desktop apps. According to TikTok’s token management guide, access tokens last 24 hours and refresh tokens last 365 days after issuance.
A refresh response may include a replacement refresh token. Store both returned tokens together so the next refresh uses the current value. In Python:
import requests
def refresh_tiktok_token(refresh_token: str, client_key: str, client_secret: str) -> dict:
response = requests.post(
'https://open.tiktokapis.com/v2/oauth/token/',
data={
'client_key': client_key,
'client_secret': client_secret,
'grant_type': 'refresh_token',
'refresh_token': refresh_token,
},
headers={'Content-Type': 'application/x-www-form-urlencoded'},
timeout=30,
)
response.raise_for_status()
data = response.json()
# Persist BOTH tokens: the refresh token may have rotated.
return {
'access_token': data['access_token'], # valid 24 h
'refresh_token': data['refresh_token'], # may differ from the input
'expires_in': data['expires_in'],
}
Request the scopes your product needs for publishing and analytics. TikTok lists them in its scopes reference:
| Scope | What it grants |
|---|---|
user.info.basic |
Open ID, avatar, display name. Added by default. |
user.info.profile |
Profile link, bio, verified flag. |
user.info.stats |
Likes, follower, following, and video counts. |
video.list |
Read a user’s public TikTok videos. |
video.upload |
Send a draft to the creator for editing and publishing in TikTok. |
video.publish |
Publish content to a user’s TikTok profile. |
Ask for video.publish and video.upload only if you use both modes; the audit reviews every scope you request against a demo of your product using it.
Getting through the Content Posting API audit
New Content Posting API apps start unaudited. Under TikTok’s content sharing guidelines, Direct Post is limited to SELF_ONLY privacy and five users per 24-hour window. A public posting attempt returns unaudited_client_can_only_post_to_private_accounts; a sixth creator gets reached_active_user_cap. Upload drafts are exempt from the privacy restriction.
TikTok’s FAQ gives a review estimate of several days to two weeks after submission. Reviewers check compliance with TikTok’s terms and the posting interface. Your compose screen must:
- Show the creator’s nickname from
creator_info, so they can confirm which account they are posting to. - Offer a privacy selector with no default. The creator must choose from the
privacy_level_optionsreturned for their account. - Leave interaction toggles off. The creator must choose to enable comments, duets, and stitches.
- Include a commercial content disclosure toggle, off by default, with “Your brand” and “Branded Content” choices.
- Display the required consent text, including TikTok’s Music Usage Confirmation or the applicable Branded Content Policy text.
Submit a demo video showing the full flow, from sign-in and authorisation to a published private post.
Most of our rejections said “Website is not accessible / Invalid Website URL,” even though the site was up. We traced the problem to Vercel’s DDoS mitigation and a Cloudflare managed rule blocking the cloud datacenter IP ranges used by TikTok’s validator and reviewers. Reviewers also followed the demo into our app domain, so we had to check access there too.
We documented the diagnosis and fixes in our TikTok “Invalid Website URL” post. If an app record has accumulated repeated rejections, consider submitting a fresh one. Once you have approval, plan configuration changes with care: editing URLs or scopes can trigger another review.
Publishing a video
To publish through Direct Post, fetch the creator’s posting options, initialise the post, and poll its status until TikTok finishes processing it.
1. Query creator info. POST /v2/post/publish/creator_info/query/ returns the creator’s nickname, their privacy_level_options, whether comments, duets, or stitches are disabled on the account, and max_video_post_duration_sec. You need this before showing the compose form, because the privacy level you send must be one of the returned options or the publish fails with privacy_level_option_mismatch.
2. Initialise the post. POST /v2/post/publish/video/init/ with post_info and source_info. The Direct Post reference defines the fields:
| Field | Meaning |
|---|---|
post_info.title |
The caption, up to 2,200 UTF-16 code units. Hashtags and @mentions are parsed. |
post_info.privacy_level |
PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, or SELF_ONLY; must match privacy_level_options. |
post_info.disable_comment / disable_duet / disable_stitch |
Booleans. |
post_info.video_cover_timestamp_ms |
Frame to use as the cover; defaults to the first frame. |
post_info.brand_content_toggle / brand_organic_toggle |
Paid partnership and own-brand disclosure. |
post_info.is_aigc |
Labels the video as AI-generated. |
source_info.source |
PULL_FROM_URL or FILE_UPLOAD. |
source_info.video_url |
For pull: a public URL TikTok fetches. Your domain must be verified in the developer portal. |
source_info.video_size, chunk_size, total_chunk_count |
For file upload: sizes in bytes and the chunk plan. |
The response contains a publish_id, and for file upload an upload_url that stays valid for one hour.
3. Poll status. POST /v2/post/publish/status/fetch/ with the publish_id. The status reference lists PROCESSING_UPLOAD, PROCESSING_DOWNLOAD, SEND_TO_USER_INBOX (upload mode only), PUBLISH_COMPLETE, and FAILED with a fail_reason.
This Node example starts a video post using a URL that TikTok can fetch:
const TIKTOK = 'https://open.tiktokapis.com/v2'
async function tiktokPost(path, body, token) {
const response = await fetch(`${TIKTOK}${path}`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json; charset=UTF-8' },
body: JSON.stringify(body),
})
const json = await response.json()
if (!response.ok || json.error?.code !== 'ok') throw new Error(JSON.stringify(json.error ?? json))
return json.data
}
export async function publishVideoFromUrl({ token, videoUrl, caption, privacyLevel }) {
const creator = await tiktokPost('/post/publish/creator_info/query/', {}, token)
if (!creator.privacy_level_options.includes(privacyLevel)) {
throw new Error(`privacy level ${privacyLevel} not available for ${creator.creator_nickname}`)
}
const { publish_id } = await tiktokPost(
'/post/publish/video/init/',
{
post_info: {
title: caption,
privacy_level: privacyLevel,
disable_comment: false,
disable_duet: false,
disable_stitch: false,
},
source_info: { source: 'PULL_FROM_URL', video_url: videoUrl },
},
token,
)
return publish_id
}
Use the returned publish_id to check progress. This Python example waits for completion or a failure:
import time
import requests
TIKTOK = 'https://open.tiktokapis.com/v2'
def wait_for_publish(publish_id: str, token: str, timeout_s: int = 600) -> dict:
deadline = time.time() + timeout_s
while time.time() < deadline:
response = requests.post(
f'{TIKTOK}/post/publish/status/fetch/',
json={'publish_id': publish_id},
headers={'Authorization': f'Bearer {token}'},
timeout=30,
)
data = response.json().get('data', {})
status = data.get('status')
if status == 'PUBLISH_COMPLETE':
return data # includes publicaly_available_post_id when public
if status == 'FAILED':
raise RuntimeError(f"TikTok publish failed: {data.get('fail_reason')}")
time.sleep(10) # stay well under 6 requests/minute per token
raise TimeoutError('TikTok is still processing after 10 minutes')
To upload a file, initialise the post with source: 'FILE_UPLOAD', then send PUT requests to the returned upload_url with Content-Range headers. The media transfer guide sets the chunk rules: 5 MB to 64 MB per chunk, at most 1,000 chunks, a final chunk that may run to 128 MB, and files under 5 MB sent whole.
Photo posts and the PNG restriction
Photo carousels use POST /v2/post/publish/content/init/ with media_type: PHOTO. The photo post reference allows up to 35 image URLs in source_info.photo_images. Set photo_cover_index to choose the cover image.
Photos must be available at public URLs on a domain you have verified in the developer portal. There is no file-upload option; an unverified URL returns url_ownership_unverified. Photo posts also have a short title alongside the description. PostZen limits that title to 90 characters.
TikTok accepts JPEG and WebP photos, up to 20 MB each. We ran into this restriction in production: a PNG photo post returned FAILED with fail_reason: file_format_check_failed within seconds. PostZen now converts PNG uploads to JPEG before sending them to TikTok. In your own integration, convert unsupported formats or reject them when the user uploads the file. TikTok also rejects GIFs.
Media requirements
From the media transfer guide, as of September 2026:
| Video | Photo post | |
|---|---|---|
| Formats | MP4 (recommended), WebM, MOV | JPEG, WebP |
| Codecs | H.264 (recommended), H.265, VP8, VP9 | |
| Max size | 4 GB | 20 MB per image |
| Count | 1 | Up to 35 |
| Duration | Up to the creator’s max_video_post_duration_sec; 3 minutes for most creators, 5 or 10 for some, 10 minutes is the API ceiling |
|
| Frame rate | 23 to 60 fps | |
| Resolution | 360 px to 4096 px on each side | Up to 1080p |
| Source | Public URL on a verified domain, or chunked file upload | Public URL on a verified domain only |
Serve URL-based media from a CDN or another reliable host. TikTok stops a download after an hour, which can leave the creator waiting before you receive a failure. Check max_video_post_duration_sec before uploading, too: a four-minute video may exceed one creator’s limit even if another account can publish it.
Rate limits and publishing caps
The documented request limit is 6 calls per minute per access token, which is why the polling example above sleeps 10 seconds. Beyond that, TikTok enforces caps it does not put numbers on:
- A daily post cap per creator, surfaced as
spam_risk_too_many_posts, described only as “the daily post cap from the API is reached for the current user.” Third-party reports put it in the low teens; TikTok does not publish it. - A pending-share cap,
spam_risk_too_many_pending_share, for uploads waiting in a creator’s inbox. - An active-user cap per app,
reached_active_user_cap: 5 creators per 24 hours while unaudited, and after the audit a figure “based on the usage estimates provided in the audit application form.” Use realistic usage estimates in your application, since TikTok uses them to set your app’s cap.
Common errors and fixes
These errors appear in the Content Posting API references. We also include the format failure we encountered with a PNG photo upload.
| Code | HTTP | Meaning | Fix |
|---|---|---|---|
access_token_invalid |
401 | Token expired (they last 24 hours) or revoked | Refresh; if refresh fails, reconnect the creator |
scope_not_authorized |
401 | Token lacks video.publish (or video.upload) |
Re-authorise with the missing scope |
rate_limit_exceeded |
429 | Over 6 requests per minute per token | Back off; poll every 10 seconds or slower |
unaudited_client_can_only_post_to_private_accounts |
403 | App not audited, privacy not SELF_ONLY |
Pass the audit, or post SELF_ONLY / use Upload |
reached_active_user_cap |
403 | Daily active publishing users exhausted | Wait for the window; raise estimates in the audit |
spam_risk_too_many_posts |
403 | Creator hit the unpublished daily post cap | Wait 24 hours |
spam_risk_user_banned_from_posting |
403 | Creator is banned from posting | Nothing to fix in code |
url_ownership_unverified |
403 | Media URL domain not verified for your app | Verify the domain or URL prefix in the portal |
privacy_level_option_mismatch |
403 | Privacy level not in the creator’s options | Query creator_info first and offer only those |
invalid_params |
400 | Malformed request | Check the error message |
invalid_file_upload |
400 | Uploaded file breaks the spec | Re-encode within the media limits |
fail_reason: file_format_check_failed |
status | Media format rejected (our PNG photo case) | Transcode to JPEG or WebP |
internal |
500 | TikTok-side error | Retry with backoff; contact support if persistent |
Log the error.log_id from every failed response; TikTok support asks for it.
Is the TikTok API free?
TikTok publishes no fee for Login Kit, the Display API, or the Content Posting API. You still need to budget development time for the audit and ongoing maintenance, including token refresh and media validation. Our social media API comparison covers the other networks, with a separate breakdown of X API pricing.
Publishing through PostZen
We built this integration for PostZen, and our TikTok app has passed the audit. Once a creator connects their account, you can submit a post with the request below. PostZen handles token refresh, creator-info checks, media hosting, PNG-to-JPEG conversion, and status polling.
curl -X POST https://api.postzen.dev/v1/posts \
-H "Authorization: Bearer pzn_live_..." \
-H "Content-Type: application/json" \
-d '{
"content": "Behind the scenes of the new release #buildinpublic",
"publishNow": true,
"mediaItems": [{ "url": "https://cdn.example.com/release.mp4" }],
"platforms": [
{
"platform": "tiktok",
"accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
"settings": {
"privacyLevel": "publicToEveryone",
"allowComments": true,
"allowDuet": false,
"allowStitch": false
}
}
]
}'
Set privacyLevel for Direct Post, or use uploadAsDraft: true to send the post to the creator’s TikTok inbox. The available privacy levels depend on the creator’s account. This endpoint accepts videos up to 4 GB and photo posts with up to 10 items per request. Use brandContentToggle and brandOrganicToggle for commercial content disclosure.
The account ID comes from the accounts endpoint after the creator connects through a hosted OAuth page. See the TikTok integration docs for the full request options, or the TikTok page and social media API overview for supported features across networks. TikTok’s publishing caps and account restrictions still apply to posts sent through PostZen.
Frequently asked questions
Does TikTok have an API for posting videos?
Yes. The Content Posting API publishes videos and photo posts to a creator’s account after they authorise your app through Login Kit. Until your app passes TikTok’s audit, posts are private-only and limited to five users per 24 hours.
Is the TikTok API free?
TikTok publishes no fee for Login Kit, the Display API, or the Content Posting API. Your app must pass an audit before it can publish public posts through Direct Post.
What is the difference between Direct Post and Upload in the TikTok API?
Direct Post publishes straight to the profile with the privacy and interaction settings you send. Upload sends the media to the creator’s TikTok inbox as a draft, and they finish and publish it in the TikTok app. Direct Post needs the video.publish scope; Upload needs video.upload.
How long do TikTok API access tokens last?
Access tokens are valid for 24 hours. Refresh tokens are valid for 365 days, and each refresh may return a new refresh token that replaces the old one, so store the returned value every time.
Why did my TikTok API post fail with unaudited_client_can_only_post_to_private_accounts?
Your app has not passed TikTok’s Content Posting API audit. Unaudited apps may only publish with SELF_ONLY privacy. Submit the audit from the app’s Content Posting API page, with a demo that shows the required consent UX.
What formats does the TikTok API accept?
Videos: MP4, WebM, or MOV, H.264 recommended, up to 4 GB, 23 to 60 fps, 360 to 4096 px. Photo posts: JPEG or WebP only, up to 35 images, 20 MB each. PNG photos are rejected with file_format_check_failed.



