Skip to main content
Socialift
Documentation sections
SDK reference

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.

python
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

Exchanges 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_uri you used in login_url(); add code_verifier if 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, ApiError is raised.

from_env

classmethod

Creates 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 ConfigurationError later, for example when login_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,
) -> str
  • redirect_uri must match a URI registered for the app: absolute https, 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_SCOPES unless you passed your own to the constructor); TikTok expects them comma-separated, which the SDK handles.
  • Desktop apps must use PKCE: TikTok expects code_challenge as the hex-encoded SHA-256 of the verifier with code_challenge_method="S256". The SDK adds neither for you — pass both, then pass code_verifier to exchange_code().
  • Raises ConfigurationError if client_key is empty.
Example — python
url = tt.login_url(redirect_uri="https://siz.uz/cb", state=state)

Gets 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_token than the one you sent; always save the new value from the returned bundle.
  • A refresh token is valid for 365 days.
Example — python
bundle = await tt.refresh_token(refresh_token)

revoke

async

Revokes 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 True when TikTok accepts the request; delete the stored tokens on your side too.
Example — python
await tt.revoke(token)

Profile and videos

The creator's profile and own videos (Open API v2).

iter_videosmevideovideos

Walks 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

Example — python
page = await tt.videos(token, limit=20)
all_videos = await tt.iter_videos(token, max_items=100)

me

async

Returns 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 and display_name need user.info.basic; bio_description, profile_deep_link, is_verified need user.info.profile; follower_count, following_count, likes_count, video_count need user.info.stats.
  • All of these fields are requested by default; pass a shorter fields string to request only fields covered by the scopes the user granted.
  • Profile.username and full_name both hold display_name; TikTok's raw object is in raw.

Required permissions:user.info.basicuser.info.profileuser.info.stats

Example — python
me = await tt.me(token)

video

async

Fetches 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.list scope.

Required permissions:video.list

Example — python
videos = await tt.video(["7300000000000000000"], token)

videos

async

Returns 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; limit is clamped to 1–20.
  • cursor is a UTC Unix timestamp in milliseconds: pass the cursor from the previous page to get older videos.
  • Requires the video.list scope.

Required permissions:video.list

Example — python
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

Returns 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_level you send must be one of privacy_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 as RateLimitError.
  • Requires the video.publish scope.

Required permissions:video.publish

Example — python
info = await tt.creator_info(token)
print(info["privacy_level_options"])

Creates 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.
  • title is limited to 90 and description to 4000 UTF-16 characters; cover_index is zero-based.
  • Direct posts need the video.publish scope, draft=True (MEDIA_UPLOAD) needs video.upload. auto_add_music applies to direct posts only.
  • Don't combine draft=True with wait=True: the status stays SEND_TO_USER_INBOX until the creator posts.

Required permissions:video.publish

Example — python
result = await tt.publish_photos(token, photo_urls=["https://cdn.siz.uz/1.jpg"], title="Karusel")

