Developer guide

TikTok API Content Posting API, keys, audit and limits in 2026

Checked against TikTok for Developers documentation in September 2026

Short answer: to post to TikTok from code you use the Content Posting API. Register an app at developers.tiktok.com to get a client key and secret, get the video.publish scope approved, and authorize each creator with Login Kit. Until TikTok audits your app, every post is private and only 5 users a day can post. Even after the audit, a creator can post about 15 times a day through the API.

The TikTok API is several APIs: which one do you need?

“TikTok API” covers a handful of separate products with separate sign-ups, and picking the wrong one wastes weeks of review. TikTok for Developers hosts the ones for apps and creators; ads and commerce live elsewhere.

APIWhat it doesUse it if you want to
Content Posting APIPublishes videos and photo posts to a creator’s profile (Direct Post) or sends them to their inbox as drafts (Upload)Post or schedule TikTok content from your app
Login KitOAuth sign-in with TikTok; returns the access token and open_id every other user API needsConnect a user’s TikTok account (required for posting)
Display API/v2/user/info/, /v2/video/list/, /v2/video/query/ with user.info.basic and video.listShow a connected user’s profile and their public videos
Research APIPublic videos, comments and accounts for approved researchersDo non-commercial academic or non-profit research
TikTok API for Business (Marketing API)Campaigns, ads, audiences and reporting, on business-api.tiktok.comManage TikTok ads programmatically
TikTok Shop Partner APIProducts, orders and fulfilment for TikTok Shop sellers, on partner.tiktokshop.comBuild commerce or logistics integrations

Sources: Content Posting API, Display API, Research API. Read 29 September 2026.

The Research API is the one people most often apply for by mistake. TikTok limits it to researchers at academic institutions or non-profit organisations in eligible regions (the US, EEA, UK, Canada, Switzerland, and Brazil for youth-safety research) who work on a non-commercial basis. A marketing tool or a trend dashboard will not qualify. The rest of this guide is about the Content Posting API.

How to get a TikTok API key

TikTok does not issue a single “API key”. You get a client key and client secret per app, then a user access token per creator. From the app registration guide:

  1. Create a developer account at developers.tiktok.com, and ideally an organization to own the app.
  2. Connect an app: profile icon → Manage apps → Connect an app. The Credentials section shows the client key and client secret. Keep the secret on your server; TikTok’s guidelines forbid embedding it in open source code.
  3. Fill in app details: icon (1024×1024), name, category, description, platforms (Web needs your website URL), terms of service and privacy policy URLs.
  4. Add products: Login Kit (with your redirect URIs: https, static, no query string or fragment, up to 10) and the Content Posting API. For publishing, turn on the Direct Post configuration.
  5. Add scopes: user.info.basic comes with Login Kit. Add video.publish for Direct Post and/or video.upload for inbox drafts.
  6. Verify URL properties: apps created after 9 September 2024 must verify their terms, privacy and website URLs, and any domain you will use with PULL_FROM_URL must be verified too, by DNS record or signature file.
  7. Submit for review with an explanation of how each product and scope is used and at least one demo video of the full flow (up to 5 videos, 50 MB each).

Sandbox mode lets you test integrations before the app is approved. App review and the Direct Post audit are two different gates: review makes your app live, the audit decides whether its posts can be public. More on that below.

OAuth: getting a user access token

https://www.tiktok.com/v2/auth/authorize/
  ?client_key=YOUR_CLIENT_KEY
  &response_type=code
  &scope=user.info.basic,video.publish
  &redirect_uri=https://yourapp.com/callback/
  &state=RANDOM_STRING

Check that state matches on the callback, then exchange the code:

# Code -> access token (24 h) + refresh token (365 days)
curl -X POST https://open.tiktokapis.com/v2/oauth/token/ \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "client_key=YOUR_CLIENT_KEY" \
  --data-urlencode "client_secret=YOUR_CLIENT_SECRET" \
  --data-urlencode "code=AUTH_CODE" \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "redirect_uri=https://yourapp.com/callback/"

