Skip to main content
Before you start. Every TikTok post currently goes to the creator’s upload inbox as a draft: the video uploads, TikTok notifies the creator, and they confirm the publish in the TikTok app. letmepost resolves your requested privacy to self_only and reports that back, but does not send a privacy level upstream at all — the upload-inbox request has no such field, and the creator picks one when they confirm.TikTok’s Direct Post (video.publish) App Review has cleared, but Direct Post is a different endpoint (/v2/post/publish/video/init/) and is not wired up yet, so public posting still requires that confirmation tap. Passing review does not change this on its own.

Quick reference

Connect an account

POST /v1/accounts/connect/tiktok. OAuth 2.0 with PKCE. After the callback the provider:
  1. Calls GET /v2/user/info/ to pin platformAccountId to the real TikTok open_id.
  2. Calls POST /v2/post/publish/creator_info/query/ to snapshot the privacy allowlist; accounts with only SELF_ONLY are flagged as auditState: "audit" and the publisher forces SELF_ONLY on every post until that flips.

Scopes

Extended (not requested by default): video.publish (Direct Post), video.list.

Token lifecycle

24-hour access tokens; 365-day refresh tokens that roll on every use. The provider refreshes 1 hour before expiry; subscribe to token.expiring to know when a re-auth window is coming.

Post a video

tiktok-video-post.json

Privacy

privacy accepts three values: public_to_everyone, mutual_follow_friend, self_only. All three are currently rewritten to SELF_ONLY, whatever the account’s audit state, because posts route through the upload inbox and that endpoint takes no privacy level. letmepost attaches a tiktok.audit.self_only warning every time it rewrites your intent, so a request for a public post is never silently downgraded. The value becomes meaningful once Direct Post is wired up.

Branded content

brandContentToggle: true flags the post as a paid partnership. brandOrganicToggle: true flags it as content for your own brand. TikTok rejects requests that set both — preflight catches it with tiktok.branded_content.mutual_exclusive.

Code samples

How publishing works under the hood

TikTok’s Content Posting API is asynchronous. The publisher:
  1. Calls POST /v2/post/publish/inbox/video/init/ with source: "FILE_UPLOAD", video_size, chunk_size, total_chunk_count. Returns upload_url + publish_id.
  2. PUTs the video bytes to upload_url. Files under 64 MiB go in one request; larger files chunk at 10 MiB with explicit Content-Range headers.
  3. Returns status: "publishing" with the publish_id stamped on cid so the caller can correlate by id.
  4. A BullMQ worker polls POST /v2/post/publish/status/fetch/ on a bucketed schedule (5 s → 30 s → 2 min) until TikTok reaches a terminal state.
  5. Terminal PUBLISH_COMPLETE → post.published webhook with the public TikTok URL. Terminal FAILED → post.failed with the upstream reason. The 30-minute deadline turns into post.failed with tiktok.publish.pending.
The SEND_TO_USER_INBOX state is also treated as terminal-published — TikTok’s upload-inbox flow IS the documented end-state for audit-mode posts. The user confirms publish in their TikTok app; we have no API signal for that final tap.

Wisdom (platform-specific things that bite)

  • Every post is rewritten to SELF_ONLY. The upload-inbox endpoint accepts no privacy level, so whatever you pass is rewritten and a tiktok.audit.self_only warning fires. This is true on audited and unaudited accounts alike — the constraint is the endpoint, not the account.
  • Direct Post is approved but not implemented. TikTok’s video.publish review has cleared, but letmepost still posts through the inbox endpoint and does not yet request the video.publish scope. Posts land for manual confirmation. There’s no API signal for the user tapping publish, so the worker treats SEND_TO_USER_INBOX as terminal-published.
  • ffprobe is needed for duration / resolution preflight. Without ffprobe installed locally, letmepost emits a tiktok.video.probe_unavailable warning and lets the upload proceed; TikTok’s own preflight will catch resolution issues at publish time.
  • Aspect ratio 9:16 is strongly preferred. 1:1 and 16:9 work but emit tiktok.video.aspect_non_vertical. Anything else trips tiktok.video.aspect_unusual.
  • Refresh tokens roll on every use. TikTok issues a new refresh_token on each refresh; letmepost stores it. If you bypass the refresh worker and call the upstream endpoint yourself, persist the new refresh token — discarding it silently shortens the next refresh window.
  • push_by_file only. pull_by_url requires media-domain verification on developer.tiktokapis.com which letmepost has not completed; v1 uploads always go through the chunked PUT path.

Common errors

What you can’t do (yet)

  • Photo carousels (unaudited apps).
  • Direct Post (public publishing without user confirmation). The scope is approved; the endpoint is not implemented yet.
  • pull_by_url uploads (domain verification gate).
  • Reading post engagement, comments, or analytics.
  • TikTok Live, Shop, DMs.

API reference