| name | tiktok-content-posting |
| description | Upload videos and photo posts to the user's own authorized TikTok account through TikTok's official Content Posting API, with draft upload for in-app editing, Direct Post, exact preview, explicit confirmation, creator-capability checks, commercial and AI-generated disclosures, and status verification. Use whenever someone asks to post, publish, upload, or send a local video, hosted video, or photo carousel to TikTok; create a TikTok draft; check a publish_id; or diagnose Content Posting API authentication, scope, privacy, upload, processing, moderation, or posting failures. Not for trend research, content ideation alone, downloading videos, comments, likes, follows, browser automation, or multi-account engagement.
|
Post content to TikTok
Use the bundled CLI to transfer video or photo content through TikTok's official API. Default to a
draft that the user finishes in TikTok. Treat both draft upload and Direct Post as external writes:
show the exact plan first and require a separate confirmation before sending any media.
Run commands from this skill directory:
node scripts/tiktok-post.mjs <command>
Keep these boundaries
- Operate one TikTok account that the user has explicitly authorized. Never add account rotation,
proxy rotation, cookie reuse, or browser automation.
- Use only media the user owns or is authorized to publish. Do not copy arbitrary content from
another platform.
- Never put an access token, refresh token, client secret, or complete upload URL in a command,
response, audit record, or committed file. Follow setup.md for local
credentials.
- Never pass
--yes until the user has seen the current media and exact posting plan and has
expressly approved that plan. Execution also requires the --confirm-plan ID emitted by that
dry-run. A previous general request to "post this" does not replace post-preview confirmation.
- Do not silently fall back to a web uploader when an API scope, app review, or account capability
blocks the requested operation. Explain the blocker and offer a manual handoff.
Verify the installed contract without TikTok
From this skill directory, run the fixed tokenless evaluation below. It needs no TikTok account,
token, or media file, and it must make no network request or filesystem write:
env -u TIKTOK_ACCESS_TOKEN node scripts/tiktok-post.mjs photos \
https://media.example.invalid/launch-1.webp \
https://media.example.invalid/launch-2.webp \
--expected-open-id EVAL_ONLY_NOT_A_REAL_ACCOUNT \
--title 'Launch notes' \
--description 'Two-frame product update.' \
--dry-run
Require dry_run: true, executed: false, published: false, mode: "draft", and
media.type: "remote_photos" with media.count: 2. Require account_info, url_reachability,
url_redirects, and remote_media_specs to be "unchecked"; creator_info must be
"not_required", and url_ownership must be "unchecked_by_cli". Stop there: never add --yes
or --confirm-plan to this evaluation. If an independent installed-copy run completes, use the
structured tester form
only when the reporter independently installed and actually ran tiktok-content-posting for their
own non-internal purpose and is not a maintainer, a maintainer's teammate, or an internal tester.
Use the form's incomplete outcome for install-only or failed attempts, and remove tokens, account
identifiers, private URLs, media, and personal paths. This proves only the packaged dry-run
contract; it does not prove TikTok authorization, upload, or publication.
Choose the path
| intent | mode | result |
|---|
| Let the user add music, effects, or edits in TikTok | draft (default) | Sends media to the TikTok inbox; the user finishes and publishes it in TikTok |
| Publish with metadata already finalized | direct | Asks TikTok to publish after processing and moderation |
Draft upload requires an approved and authorized video.upload scope. Direct Post requires
video.publish; draft identity verification also requires user.info.basic. Public visibility
additionally depends on TikTok's audit of the user's API client.
An unaudited client is restricted to a private account and SELF_ONLY visibility. Read
policy-and-evidence.md before promising public Direct Post.
Use a local file for a video that is on the user's device. Use a verified HTTPS URL for media that
already lives on a server. Photo posts accept verified HTTPS URLs, not local file upload. See
api-contract.md for formats, limits, endpoints, and status values.
Follow the posting workflow
1. Diagnose the setup
Run doctor before the first transfer and whenever authentication or configuration changes:
node scripts/tiktok-post.mjs doctor
doctor checks the local Node runtime, ffprobe availability, and whether a token is configured; it
does not inspect remote app approval or granted scopes. Use setup.md, then the
read-only account call for a draft or creator call for Direct Post, to verify the destination.
Resolve token expiry and URL-ownership errors from structured API responses. Do not ask the user to
paste secrets into chat.
2. Inspect the target creator
Before a draft, resolve the account bound to the current access token:
node scripts/tiktok-post.mjs account
Show its display_name and copy the exact open_id into --expected-open-id. Execution calls the
official user-info endpoint again and stops before init if the token now belongs to another account.
Before Direct Post, fetch the latest creator capabilities:
node scripts/tiktok-post.mjs creator
Show the returned username and nickname so the user can verify the destination account. Copy the
exact creator_username into --expected-creator; execution queries creator info again and stops
if the token resolves to a different account. Accept only a privacy value currently returned for
that creator. Respect disabled comment, Duet, and Stitch settings, and reject a video longer than
the creator's current maximum duration.
3. Build a dry-run plan
Run video or photos without --yes. The default mode is draft:
node scripts/tiktok-post.mjs video ./launch.mp4 \
--expected-open-id '<open_id>'
node scripts/tiktok-post.mjs photos https://media.example.com/1.jpg https://media.example.com/2.jpg \
--expected-open-id '<open_id>'
A dry run performs only local validation: it does not read the token, make network requests,
initialize a post, transfer media, or create an inbox draft. Run the separate read-only account
or creator command first, then present the plan together with a viewable preview of the local video
or every photo. Let the user edit supplied text and hashtags before proceeding.
--expected-open-id is required only for draft mode and must come from the latest account result.
It is a stable app-scoped account identifier, not a secret or access token.
For Direct Post, require all of the following on every plan:
node scripts/tiktok-post.mjs video ./launch.mp4 \
--mode direct \
--privacy SELF_ONLY \
--commercial none \
--expected-creator '<creator_username>'
--privacy must be an explicit choice from the latest creator response; never invent or default
it.
--commercial must explicitly declare whether the post is non-commercial (none), promotes the
creator's own brand (own), promotes a third party (paid), or both (both). Reject incompatible
branded-content/privacy combinations.
--expected-creator must be the username from the latest creator result; never infer it from a
stale profile or a media filename.
- Do not put
--music-usage-confirmed or --branded-content-policy-confirmed on the dry-run. The
output identifies the declarations that must be shown; add each consent flag only after the user
accepts the corresponding declaration.
- Add
--aigc when the video is AI-generated or significantly AI-edited so TikTok can apply the
creator disclosure label. Do not use that flag merely because an AI agent helped write a caption.
- For a remote video, every remote photo, or a local video that ffprobe cannot inspect, verify the
applicable format, dimensions, size, frame-rate, and duration constraints outside the CLI and add
--media-specs-confirmed to both dry-run and execution. Never use it merely to bypass a failed
media check.
Do not send commercial disclosure, Direct Post interaction, or AIGC flags to a draft endpoint that
does not support them. Tell the user to finish those choices in TikTok instead.
4. Obtain explicit approval
Summarize the plan without changing it:
- destination display name plus bound
open_id for a draft, or creator identity for Direct Post;
- media source, SHA-256 for a local video, count, size, duration, and cover choice when applicable;
draft or direct outcome;
- caption or description;
- privacy and interaction settings for Direct Post;
- commercial and AIGC disclosures;
- each declaration listed by the plan and the visible
Promotional content or Paid partnership
label when present;
- the fact that processing or moderation can delay visibility.
Ask the user to confirm this exact plan and retain its confirmation_id. If metadata, destination
account, local file contents, or media URL changes, generate and show a new dry run. Remote URL contents
cannot be cryptographically bound, so call out that limitation.
5. Execute the same plan
After confirmation, preserve all content-bearing options and add the emitted plan ID plus execution
flags:
node scripts/tiktok-post.mjs video ./launch.mp4 \
--expected-open-id '<open_id>' \
--confirm-plan '<confirmation_id>' \
--yes
node scripts/tiktok-post.mjs video ./launch.mp4 \
--mode direct \
--privacy SELF_ONLY \
--commercial none \
--expected-creator '<creator_username>' \
--music-usage-confirmed \
--confirm-plan '<confirmation_id>' \
--yes
For --commercial paid or both, also add --branded-content-policy-confirmed after the user
accepts that policy. The plan ID binds the API payload, expected draft open_id or Direct Post
creator, remote URLs, and local file SHA-256. The CLI re-checks the destination account and re-hashes
and re-opens the same local file before init; either identity or media changes stop before a write.
Return the redacted result and publish_id. Do not report a successful HTTP response or completed
byte upload as a published TikTok post.
6. Verify the outcome
Poll by the returned identifier:
node scripts/tiktok-post.mjs status '<publish_id>'
Interpret the state by mode:
SEND_TO_USER_INBOX: draft delivery succeeded; explicitly tell the user it is not published
and must be completed in TikTok.
PROCESSING_UPLOAD or PROCESSING_DOWNLOAD: keep the publish_id and check later without
creating another post.
PUBLISH_COMPLETE: the Direct Post completed, or the user completed an inbox draft in TikTok.
Public post IDs can remain unavailable until moderation finishes.
FAILED: report the structured failure reason and the safe corrective action.
If a request times out after initialization, use its returned publish_id and status before any
retry. Never blindly initialize the same content again; TikTok does not provide a general
idempotency key and a retry can create a duplicate.
Troubleshoot without bypassing controls
| symptom | action |
|---|
scope_not_authorized | Reauthorize user.info.basic + video.upload for a draft, or video.publish for Direct Post; do not switch transports |
access_token_invalid | Refresh or repeat OAuth through the configured app; never request a token in chat |
| draft account mismatch | Run account again, show the new destination, and obtain approval for a new plan |
privacy_level_option_mismatch | Run creator again and ask the user to choose from the current options |
| unaudited client rejected | Use the allowed private-account/SELF_ONLY test path or use draft mode; do not claim public publishing |
| URL ownership rejected | Use a URL under a verified property or a local video with the file-upload path |
| pending-share or posting cap | Wait for the stated window or finish existing drafts; do not rotate accounts |
| upload URL expired or transfer interrupted | Check status and uploaded bytes first, then follow the API's safe retry rules |
Read setup.md for app and OAuth preparation,
api-contract.md for the wire contract, and
policy-and-evidence.md for dated platform constraints.