# Refresh before the 24 h are up (store the refresh_token you get back)
curl -X POST https://open.tiktokapis.com/v2/oauth/token/ \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "client_key=YOUR_CLIENT_KEY" \
  --data-urlencode "client_secret=YOUR_CLIENT_SECRET" \
  --data-urlencode "grant_type=refresh_token" \
  --data-urlencode "refresh_token=REFRESH_TOKEN"

Access tokens last 24 hours, refresh tokens 365 days (token docs). Refresh in a background job, and save the refresh_token from every refresh response: TikTok may rotate it, and code that keeps using the original is the usual reason TikTok connections die after a few days.

Direct Post vs Upload (inbox drafts)

Direct PostUpload
Scopevideo.publishvideo.upload
Video endpoint/v2/post/publish/video/init//v2/post/publish/inbox/video/init/
ResultPublished to the profile with your caption and settingsDraft in the creator’s inbox; they finish and post it in the app
Caption and privacySent in post_infoSet by the creator in TikTok (video drafts take only the file)
Before the auditPrivate (SELF_ONLY) only, private accounts onlyNot subject to the Direct Post audit
Daily cap~15 posts per creator (shared across apps)5 pending drafts per creator per 24 hours

Sources: Direct Post, Upload, content sharing guidelines. Read 29 September 2026.

Upload is useful when a person reviews every post anyway, or while you wait for the audit. It is not automation: nothing goes live until the creator taps the notification.

How to post a video with the TikTok Content Posting API

Step 1: query creator info (mandatory)

curl -X POST https://open.tiktokapis.com/v2/post/publish/creator_info/query/ \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json; charset=UTF-8"

# -> "data": {
#      "creator_nickname": "Acme Studio",
#      "privacy_level_options": ["PUBLIC_TO_EVERYONE", "MUTUAL_FOLLOW_FRIENDS", "SELF_ONLY"],
#      "comment_disabled": false, "duet_disabled": false, "stitch_disabled": true,
#      "max_video_post_duration_sec": 300
#    }

This call is not optional. The privacy_level you send must be one of the returned privacy_level_options, or the post fails with privacy_level_option_mismatch. A public account gets PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS and SELF_ONLY; a private account gets FOLLOWER_OF_CREATOR, MUTUAL_FOLLOW_FRIENDS and SELF_ONLY. Check the video length against max_video_post_duration_sec, since different creators have different limits. If this call returns spam_risk_too_many_posts (with HTTP 200), stop and tell the user to try later.

Step 2: initialize, upload, check status

# 1. Initialize the post (FILE_UPLOAD: you send the bytes)
curl -X POST https://open.tiktokapis.com/v2/post/publish/video/init/ \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json; charset=UTF-8" \
  -d '{
    "post_info": {
      "title": "Day 12 of building in public #buildinpublic",
      "privacy_level": "PUBLIC_TO_EVERYONE",
      "disable_duet": false,
      "disable_comment": false,
      "disable_stitch": false,
      "video_cover_timestamp_ms": 1000
    },
    "source_info": {
      "source": "FILE_UPLOAD",
      "video_size": 30567100,
      "chunk_size": 30567100,
      "total_chunk_count": 1
    }
  }'
# -> {"data": {"publish_id": "v_pub_file~v2-1.123", "upload_url": "https://open-upload.tiktokapis.com/video/?upload_id=...&upload_token=..."}}

# 2. PUT the file to upload_url (valid for 1 hour)
curl -X PUT "UPLOAD_URL" \
  -H "Content-Type: video/mp4" \
  -H "Content-Range: bytes 0-30567099/30567100" \
  --data-binary @clip.mp4

# 3. Check the status
curl -X POST https://open.tiktokapis.com/v2/post/publish/status/fetch/ \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json; charset=UTF-8" \
  -d '{"publish_id": "v_pub_file~v2-1.123"}'

