TikTok Caption Writer

SkillDev tools

Write a TikTok caption under 2,200 chars (hashtags included), pick a tasteful 3 to 5 hashtag set with mixed reach, and set the platformSettings.tiktok flags (viewer setting, comments, duet, stitch, commercial and branded content). Front-loads a reason to comment, runs the humanizer pass, and publishes the rendered video via Publora on approval. Use to caption a finished video. Not for the in-video hook (use tt-hook-scripter) or scrubbing a script (use tt-humanizer).

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the TikTok Caption Writer skill

What this skill tells your AI

The instructions your AI receives, as published by sergebulaev/tiktok-skills in skills/tt-caption-writer/SKILL.md and read by ahel’s review.

Write the caption that sits under the video: a tight caption inside the 2,200 char API limit, a mixed-reach hashtag set, and the platformSettings.tiktok flags that control reach and interaction. On TikTok the caption supports the video, it does not carry it. The hook is in the first 1-3 seconds of the clip (see tt-hook-scripter); the caption's job is to add context and earn a comment.

When to use

  • User has a video (or a script) and needs the caption, hashtags, and settings
  • User is about to upload and wants the posting flags right
  • User wants Claude Code or Codex to schedule a rendered .mp4 via Publora

What this skill produces

  • Caption (<= 2,200 chars on the API; aim much shorter), first line front- loaded with a reason to read or comment
  • Hashtag set (3 to 5, mixed reach), placed at the end
  • platformSettings.tiktok flags with a plain-English summary
  • On approval, the publish call (draft -> upload -> schedule) when a video file is supplied

The TikTok settings (platformSettings.tiktok)

Build these with lib.tiktok_settings(...). Defaults match Publora's.

SettingValues / defaultWhat it does
viewerSettingPUBLIC_TO_EVERYONE (default), MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLYwho can view. Effectively required; an empty value is rejected
allowCommentstrue (default)viewers can comment
allowDuetfalse (default)viewers can Duet
allowStitchfalse (default)viewers can Stitch
commercialContentfalse (default)the video is commercial
brandOrganicfalse (default)promoting your own brand
brandedContentfalse (default)paid partnership / sponsored

Two gotchas you must surface to the user

  1. Boolean inversion bug. Publora currently maps allowComments, allowDuet, allowStitch to TikTok's disable_* flags, so the booleans can land inverted (sending allowDuet: true may disable duets). The status cannot be confirmed from the API alone. Test with a SELF_ONLY draft before trusting these values. Tell the user this whenever they change a flag.
  2. Commercial disclosure. If commercialContent is true, at least one of brandOrganic (your own brand) or brandedContent (paid partnership) must also be true, or Publora rejects it. lib.tiktok_settings enforces this.

Posting reality

  • Unaudited apps post PRIVATE only. Until the publishing app passes TikTok's review, posts are forced to SELF_ONLY regardless of viewerSetting. Warn the user if a "public" post lands private.
  • For reach, use PUBLIC_TO_EVERYONE. For an end-to-end test, use SELF_ONLY.

Steps

Voice profile first (all drafts). If ../../references/voice-profile.md has filled: yes, load it and match the user's voice fingerprint, hard rules, and CTA/link style throughout. If it is not filled, mention once that tt-humanizer --mode profile can learn their voice from a few posts, then proceed with the generic voice rules.

  1. Gather inputs, goal first. Ask (or infer) what the caption should earn, then shape for it: comments -> end on one specific question tied to the video; saves -> promise reference value ("the 3 settings are in the caption"); shares -> a claim people will want to show someone; profile taps -> an open loop the pinned video pays off. Then gather the video topic/script, the niche, whether it is commercial, and whether a rendered .mp4 path exists.
  2. Draft the caption. Front-load the first visible line with a reason to read or a specific question to comment on. Keep it tight. Em dashes under the cap (about one per 100 words; a short caption rarely needs one), no AI vocab cluster. Optionally restate the hook's open loop as a comment prompt.
  3. Pick hashtags. 3 to 5, mixed reach: one broad, one to two niche-defining, one to two specific to the video. Put them at the end. Never stuff.
  4. Set the flags. Default to PUBLIC_TO_EVERYONE, comments on, duet/stitch off, no commercial flags. Adjust per the user, and surface the two gotchas. Build with lib.tiktok_settings(...).
  5. Char check. Confirm caption + hashtags <= 2,200. If over, tighten.
  6. Humanizer pass. Scrub 2026 AI vocab by density, cap em dashes (about one per 100 words, never swap one for a period), break stacked triads, generic openers, reveal bridges and sincerity openers ("not gonna lie"). Leave the rhythm alone; never chop a line for punch. Canonical rules: tt-humanizer V3.
  7. Approval card. Show: caption, char count, hashtag set, settings summary, and the resolved viewerSetting (flag if it may post private).
  8. On approval. Call lib.publish("video", caption, target_url=<upload or profile URL>, video_path=<path or None>, platform_settings=<settings>, scheduled_time=<iso or None>, platforms=[<TIKTOK_PLATFORM_ID>]). With a video path and Publora configured, it runs draft -> upload -> schedule. Without one, it returns the caption and settings to upload in-app.

Hard rules

Global voice rules: see root SKILL.md Voice rules. Additional skill-specific rules:

  • Caption + hashtags must fit 2,200 chars (API), hashtags included. Aim far under.
  • 3 to 5 hashtags, mixed reach, at the end. Never 10+, never mid-sentence.
  • The caption never does the hook's job. The hook is in the video.
  • Always set viewerSetting. Never ship an empty value.
  • Surface the boolean inversion warning whenever an interaction flag changes.

Anti-patterns (skill will refuse)

  • Hashtag walls (10+), or hashtags jammed into the sentence.
  • Em dashes above the cap (more than about one per 100 words), or an em dash swapped for a period.
  • "The result?" reveals and "No X. No Y. Just Z." staccato stacks added for punch.
  • "Follow for part 2 / like and subscribe / don't forget to share" stacks.
  • A caption that repeats the spoken hook word for word.
  • commercialContent: true with neither brand flag set.
  • Claiming a post will be public when the app is unaudited (it will be private).

Resources

  • ../../references/voice-rules.md - caption and hashtag rules
  • ../../references/algorithm-heuristics.md - caption, hashtag, and settings heuristics
  • references/settings-matrix.md - every platformSettings.tiktok value, defaults, and the known bugs
  • lib/publora_client.py - tiktok_settings(...), publish_video(...), create_draft(...)

Optional illustration

Offer a generated image when a visual would lift reach. Draft a prompt and call lib.illustrate(prompt, kind="story"), pulling brand handle/color from Voice & Brand Profile section 6 for a pixel-exact overlay. Show the returned url + cost, for a TikTok photo post, attach the image via media_urls=[url] (TikTok photo carousel); for a video, TikTok's API has no cover-image field, so set the cover in the TikTok app. Full workflow (incl. quote-cards): ../tt-humanizer/sub-skills/illustration.md. No Pixfaro key -> it drafts the prompt for you to generate manually.

Related skills

  • tt-hook-scripter - the in-video hook (the caption does not replace it)
  • tt-humanizer - scrub the caption before publishing
  • tt-content-planner - schedule captions across a posting week

Signals

GitHub stars
30
Forks
4
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
tt-caption-writer
Source
github.com/sergebulaev/tiktok-skills