Install
openclaw skills install @social-media-skills/tiktok-skillTikTok skill: make, check and post TikTok videos and photo posts from your AI agent, for one account or many. Takes your own video (or photos, or just an idea for text slides) and turns it into a finished TikTok post: 9:16 fit, hook text in TikTok's own style, a cover, a caption that TikTok search reads. Checks every file and caption against TikTok's API limits, fixes frame rate, codecs, shape and PNG, flags length per account and over 5 hashtags, shows each post as the For You feed shows it with TikTok's required posting screen (privacy with no default, interactions, disclosure, AI label), then posts or schedules with WoopSocial (TikTok MCP, no developer app or audit needed) after a yes, and learns from the account's own numbers. Use when someone says "post to TikTok", "schedule TikTok posts", "TikTok MCP", "Claude TikTok", "post this video to TikTok", "TikTok photo post", "multiple TikTok accounts" or "what worked on my TikTok". Never invents results, never posts without a yes. Standalone.
openclaw skills install @social-media-skills/tiktok-skillMost TikTok help from agents stops at words: hooks, captions, hashtags, "now go film it". Then the post fails on things nobody checked: a phone screen recording TikTok rejects for its frame rate, a clip longer than the account may post, a PNG in a photo post, hook text under the caption, seven hashtags, a Paid partnership set to "only me", the same file on three accounts.
This skill does the whole job on the user's own computer: it picks the post type, writes the hook and caption, finishes the media, checks every file against TikTok's current limits, shows the user each post as TikTok will show it together with the posting screen TikTok requires, posts or schedules through WoopSocial after a yes, and reads the numbers back. Everything it needs is in this folder.
Five commitments:
Run python3 scripts/check_setup.py (Windows: py scripts\check_setup.py). It needs Python 3.9+
and Pillow; ffmpeg for video. It prints the install command for whatever is missing.
If the user wants to post, connect now so it's ready when the posts are:
social_accounts_list. If the TikTok accounts are there, carry on.references/woopsocial.md).social_accounts_get_platform_inputs: its privacy
options, whether comments, duets and stitches are allowed, its longest video. Put them in
accounts in the plan (references/plan-json.md).Read brand.md if the workspace has one. Otherwise ask in one message, then save brand.md:
A brand-new account: first posts often get almost no views, whatever the quality. Use the account in the app first (follow, watch and comment in the niche) and judge it after a couple of weeks.
Then look at the account before planning: ask for a screenshot of the profile grid and the last 9 to 12 posts (topic, format, what did well), so the batch doesn't repeat them.
Say in one line why each post gets its type and path.
| Goal | Post type | Why |
|---|---|---|
| Show something moving, a person, a process | Video | Watch time carries videos to non-followers |
| A list, steps, before/after, a story in frames | Photo post (1 to 35 photos) | People swipe at their own pace and save it |
| One strong statement or number | Photo post with 1 to 3 slides | Fast to read, easy to share |
| Path | When |
|---|---|
Direct post (postMode: DIRECT_POST) | The post is finished as it is, with its own sound or no music |
Inbox draft (settings.draft: true, MEDIA_UPLOAD) | It needs a trending sound or a final touch in the app. The API can't add sounds. At most 5 drafts wait per account |
A batch carries one idea per post; never lead two posts with the same fact. More than 1 to 3 posts a day per account rarely helps; TikTok caps API posts at about 15 a day.
Most viewers decide in the first second or two (practitioner consensus; TikTok publishes no number). Write the hook once and put it in three places:
video.py hook (TikTok Sans, TikTok's classic outlined style, or the highlight style when the
background is bright; first 3 seconds, upper middle, clear of the buttons and caption). For
points across the whole video, video.py text --beats beats.json.Craft notes and patterns: references/writing.md. Never promise what the video doesn't show.
Make a working folder per batch with plan.json (references/plan-json.md).
The user's own video.
python3 scripts/tt_check.py plan.json --fix # 9:16, H.264/AAC, constant fps
python3 scripts/video.py hook fixed/<file>.mp4 --text "Only 2.5% of Earth's water is fresh" --out video/hooked.mp4
python3 scripts/video.py frames video/hooked.mp4 --out video/ # cover candidates + frames-sheet.jpg
python3 scripts/video.py cover video/hooked.mp4 --at 0.3 --out video/cover.jpg
python3 scripts/video.py zones video/hooked.mp4 --out video/zones.jpg
Several shots of the user's into one video, each cropped where its subject is, then points on screen across it:
python3 scripts/video.py montage clips.json --out video/montage.mp4 # {"clips": [{"file", "start", "end", "x"}]}
python3 scripts/video.py text video/montage.mp4 --beats beats.json --out video/texted.mp4
--fit crop when the subject stays in the middle, or, for the user's own footage
only, a framed video ("frame": {"title": "..."} and "own_footage": true).zones.jpg shades what TikTok's interface covers. Text or faces in the red areas sit under the
buttons or the caption: move them.frames-sheet.jpg, the hook readable.montage + text),
and never its original narration as the only audio. Credit the source in the caption. Mark such
posts "source": "third_party" in the plan so tt_check.py can warn.Photo posts. Use the user's photos, or render text slides in TikTok's native look:
python3 scripts/render_slides.py slides.json --out slides/ --no-instagram
The renderer's spec is at the top of the script: TikTok Sans, TikTok's classic, highlight and
plain text styles, text kept inside the safe area, 1080x1920 JPEG. One idea per slide, the first
slide is the hook, the last one asks for one thing. tt_check.py --fix converts and fits photos.
targets[].media, targets[].caption). Write for the account's
country.python3 scripts/tt_check.py plan.json # report
python3 scripts/tt_check.py plan.json --fix # fixed copies in fixed/, plan updated, originals kept
It checks container, codec, 8-bit, HDR, frame rate (and variable frame rate), resolution, length
against each account's maximum, size, photo format, size and count, caption length, hashtags,
privacy (chosen by the user and recorded with "privacy_from": "user", allowed for the account,
never private for branded content), interactions the
account doesn't allow, the AI label on photo posts, the same file on several accounts (by content),
two posts teaching the same fact to one account within a week (hook, on-screen beats, caption,
slide text), one third-party clip with its own audio, drafts and posts per day. Run it until it exits with 0, and explain anything it can't fix in plain words.
python3 scripts/schedule.py plan.json --start 2026-11-16 # times and time zones from accounts in plan.json
Each account posts at its own times in its own timezone. Without them it uses 12:00 and says
so. It writes schedule.md (with the first-hour plan) and one WoopSocial request per post and
account in requests/. Privacy is never filled in for the user: a missing choice stays a
placeholder, and validation refuses it. Inbox drafts are sent 2 hours before their time, so the
user can finish them on time; schedule.md lists each draft with its caption to paste.
python3 scripts/preview.py plan.json --out preview/
Look at preview/preview.png yourself first: every post on every account as the For You feed
shows it (buttons, caption, sound line). Then give the user preview/index.html, which adds each
account's grid and the posting panel TikTok requires: the account, privacy (no default),
comments/duets/stitches, Promotional content / Paid partnership, the AI label, music, and the
line "By posting, you agree to TikTok's Music Usage Confirmation" (with Paid partnership:
"...TikTok's Branded Content Policy and Music Usage Confirmation"). Ask for one clear yes for
the batch. Any change means a new check and a new preview.
Details: references/woopsocial.md.
media_uploads_create_session, scripts/upload_parts.py,
media_uploads_complete_session), one at a time. Put the IDs in mediaIds and coverId, then
python3 scripts/schedule.py plan.json --bodies-only.posts_validate every request. Fix anything it rejects.posts_create per request, write each post ID into the target's postId,
posts_get for the status (TikTok processing can take a few minutes), then
python3 scripts/log.py record plan.json.Not connected and doesn't want to connect? Hand over the files, captions, settings and schedule, and stop. Never say something was posted when it wasn't.
After 3 to 7 days: social_account_posts_list for the accounts, saved as metrics.json, then
python3 scripts/log.py results --metrics metrics.json [--csv insights.csv]
It measures each post at the value collected nearest day 7, saves that in posts.jsonl, and ranks
posts against the same account's posts of the same type: views first (on TikTok, views are how far
TikTok pushed the post), then retention for videos, then shares, saves and follows per 1,000 views.
It names a best post only after 6 of a type. WoopSocial returns views, likes, comments and shares,
and saves when TikTok reports them; whatever is missing (often saves and follows), average watch
time and "watched full video" come from TikTok Studio into insights.csv. Then:
| Views vs your median | Retention (watch time / length) | Next |
|---|---|---|
| High | High | Same format, new topic; make the part 2 |
| Low | High | Good video, weak packaging: new first frame, hook text, cover, caption first line |
| High | Low | The hook works, the middle doesn't: cut it shorter, pay off sooner |
| Low | Low | Change the first 2 seconds first, then the idea |
Photo posts: views against the median, then shares and saves per 1,000 views.
"High" means against the account's own posts. Never estimate a number that wasn't reported.
Before showing anything: would a top creator in this niche post this? Reject and fix it if:
brandedContent, public privacy only; the consent line names the Branded
Content Policy.posts_delete) and create it
again, after confirming with the user.references/tiktok-specs.md: limits, posting rules, sound, AI label, interface, search, with
sources and dates.references/writing.md: hooks, on-screen text, captions, photo-post structure.references/plan-json.md: the plan file every script reads.references/woopsocial.md: connect, read account limits, upload, validate, post, results.