The caption goes in title, up to 2,200 UTF-16 runes, with hashtags and mentions parsed from the text. Other post_info fields: disable_duet, disable_stitch, disable_comment, video_cover_timestamp_ms, brand_content_toggle (paid partnership), brand_organic_toggle (promoting your own business) and is_aigc (AI-generated label).

The status endpoint returns PROCESSING_UPLOAD, PROCESSING_DOWNLOAD, SEND_TO_USER_INBOX, PUBLISH_COMPLETE or FAILED with a fail_reason. Public posts go through moderation first, which usually takes under a minute but can take hours, and the post ID only appears once it passes (status docs). Webhooks (post.publish.complete, post.publish.failed, post.publish.publicly_available) save you the polling.

FILE_UPLOAD or PULL_FROM_URL?

With PULL_FROM_URL, TikTok downloads the file from your server:

"source_info": {
  "source": "PULL_FROM_URL",
  "video_url": "https://media.yourdomain.com/tiktok/clip.mp4"
}

The URL must sit under a domain or URL prefix verified in your app, use https and not redirect (a 3xx counts as invalid), or you get url_ownership_unverified. The download times out after an hour. TikTok’s guidelines actually tell you to use PULL_FROM_URL when the file is already on your servers and FILE_UPLOAD when it is on the user’s device.

With FILE_UPLOAD, chunk rules are strict (media transfer guide): chunks of 5 to 64 MB, sent in order; files under 5 MB go in one piece; files over 64 MB must be chunked; 1 to 1,000 chunks; the last chunk takes the leftover bytes and can be up to 128 MB. total_chunk_count is video_size / chunk_size rounded down. Each chunk returns 206, the last one 201.

Photo posts and carousels

curl -X POST https://open.tiktokapis.com/v2/post/publish/content/init/ \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "media_type": "PHOTO",
    "post_mode": "DIRECT_POST",
    "post_info": {
      "title": "5 desk setups",
      "description": "Which one would you pick? #desksetup",
      "privacy_level": "PUBLIC_TO_EVERYONE",
      "auto_add_music": true
    },
    "source_info": {
      "source": "PULL_FROM_URL",
      "photo_cover_index": 0,
      "photo_images": [
        "https://media.yourdomain.com/desks/1.jpg",
        "https://media.yourdomain.com/desks/2.jpg"
      ]
    }
  }'

Photos use a different endpoint, /v2/post/publish/content/init/, with media_type=PHOTO and post_mode set to DIRECT_POST or MEDIA_UPLOAD. Up to 35 images, WebP or JPEG, up to 1080p and 20 MB each, and only by PULL_FROM_URL, so a verified domain is required. The title is limited to 90 characters and the description to 4,000. Photo drafts need TikTok app version 31.8 or later on the user’s phone, otherwise you get app_version_check_failed.

The audit: what unaudited apps can do

This is the part that stops most projects. From TikTok’s content sharing guidelines, an unaudited API client:

  • can let up to 5 users post in a 24-hour window;
  • can only post to accounts that are set to private at the time of posting;
  • can only post with SELF_ONLY visibility. To make a post public later, the owner has to switch the account to public and change each post to “Everyone” by hand.

Posting to a public account from an unaudited client fails at init with unaudited_client_can_only_post_to_private_accounts. To lift the restriction you apply for the audit, which checks your integration against TikTok’s terms and its required UX. That UX is specific: show the creator’s nickname; a privacy dropdown with no default value; comment, duet and stitch toggles that are off by default and greyed out when the creator has disabled them; a commercial content disclosure with “Your brand” and “Branded content”; the “By posting, you agree to TikTok’s Music Usage Confirmation” line; a preview of the post; and editable captions. No watermarks or promotional branding on the content.

The guidelines also say who will not pass. “An app that copies arbitrary contents from other platforms to TikTok” and “a utility tool to help upload contents to the account(s) you or your team manages” are both listed as not acceptable. API clients must be intended for a wide audience. If you are an agency or a brand automating your own accounts, the audit is not a route open to you.