Returns 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) -> dict
  • status values: PROCESSING_UPLOAD (file upload), PROCESSING_DOWNLOAD (URL pull), SEND_TO_USER_INBOX (drafts), PUBLISH_COMPLETE, FAILED (reason in fail_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.
Example — python
status = await tt.publish_status(publish_id, token)

Posts 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_level defaults to SELF_ONLY.
  • video_url (PULL_FROM_URL) needs a verified domain or URL prefix, https and no redirects, otherwise you get url_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.publish scope.

Required permissions:video.publishvideo.upload

Example — python
result = await tt.publish_video(token, video_url="https://cdn.siz.uz/reel.mp4", title="Salom TikTok!",
                              privacy_level="SELF_ONLY", wait=True)
Example — python
with open("reel.mp4", "rb") as f:
    result = await tt.publish_video(token, video=f.read(), title="Salom!")

Sends 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.upload scope; 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_INBOX and becomes PUBLISH_COMPLETE only after the creator posts. wait=True waits for PUBLISH_COMPLETE, so it ends with ApiError unless the creator posts within about 2 minutes.
  • Source rules match publish_video(): video_url needs a verified domain, video= is uploaded in chunks.

Polls 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 attempts and delay.
  • FAILED raises ApiError with the fail_reason; running out of attempts raises ApiError too.
  • SEND_TO_USER_INBOX is 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

Returns 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.com with the Access-Token header — a separate API family from the Open API used by me(); your app needs access to TikTok API for Business.
  • Needs business_id (usually the creator's open_id): pass it or set business_id= on the client, otherwise ConfigurationError.
  • fields is a Python list; the SDK sends it as a JSON array string.
Example — python
account = await tt.business_account(token)

Lists 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 cursor from the previous response for the next page; max_count defaults to 20.
  • Needs business_id (argument or client default) and access to TikTok API for Business.
Example — python
stats = await tt.business_videos(token, max_count=20)

Lists comments on one of the business account's videos; include_replies=True adds replies to top-level comments.

await tt.comments(
    video_id: str,
    token: str,
    *,
    business_id: Optional[str] = None,
    cursor: Optional[int] = None,
    max_count: int = 30,
    include_replies: bool = False,
) -> dict
  • Returns the response's data object; page with cursor, max_count defaults to 30.
  • Needs business_id and access to TikTok API for Business.
Example — python
comments = await tt.comments("7300000000000000000", token, max_count=30)

Deletes 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_id and access to TikTok API for Business.
Example — python
await tt.delete_comment(comment_id, token)

Hides 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 action HIDE or UNHIDE; needs business_id.
Example — python
await tt.hide_comment(comment_id, token, hidden=True)

Likes 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 action LIKE or UNLIKE; needs business_id.
Example — python
await tt.like_comment(comment_id, token, liked=True)

Pins 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 action TOP or UNTOP to business/comment/top/; needs business_id.
Example — python
await tt.pin_comment(comment_id, token, pinned=True)

Posts 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_id is optional in the SDK, but pass it when you have it.
  • Needs business_id and access to TikTok API for Business.
Example — python
await tt.reply_comment(comment_id, token, "Rahmat!", video_id=video_id)

Low-level calls

Direct access to endpoints the SDK doesn't wrap.

businesscallrequest

Calls 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,
) -> dict
  • path is relative to https://business-api.tiktok.com/open_api/v1.3/; the trailing / is added automatically.
  • The default method is POST; a non-zero code in the response body raises an error.
  • business_id isn't added for you; put it in params or json.
Example — python
data = await tt.business("business/get/", token, method="GET", params={"business_id": open_id})

call

async

Calls 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,
) -> dict
  • path is relative to https://open.tiktokapis.com/v2/; the trailing / TikTok requires is added for you.
  • The default method is POST; query parameters such as fields go in params, the request body in json.
  • Argument order is path, token (in Ads.call the token comes first).

request

async

The 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 headers yourself.
  • An error.code other than "ok" (Open API) or a non-zero code (Business API) raises an ApiError subclass.
  • 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

async

Sends 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,
) -> Any
  • None values in params are not sent.
  • Rate limits (RateLimitError) and transient server errors (HTTP 429, 500, 502, 503, 504) are retried according to RetryPolicy; a Retry-After header is honoured. Network failures are raised as TransportError.

get

async

Shorthand for request("GET", ...).

await tt.get(url: str, **kw) -> Any

post

async

Shorthand for request("POST", ...).

await tt.post(url: str, **kw) -> Any

delete

async

Shorthand for request("DELETE", ...).

await tt.delete(url: str, **kw) -> Any

Follows 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_size sets the limit of each request.

close

async

Closes 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() -> None

Frequently 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.

Sources

  1. TikTok for Developers — Manage User Access Tokens
  2. TikTok for Developers — Content Posting API: Direct Post
  3. TikTok for Developers — Content Posting API: Upload
  4. TikTok for Developers — Content Posting API: Photo Post
  5. TikTok for Developers — Media Transfer Guide
  6. TikTok for Developers — Get Post Status

Related pages