TikTok Python SDK reference
The TikTok client in the Socialift Python SDK wraps TikTok's Open API v2 (Login Kit, profile, video list, Content Posting API) and TikTok API for Business (account metrics, comment moderation). All methods are async and take the user's access token as an argument, so one client can serve many creators. Below is every method, grouped by task, with limits taken from TikTok's documentation.
from socialift import TikTok26 methodsas of socialift 1.3.0Updated:
How do I create a TikTok client?
Create an app on developers.tiktok.com and copy its Client key and Client secret. Pass them as client_key and client_secret, or call TikTok.from_env(), which reads the TIKTOK_CLIENT_KEY and TIKTOK_CLIENT_SECRET environment variables. The Socialift license key comes from SOCIALIFT_LICENSE_KEY (or the license_key= argument).
The client never stores tokens: every method takes token as an argument. An access token is valid for 24 hours and a refresh token for 365 days, and TikTok may return a new refresh token on each refresh, so overwrite the stored value every time.
By default login_url() requests the scopes in DEFAULT_SCOPES (user.info.basic, user.info.profile, user.info.stats, video.list, video.upload, video.publish); pass scopes= to ask for fewer. Business API methods (business_account, comments and others) also need business_id: set it once with TikTok(..., business_id=open_id) or pass it on each call.
Use the client with async with so the HTTP session is closed properly. Even when TikTok returns an error inside an HTTP 200 response, the SDK raises it as AuthError, PermissionError, RateLimitError and so on.
import asyncio
import os
import secrets
from socialift import TikTok
# SOCIALIFT_LICENSE_KEY must be set as well
tt = TikTok(
client_key=os.environ["TIKTOK_CLIENT_KEY"],
client_secret=os.environ["TIKTOK_CLIENT_SECRET"],
)
# Same thing: tt = TikTok.from_env()
async def main():
async with tt:
state = secrets.token_urlsafe(16) # store it, compare it in the callback
url = tt.login_url(redirect_uri="https://example.com/tiktok/callback", state=state)
print(url)
# in the callback: bundle = await tt.exchange_code(code, redirect_uri=...)
# profile: me = await tt.me(bundle.access_token)
asyncio.run(main())Login and tokens
OAuth via Login Kit: authorization URL, code exchange, refresh and revoke.
exchange_codefrom_envlogin_urlrefresh_tokenrevoke
exchange_code
asyncExchanges the code from the Login Kit callback for a TokenBundle with access and refresh tokens. Call it once in your redirect handler.
await tt.exchange_code(
code: str,
*,
redirect_uri: str,
code_verifier: Optional[str] = None,
) -> socialift.models.TokenBundle- The access token is valid for 24 hours, the refresh token for 365 days.
- Pass the same
redirect_uriyou used inlogin_url(); addcode_verifierif you started the flow with PKCE. - The request is sent form-urlencoded, as TikTok's token endpoint requires; if the response has no
access_token,ApiErroris raised.
from_env
classmethodCreates a client with keys from the TIKTOK_CLIENT_KEY and TIKTOK_CLIENT_SECRET environment variables. Extra keyword arguments (scopes, business_id, timeout, license_key, ...) go to the constructor.
TikTok.from_env(**kwargs) -> TikTok- A missing variable becomes an empty string and doesn't fail here; you get
ConfigurationErrorlater, for example whenlogin_url()runs.
Builds the TikTok Login Kit authorization URL to redirect the user to. It is synchronous and makes no network call.
tt.login_url(
*,
redirect_uri: str,
state: Optional[str] = None,
scopes: Optional[list[str]] = None,
code_challenge: Optional[str] = None,
code_challenge_method: Optional[str] = None,
disable_auto_auth: bool = False,
) -> strredirect_urimust match a URI registered for the app: absolutehttps, static (no query string or fragment), up to 512 characters; an app can register up to 10.- Without
scopes=the client's scopes are used (DEFAULT_SCOPESunless you passed your own to the constructor); TikTok expects them comma-separated, which the SDK handles. - Desktop apps must use PKCE: TikTok expects
code_challengeas the hex-encoded SHA-256 of the verifier withcode_challenge_method="S256". The SDK adds neither for you — pass both, then passcode_verifiertoexchange_code(). - Raises
ConfigurationErrorifclient_keyis empty.
url = tt.login_url(redirect_uri="https://siz.uz/cb", state=state)refresh_token
asyncGets a new access token using a stored refresh token. Run it on a schedule, since access tokens last only 24 hours.
await tt.refresh_token(refresh_token: str) -> socialift.models.TokenBundle- TikTok may return a different
refresh_tokenthan the one you sent; always save the new value from the returned bundle. - A refresh token is valid for 365 days.
bundle = await tt.refresh_token(refresh_token)revoke
asyncRevokes the access token so your app loses access to the user's account. Use it when the user clicks disconnect.
await tt.revoke(token: str) -> bool- Returns
Truewhen TikTok accepts the request; delete the stored tokens on your side too.
await tt.revoke(token)Profile and videos
The creator's profile and own videos (Open API v2).
iter_videos
asyncWalks the pages via videos() and collects up to max_items videos into one list. Handy for syncing a creator's library into your database.
await tt.iter_videos(
token: str,
*,
max_items: int = 100,
fields: Optional[str] = None,
) -> list[dict]max_items(default 100) is a safety cap: large accounts have thousands of videos and an unbounded loop runs into rate limits.- Each page is a separate request of at most 20 videos.
Required permissions:video.list
page = await tt.videos(token, limit=20)
all_videos = await tt.iter_videos(token, max_items=100)me
asyncReturns the creator's profile as a Profile: id (open_id), name, avatar, bio, follower, following and video counts. Handy right after login to show who connected.
await tt.me(token: str, *, fields: Optional[str] = None) -> socialift.models.Profile- Scopes per field:
open_id, avatars anddisplay_nameneeduser.info.basic;bio_description,profile_deep_link,is_verifiedneeduser.info.profile;follower_count,following_count,likes_count,video_countneeduser.info.stats. - All of these fields are requested by default; pass a shorter
fieldsstring to request only fields covered by the scopes the user granted. Profile.usernameandfull_nameboth holddisplay_name; TikTok's raw object is inraw.
Required permissions:user.info.basicuser.info.profileuser.info.stats
me = await tt.me(token)video
asyncFetches specific videos by ID, for example to refresh counters or expiring cover URLs of videos you already know.
await tt.video(
video_ids: Iterable[str],
token: str,
*,
fields: Optional[str] = None,
) -> list[dict]- TikTok accepts up to 20 IDs per request; the SDK silently keeps only the first 20.
- Don't rely on the result order matching your IDs; match by
id. - Requires the
video.listscope.
Required permissions:video.list
videos = await tt.video(["7300000000000000000"], token)videos
asyncReturns one page of the creator's own videos as {"data", "cursor", "has_more"}. Use it for paged lists in your UI.
await tt.videos(
token: str,
*,
limit: int = 20,
cursor: Optional[int] = None,
fields: Optional[str] = None,
) -> dict- TikTok returns at most 20 videos per request;
limitis clamped to 1–20. cursoris a UTC Unix timestamp in milliseconds: pass thecursorfrom the previous page to get older videos.- Requires the
video.listscope.
Required permissions:video.list
page = await tt.videos(token, limit=20)
all_videos = await tt.iter_videos(token, max_items=100)Publishing
Content Posting API: direct posts, inbox drafts, photo posts and status tracking.
creator_infopublish_photospublish_statuspublish_videoupload_videowait_publish
creator_info
asyncReturns the creator's current posting options: allowed privacy_level_options, max_video_post_duration_sec, and whether comments, duets and stitches are disabled. Call it before every post and build your post form from it.
await tt.creator_info(token: str) -> dict- TikTok's Direct Post flow expects you to query creator info before initializing a post.
- The
privacy_levelyou send must be one ofprivacy_level_options. - There is no daily quota field; when the creator hits the API's daily post cap, TikTok returns
spam_risk_too_many_posts, which the SDK raises asRateLimitError. - Requires the
video.publishscope.
Required permissions:video.publish
info = await tt.creator_info(token)
print(info["privacy_level_options"])publish_photos
asyncCreates a photo (carousel) post from image URLs, either posted directly or sent to the creator's inbox as a draft with draft=True.
await tt.publish_photos(
token: str,
*,
photo_urls: list[str],
title: Optional[str] = None,
description: Optional[str] = None,
privacy_level: str = 'SELF_ONLY',
cover_index: int = 0,
disable_comment: bool = False,
auto_add_music: bool = True,
draft: bool = False,
wait: bool = False,
brand_content: bool = False,
brand_organic: bool = False,
) -> dict- Up to 35 images, JPEG or WebP, max 20 MB each; only URLs (PULL_FROM_URL) from a verified domain are accepted.
titleis limited to 90 anddescriptionto 4000 UTF-16 characters;cover_indexis zero-based.- Direct posts need the
video.publishscope,draft=True(MEDIA_UPLOAD) needsvideo.upload.auto_add_musicapplies to direct posts only. - Don't combine
draft=Truewithwait=True: the status staysSEND_TO_USER_INBOXuntil the creator posts.
Required permissions:video.publish
result = await tt.publish_photos(token, photo_urls=["https://cdn.siz.uz/1.jpg"], title="Karusel")publish_status
asyncReturns the current processing state of a post by publish_id. Poll it yourself or use wait_publish().
await tt.publish_status(publish_id: str, token: str) -> dictstatusvalues:PROCESSING_UPLOAD(file upload),PROCESSING_DOWNLOAD(URL pull),SEND_TO_USER_INBOX(drafts),PUBLISH_COMPLETE,FAILED(reason infail_reason).- Limited to 30 requests per minute per access token.
publicaly_available_post_id(TikTok's spelling) appears only once the post is public and has passed moderation.
status = await tt.publish_status(publish_id, token)publish_video
asyncPosts a video straight to the creator's profile (Direct Post) from a public URL or from bytes/a file path. Returns {"publish_id"}, plus the final status when wait=True.
await tt.publish_video(
token: str,
*,
video_url: Optional[str] = None,
video: Union[bytes, str, os.PathLike, NoneType] = None,
title: Optional[str] = None,
privacy_level: str = 'SELF_ONLY',
disable_comment: bool = False,
disable_duet: bool = False,
disable_stitch: bool = False,
cover_timestamp_ms: Optional[int] = None,
brand_content: bool = False,
brand_organic: bool = False,
chunk_size: Optional[int] = None,
wait: bool = False,
) -> dict- Until your app passes TikTok's audit, everything it posts is restricted to private viewing, which is why
privacy_leveldefaults toSELF_ONLY. video_url(PULL_FROM_URL) needs a verified domain or URL prefix,httpsand no redirects, otherwise you geturl_ownership_unverified(PermissionError). TikTok stops the download after one hour.- With
video=the SDK uploads in chunks: up to 5 MB in one piece, larger files in 5–64 MB chunks (chunk_size). - Init requests are limited to 6 per minute per access token. Requires the
video.publishscope.
Required permissions:video.publishvideo.upload
result = await tt.publish_video(token, video_url="https://cdn.siz.uz/reel.mp4", title="Salom TikTok!",
privacy_level="SELF_ONLY", wait=True)with open("reel.mp4", "rb") as f:
result = await tt.publish_video(token, video=f.read(), title="Salom!")upload_video
asyncSends a video to the creator's TikTok inbox as a draft instead of posting it; the creator edits and posts it in the app. Use it when the creator should pick the caption and privacy.
await tt.upload_video(
token: str,
*,
video_url: Optional[str] = None,
video: Union[bytes, str, os.PathLike, NoneType] = None,
chunk_size: Optional[int] = None,
wait: bool = False,
) -> dict- Requires the
video.uploadscope; there are no caption or privacy parameters here. - TikTok allows at most 5 pending shares within any 24 hours.
- The status stops at
SEND_TO_USER_INBOXand becomesPUBLISH_COMPLETEonly after the creator posts.wait=Truewaits forPUBLISH_COMPLETE, so it ends withApiErrorunless the creator posts within about 2 minutes. - Source rules match
publish_video():video_urlneeds a verified domain,video=is uploaded in chunks.
wait_publish
asyncPolls publish_status() until the post reaches PUBLISH_COMPLETE and returns that status. publish_video(wait=True) uses it internally.
await tt.wait_publish(
publish_id: str,
token: str,
*,
attempts: int = 40,
delay: float = 3.0,
) -> dict- Defaults: 40 attempts × 3 seconds, about 2 minutes; tune with
attemptsanddelay. FAILEDraisesApiErrorwith thefail_reason; running out of attempts raisesApiErrortoo.SEND_TO_USER_INBOXis not treated as done, so don't use it for inbox drafts.
Business account and comments
TikTok API for Business: account metrics and comment moderation. Needs business_id.
business_accountbusiness_videoscommentsdelete_commenthide_commentlike_commentpin_commentreply_comment
business_account
asyncReturns the business account profile with day-by-day metrics through TikTok API for Business. Use it for account-level analytics.
await tt.business_account(
token: str,
*,
business_id: Optional[str] = None,
fields: Optional[list[str]] = None,
) -> dict- The request goes to
business-api.tiktok.comwith theAccess-Tokenheader — a separate API family from the Open API used byme(); your app needs access to TikTok API for Business. - Needs
business_id(usually the creator'sopen_id): pass it or setbusiness_id=on the client, otherwiseConfigurationError. fieldsis a Python list; the SDK sends it as a JSON array string.
account = await tt.business_account(token)business_videos
asyncLists the business account's videos with per-video metrics through TikTok API for Business, one page at a time.
await tt.business_videos(
token: str,
*,
business_id: Optional[str] = None,
cursor: Optional[int] = None,
max_count: int = 20,
fields: Optional[list[str]] = None,
) -> dict- Pass the
cursorfrom the previous response for the next page;max_countdefaults to 20. - Needs
business_id(argument or client default) and access to TikTok API for Business.
stats = await tt.business_videos(token, max_count=20)delete_comment
asyncDeletes a comment through TikTok API for Business. To take a comment out of view without deleting it, use hide_comment().
await tt.delete_comment(
comment_id: str,
token: str,
*,
business_id: Optional[str] = None,
) -> dict- Needs
business_idand access to TikTok API for Business.
await tt.delete_comment(comment_id, token)hide_comment
asyncHides a comment on the business account's video, or shows it again with hidden=False — a reversible way to moderate.
await tt.hide_comment(
comment_id: str,
token: str,
*,
hidden: bool = True,
business_id: Optional[str] = None,
) -> dict- Sends
actionHIDEorUNHIDE; needsbusiness_id.
await tt.hide_comment(comment_id, token, hidden=True)like_comment
asyncLikes a comment as the business account, or removes the like with liked=False.
await tt.like_comment(
comment_id: str,
token: str,
*,
liked: bool = True,
business_id: Optional[str] = None,
) -> dict- Sends
actionLIKEorUNLIKE; needsbusiness_id.
await tt.like_comment(comment_id, token, liked=True)pin_comment
asyncPins a comment to the top of the video, or unpins it with pinned=False.
await tt.pin_comment(
comment_id: str,
token: str,
*,
pinned: bool = True,
business_id: Optional[str] = None,
) -> dict- Sends
actionTOPorUNTOPtobusiness/comment/top/; needsbusiness_id.
await tt.pin_comment(comment_id, token, pinned=True)reply_comment
asyncPosts a reply from the business account to a comment on one of its videos.
await tt.reply_comment(
comment_id: str,
token: str,
text: str,
*,
video_id: Optional[str] = None,
business_id: Optional[str] = None,
) -> dict- Argument order is
comment_id, token, text;video_idis optional in the SDK, but pass it when you have it. - Needs
business_idand access to TikTok API for Business.
await tt.reply_comment(comment_id, token, "Rahmat!", video_id=video_id)Low-level calls
Direct access to endpoints the SDK doesn't wrap.
business
asyncCalls any TikTok API for Business endpoint with the Access-Token header. Use it for Business API features the SDK doesn't wrap.
await tt.business(
path: str,
token: str,
*,
method: str = 'POST',
json: Optional[dict] = None,
params: Optional[dict] = None,
) -> dictpathis relative tohttps://business-api.tiktok.com/open_api/v1.3/; the trailing/is added automatically.- The default method is
POST; a non-zerocodein the response body raises an error. business_idisn't added for you; put it inparamsorjson.
data = await tt.business("business/get/", token, method="GET", params={"business_id": open_id})call
asyncCalls any Open API v2 endpoint the SDK doesn't wrap yet, with the Authorization: Bearer header and TikTok's error check. Returns the raw JSON body.
await tt.call(
path: str,
token: str,
*,
method: str = 'POST',
json: Optional[dict] = None,
params: Optional[dict] = None,
) -> dictpathis relative tohttps://open.tiktokapis.com/v2/; the trailing/TikTok requires is added for you.- The default method is
POST; query parameters such asfieldsgo inparams, the request body injson. - Argument order is
path, token(inAds.callthe token comes first).
request
asyncThe lowest-level HTTP call: sends a request to a full URL with retries and raises an error even when TikTok returns it inside an HTTP 200. Usually prefer call() or business(), which add the auth header.
await tt.request(method: str, url: str, **kwargs) -> Any- No auth header is added; pass
headersyourself. - An
error.codeother than"ok"(Open API) or a non-zerocode(Business API) raises anApiErrorsubclass. - Retries follow
RetryPolicy(429 and 5xx responses).
Methods shared by all clients
Every HTTP client (Telegram, Facebook, Instagram, LinkedIn, TikTok, Ads) has these low-level methods — for calling an endpoint the SDK doesn't wrap yet or for reading pages of results. The connection is closed automatically when the async with block ends.
request
asyncSends an arbitrary HTTP request and returns the JSON response — for endpoints the SDK doesn't wrap with a dedicated method yet. You pass the token and signing parameters yourself in params or headers (access_token, and for Meta appsecret_proof via socialift.crypto.appsecret_proof).
await tt.request(
method: str,
url: str,
*,
params: Optional[Mapping[str, Any]] = None,
data: Optional[Any] = None,
json: Optional[Any] = None,
headers: Optional[Mapping[str, str]] = None,
) -> AnyNonevalues inparamsare not sent.- Rate limits (
RateLimitError) and transient server errors (HTTP 429, 500, 502, 503, 504) are retried according toRetryPolicy; aRetry-Afterheader is honoured. Network failures are raised asTransportError.
get
asyncShorthand for request("GET", ...).
await tt.get(url: str, **kw) -> Anypost
asyncShorthand for request("POST", ...).
await tt.post(url: str, **kw) -> Anydelete
asyncShorthand for request("DELETE", ...).
await tt.delete(url: str, **kw) -> Anypaginate
asyncFollows Meta's cursor pagination (paging.next) and returns every data item as a single list.
await tt.paginate(
url: str,
*,
params: Optional[Mapping[str, Any]] = None,
max_items: int = 500,
page_size: int = 100,
) -> list[dict]max_items(default 500) is a safety cap — a large account can have tens of thousands of records.page_sizesets thelimitof each request.
close
asyncCloses the HTTP session the SDK created itself (a session you passed in is left open). Called automatically when you use async with Client(...).
await tt.close() -> NoneFrequently asked questions
How long does a TikTok access token last?
An access token is valid for 24 hours and a refresh token for 365 days. Refresh at least once a day with refresh_token(). TikTok may return a different refresh token when you refresh, so store the returned value and use that one next time.
Why is my TikTok video posted via the API visible only to me?
All content posted by apps that haven't passed TikTok's audit is restricted to private viewing. That is why privacy_level defaults to SELF_ONLY in the SDK. After the audit, pick a value from the privacy_level_options list returned by creator_info().
Can I upload a TikTok video from a file instead of a URL?
Yes: pass bytes or a file path to publish_video(token, video=...). The SDK uses FILE_UPLOAD and splits files over 5 MB into 5–64 MB chunks. For video_url (PULL_FROM_URL), the domain or URL prefix must be verified with TikTok, and the URL must be https without redirects.
What is the difference between publish_video and upload_video?
publish_video posts the video straight to the profile (Direct Post, video.publish scope). upload_video sends it to the creator's TikTok inbox as a draft (video.upload scope); the creator edits and posts it in the app. TikTok allows at most 5 pending shares within any 24 hours.
Can I read and reply to TikTok comments through the API?
Yes, the SDK has comments, reply_comment, hide_comment and related methods. They go through TikTok API for Business (business-api.tiktok.com) with the Access-Token header. You need a business_id (usually the creator's open_id) and access to that API for your app.
comments
asyncLists comments on one of the business account's videos;
include_replies=Trueadds replies to top-level comments.dataobject; page withcursor,max_countdefaults to 30.business_idand access to TikTok API for Business.