TikTok API limits

LimitValue
Posts per creatorTypically around 15 per 24 hours via Direct Post, shared across all apps
Pending inbox drafts5 per creator per 24 hours
Active posting users per appDaily cap set from the usage estimate in your audit application
Unaudited app5 users per 24 hours, private accounts, SELF_ONLY
Rate limits (per user token)Video/photo init 6 per minute, creator info 20 per minute, status 30 per minute
VideoMP4 (recommended), WebM, MOV; H.264, H.265, VP8, VP9; 23–60 fps; 360–4096 px per side; up to 4 GB
Video lengthUp to 10 minutes via the API; each creator’s own limit is in max_video_post_duration_sec
PhotosUp to 35 per post; WebP or JPEG; max 1080p and 20 MB each
Caption2,200 UTF-16 runes (video); photo title 90, description 4,000
Upload URLValid for 1 hour
TokensAccess 24 hours, refresh 365 days

Sources: content sharing guidelines, Direct Post, creator info, post status, media transfer, photo post, tokens. Read 29 September 2026.

Common TikTok API errors and how to fix them

error.codeWhat TikTok saysFix
unaudited_client_can_only_post_to_private_accountsUnaudited clients can only post to a private account.Test on a private account with SELF_ONLY, or pass the audit.
spam_risk_too_many_postsThe daily post cap from the API is reached for the current user.Stop and retry the next day. Retrying now will not help.
spam_risk_too_many_pending_shareThe daily upload cap from the API is reached (max 5 pending shares in 24 hours).Have the creator post or clear the drafts in their inbox.
reached_active_user_capThe daily quota for active publishing users from your client is reached.An app-wide cap. Queue until the next day or apply for a higher estimate.
spam_risk_user_banned_from_postingThe user is banned from making new posts.Only the account owner can resolve it with TikTok.
privacy_level_option_mismatchprivacy_level is not specified or not among the creator’s options.Use a value from creator_info/query.
url_ownership_unverifiedThe domain or URL prefix of the media URL is not verified.Verify it under URL properties, or switch to FILE_UPLOAD.
scope_not_authorizedThe access_token does not bear the user’s grant on video.publish.Re-authorize with the scope; check it is approved on the app.
access_token_invalidThe access_token is invalid or has expired.Refresh it; if the refresh token is gone, re-authorize.
rate_limit_exceededBlocked for exceeding the API rate limit.Back off; init allows 6 calls per minute per token.

Sources: Direct Post errors, Upload errors. Read 29 September 2026.

TikTok API with Python

TikTok has no official Python SDK for the Content Posting API, and the flow is plain HTTPS. This script checks creator info, uploads a video in chunks that follow TikTok’s rules, and waits for the final status. It needs only requests.

import os
import time
import requests

API = "https://open.tiktokapis.com/v2"
TOKEN = "ACCESS_TOKEN"
HEADERS = {"Authorization": f"Bearer {TOKEN}",
           "Content-Type": "application/json; charset=UTF-8"}
MB = 1024 * 1024


def creator_info():
    r = requests.post(f"{API}/post/publish/creator_info/query/", headers=HEADERS)
    body = r.json()
    if body["error"]["code"] != "ok":
        # e.g. spam_risk_too_many_posts: stop and ask the user to try later
        raise RuntimeError(body["error"])
    return body["data"]


def direct_post(path, caption, privacy):
    size = os.path.getsize(path)
    # Chunks of 5-64 MB; files under 5 MB go whole; the last chunk
    # absorbs the remainder (up to 128 MB)
    chunk = min(10 * MB, size)
    count = max(1, size // chunk)

    r = requests.post(f"{API}/post/publish/video/init/", headers=HEADERS, json={
        "post_info": {"title": caption, "privacy_level": privacy},
        "source_info": {"source": "FILE_UPLOAD", "video_size": size,
                        "chunk_size": chunk, "total_chunk_count": count},
    })
    body = r.json()
    if body["error"]["code"] != "ok":
        raise RuntimeError(body["error"])
    publish_id = body["data"]["publish_id"]
    upload_url = body["data"]["upload_url"]

    with open(path, "rb") as f:
        for i in range(count):
            start = i * chunk
            end = size - 1 if i == count - 1 else start + chunk - 1
            f.seek(start)
            data = f.read(end - start + 1)
            requests.put(upload_url, data=data, headers={
                "Content-Type": "video/mp4",
                "Content-Range": f"bytes {start}-{end}/{size}",
            }).raise_for_status()   # 206 per chunk, 201 at the end
    return publish_id


def wait(publish_id):
    while True:   # status endpoint: 30 requests per minute per token
        r = requests.post(f"{API}/post/publish/status/fetch/", headers=HEADERS,
                          json={"publish_id": publish_id})
        data = r.json()["data"]
        if data["status"] in ("PUBLISH_COMPLETE", "FAILED"):
            return data
        time.sleep(5)


info = creator_info()
privacy = "PUBLIC_TO_EVERYONE"
if privacy not in info["privacy_level_options"]:
    raise SystemExit(f"Pick one of {info['privacy_level_options']}")
print(wait(direct_post("clip.mp4", "Day 12 #buildinpublic", privacy)))

In a real app, the privacy value comes from a dropdown the user picks from (TikTok requires no default), and the token refresh runs in its own job.

The shortcut: post to TikTok with one Upload-Post call

Building on the Content Posting API means an app review, an audit you may not qualify for, verified domains, chunked uploads, rotating refresh tokens and status polling. Upload-Post’s TikTok API is an audited TikTok integration: you connect a TikTok account once in the dashboard, and your posts go out publicly through the official Content Posting API. You send the file itself, so there is no domain to verify:

curl -X POST https://api.upload-post.com/api/upload \
  -H "Authorization: Apikey YOUR_KEY" \
  -F "[email protected]" \
  -F "user=my-brand" \
  -F "platform[]=tiktok" \
  -F "tiktok_title=Day 12 of building in public #buildinpublic" \
  -F "privacy_level=PUBLIC_TO_EVERYONE" \
  -F "disable_duet=false" \
  -F "disable_stitch=false" \
  -F "cover_timestamp=2500" \
  -F "brand_organic_toggle=true" \
  -F "scheduled_date=2026-10-01T18:00:00" \
  -F "timezone=America/Los_Angeles"

The fields map to TikTok’s: privacy_level, disable_duet, disable_comment, disable_stitch, cover_timestamp, brand_content_toggle, brand_organic_toggle and is_aigc. post_mode=MEDIA_UPLOAD sends the video to the creator’s drafts instead of publishing. Connected accounts can also take a custom cover image, a track from TikTok’s Commercial Music Library (tiktok_music_id) and a location tag. The Upload Video docs list every field.

Photo carousels, to TikTok and Instagram in the same request, with files instead of URLs:

curl -X POST https://api.upload-post.com/api/upload_photos \
  -H "Authorization: Apikey YOUR_KEY" \
  -F "user=my-brand" \
  -F "platform[]=tiktok" \
  -F "platform[]=instagram" \
  -F "photos[][email protected]" \
  -F "photos[][email protected]" \
  -F "photos[][email protected]" \
  -F "title=5 desk setups" \
  -F "description=Which one would you pick? #desksetup" \
  -F "auto_add_music=true"

The creator-info step becomes one GET, so your UI can offer only the privacy levels the account allows:

curl -G https://api.upload-post.com/api/uploadposts/tiktok/settings \
  -H "Authorization: Apikey YOUR_KEY" \
  -d profile=my-brand
# -> {"privacy_level_options": [...], "max_video_post_duration_sec": 600, ...}
  • Scheduling: scheduled_date with a timezone, up to 365 days ahead, or add_to_queue=true for the next free slot. The Content Posting API has no scheduling of its own.
  • Status: TikTok processes posts asynchronously; poll Upload Status or use webhooks to get the post URL.
  • Cross-posting: add platform[]=youtube and platform[]=instagram to send the same vertical video to Shorts and Reels.

You can create an account and API key for free, with no card. TikTok publishing is included in the paid plans, which start at $24 a month ($16 billed annually) with unlimited uploads. For the no-code route, see how to schedule TikTok posts; to post from an AI assistant, use the TikTok MCP server. Sending the same clips to YouTube? Read the YouTube API guide.

Frequently asked questions

Is there an official TikTok API?

Yes, several. TikTok for Developers (developers.tiktok.com) publishes Login Kit, the Content Posting API, the Display API, the Research API and others. Ads are a separate product, the TikTok API for Business, and TikTok Shop has its own Partner API. For publishing videos and photos to a creator’s account, the one you want is the Content Posting API.

How do I get a TikTok API key?

Sign up at developers.tiktok.com, open Manage apps and click Connect an app. The app page shows a Client key and Client secret under Credentials; together they are what people call the TikTok API key. Before real users can use the app you add products and scopes, verify your URLs and submit the app for review with a demo video.

Is the TikTok API free?

TikTok lists no fee or paid tier for Login Kit, the Content Posting API or the Display API. What it costs is approval time: an app review before release, and a separate audit before your posts can be public. The Research API is free but only for approved non-profit and academic researchers.

Why are my TikTok API posts private?

Your API client has not passed TikTok’s audit. Unaudited clients can only post with SELF_ONLY visibility, only to accounts that are set to private at the time of posting, and only for up to 5 users in 24 hours. Posting to a public account fails with unaudited_client_can_only_post_to_private_accounts. You lift this by applying for the Direct Post audit.

How many videos can I post to TikTok per day through the API?

TikTok caps posts per creator account through Direct Post at typically around 15 a day, and that cap is shared across every app the creator uses. Hitting it returns spam_risk_too_many_posts. The init endpoint also allows only 6 requests per minute per user token, and each app has a daily cap on active posting users.

What is the difference between Direct Post and Upload?

Direct Post (video.publish scope) publishes the video to the profile with the caption and settings you send. Upload (video.upload scope) sends the video to the creator’s TikTok inbox as a draft; they get a notification and finish the post in the TikTok app. Upload does not need the Direct Post audit, but at most 5 drafts can be pending in 24 hours.

Can I build an internal tool to post to my own TikTok accounts?

Not through the audit. TikTok’s content sharing guidelines list “a utility tool to help upload contents to the account(s) you or your team manages” as not acceptable, and require API clients to be intended for a wide audience. Unaudited, such a tool can only post privately. Teams in this position usually go through an already audited service.

Can I post TikTok photo carousels through the API?

Yes. POST /v2/post/publish/content/init/ with media_type PHOTO takes up to 35 images per post, as either a direct post or an inbox draft. Photos can only be sent with PULL_FROM_URL, so the images must sit on a domain or URL prefix you have verified in your TikTok app. Titles are limited to 90 characters and descriptions to 4,000.

Do I need a verified domain to use the TikTok API?

Only for PULL_FROM_URL, where TikTok downloads the file from your server: the URL must be under a domain or URL prefix verified in your app, use https and not redirect. FILE_UPLOAD, where you PUT the bytes to TikTok, needs no domain. Separately, apps created after 9 September 2024 must verify their terms of service, privacy policy and website URLs before review.

How long do TikTok access tokens last?

An access token is valid for 24 hours and a refresh token for 365 days. Refresh from a background job with grant_type=refresh_token, and always store the refresh token that comes back: TikTok may return a different one than you sent, and the old one stops being the right one to use.

Skip the app review and the audit

Publish and schedule public TikTok videos and photo posts through an audited integration, and send the same post to YouTube Shorts and Reels in the same request.