Operating doctrine for TikTok account automation — careful, value-adding creator pattern with role separation, business-suite based DM/comment ops, human-lik...
---
name: tiktok-account-operations
description: Operating doctrine for TikTok account automation — careful, value-adding creator pattern with role separation, business-suite based DM/comment ops, human-like UI flow (cliclick + nested reply textbox), strict anti-shadowban quotas, and recovery patterns. Use this for any scheduled TikTok activity (cron, agent, recurring task) where account safety, FYP eligibility and long-term reach matter more than raw output.
---
# TikTok Account Operations
This skill is the operating doctrine for every TikTok automation run on a brand, professional or personal account.
**The goal is not to comment fast. The goal is to operate TikTok like a careful human contributor: stable browser, correct context, useful action, no shadowban risk, no FYP throttling.**
Drop-in for any niche (legal, medical, software, finance, creator, ecommerce). Replace the placeholders in section 0 with your own values.
---
## 0. Configure for your brand
Before running anything, fill these placeholders in your local copy or your agent's memory:
| Placeholder | Example | Your value |
|---|---|---|
| `<BRAND_NAME>` | "Acme Studio" | — |
| `<BRAND_DOMAIN>` | "acme.studio" | — |
| `<TIKTOK_HANDLE>` | "@acmestudio" | — |
| `<TIKTOK_BUSINESS_ACCOUNT>` | yes/no (Business Suite requires Business or Creator account) | — |
| `<BROWSER_PROFILE>` | "tiktok-live" | — |
| `<BROWSER_PORT>` | "18801" | — |
| `<NICHE_KEYWORDS>` | "lost license OR speeding ticket" | — |
| `<PRIMARY_CTA>` | "WhatsApp / form / app link — pick ONE" | — |
| `<WORKSPACE_DIR>` | "~/.openclaw/workspace/tiktok-<brand>" | — |
All shell snippets below assume an [OpenClaw](https://openclaw.ai) browser CLI bound by CDP, but the doctrine works with any browser-automation stack (Playwright, Puppeteer, Chrome MCP). Swap the CLI calls for your own.
### Quick config (copy-paste YAML)
If your agent reads config from YAML, drop this in `<WORKSPACE_DIR>/config.yaml`:
```yaml
brand:
name: <BRAND_NAME>
domain: <BRAND_DOMAIN>
tiktok:
handle: <TIKTOK_HANDLE> # e.g. "@acmestudio"
business_account: true # required for /business-suite/ UI
browser_profile: <BROWSER_PROFILE> # e.g. "tiktok-live"
browser_port: <BROWSER_PORT> # e.g. 18801
discovery:
niche_keywords: <NICHE_KEYWORDS>
cta:
primary: <PRIMARY_CTA> # one channel only — e.g. "wa.me/<digits>"
workspace:
dir: <WORKSPACE_DIR> # e.g. "~/.openclaw/workspace/tiktok-acme"
alerts:
channel: telegram | slack | discord
webhook: <YOUR_WEBHOOK_URL>
schedule:
timezone: Europe/Paris # everything else is relative to this
windows:
dm_check: "0,15,30,45 9-22 * * *" # every 15 min, 9am-10pm
comment_check: "3,18,33,48 9-22 * * *" # offset by 3 min so they never overlap
browser_health: "0 * * * *" # hourly
daily_recap: "21:00"
```
### Compatibility
The skill is markdown — it works wherever an agent reads `SKILL.md`:
| Stack | Skill install path |
|---|---|
| [Claude Code](https://claude.ai/code) | `~/.claude/skills/tiktok-account-operations/` |
| [OpenClaw](https://openclaw.ai) | `~/.openclaw/skills/tiktok-account-operations/` |
| ClawHub-published | one-click install via [clawhub.ai](https://clawhub.ai) |
| Cursor / Copilot CLI | drop `SKILL.md` into your project's `.cursorrules` or `AGENTS.md` |
| Any LLM agent reading markdown rules | concatenate `SKILL.md` into your system prompt |
---
## 1. Browser architecture
### Physical profile
A single browser profile per account, attached to TikTok's Business Suite:
- `<BROWSER_PROFILE>` (CDP direct, attached to `http://127.0.0.1:<BROWSER_PORT>`)
User data dir: `~/.openclaw-browser-profiles/<BROWSER_PROFILE>`.
Use it through the OpenClaw browser CLI:
```bash
openclaw browser --browser-profile <BROWSER_PROFILE> status
openclaw browser --browser-profile <BROWSER_PROFILE> tabs
openclaw browser --browser-profile <BROWSER_PROFILE> navigate https://www.tiktok.com/business-suite/messages
openclaw browser --browser-profile <BROWSER_PROFILE> snapshot --limit 160
openclaw browser --browser-profile <BROWSER_PROFILE> click <ref>
openclaw browser --browser-profile <BROWSER_PROFILE> evaluate --fn "<async function>"
```
### The Business Suite is the canonical surface
Most TikTok operational pages (Messages, Comments, Analytics, Posts) live under:
- `https://www.tiktok.com/business-suite/messages`
- `https://www.tiktok.com/business-suite/comments`
- `https://www.tiktok.com/business-suite/analytics`
- `https://www.tiktok.com/business-suite/posts`
Critical UI fact: **`/business-suite/messages` and `/business-suite/comments` render their content inside iframes**, not in the main DOM. Many `document.querySelector(...)` calls will silently miss the actual content unless you traverse the right iframe. See §9.
### Roles (mental separation, single physical profile)
#### Role: `tk-post`
Account cockpit. Use for direct account action.
Use for:
- DM replies in `/business-suite/messages`
- Comment replies in `/business-suite/comments` (nested reply only — see §9)
- Original video uploads (separate publishing flow, usually via mobile or Creator Studio)
- Profile checks
- Analytics snapshots
Default page: `https://www.tiktok.com/business-suite/messages`
Mental model: act as the account. Protect it. Do not clutter it.
#### Role: `tk-engage`
Search and discovery radar. Use to find what deserves action.
Use for:
- Keyword searches across the public feed
- Monitoring `#niche` hashtags
- Inspecting a candidate creator's recent posts before replying
- Trend / sound discovery
Default page: `https://www.tiktok.com/search?q=<NICHE_KEYWORDS_URL_ENCODED>&t=video`
Mental model: discover, qualify, decide. Do not act impulsively from discovery.
#### Role: `tk-stealth`
Quiet maintenance bay. Use for low-noise maintenance.
Use for:
- Saving / liking videos for inspiration
- Following peer creators (cap follows — see §7)
- Quiet profile observation
- Sound library curation
Default page: `https://www.tiktok.com/foryou`
Mental model: maintain quietly. No noisy engagement.
### Operational law
- `tk-post` = act as the account
- `tk-engage` = discover what to react to
- `tk-stealth` = maintain quietly
- Stability matters more than speed
- After every run, navigate back to the role's default page (resting state)
Never collapse all workflows into chaotic browsing.
---
## 2. Session check (run first on every cron)
```bash
openclaw browser --browser-profile <BROWSER_PROFILE> status
openclaw browser --browser-profile <BROWSER_PROFILE> evaluate --fn "async () => { try { const r = await fetch('https://www.tiktok.com/api/user/detail/self/', {credentials:'include'}); const d = await r.json(); return {ok: d.statusCode === 0, uniqueId: d.userInfo?.user?.uniqueId || null}; } catch(e) { return {ok:false, error:String(e)}; } }"
```
- If `status` is `stopped`: report the blockage in your alert channel and stop. Never relaunch Chrome from inside a cron — that's a user-side action.
- If `ok` is `false` or `uniqueId` is `null`: login expired. Stop and report.
For Business Suite specifically, navigate to `/business-suite/messages` after the API check — if the page redirects to the public web feed or to a login screen, the Business account session is expired even though the API succeeded.
---
## 3. Phase gating (algorithm trust)
TikTok has no karma counter, but it has a strong **trust gradient**: brand-new or recently-warned accounts get throttled on the FYP and on DM volume. Run in two phases:
- **Phase A** (account < 30 days OR last 14 days had ≥ 1 "Community Guidelines" notice): zero brand-mentioning DMs and zero outbound comment replies. The account is allowed to post videos and react to incoming DMs only.
- **Phase B** (account ≥ 30 days, no recent warning, ≥ 500 followers): all crons authorized.
Always read `<WORKSPACE_DIR>/memory/tiktok-state.md` at start (last line = current phase) AND verify via `/business-suite/posts` (looks for a "limited" banner near the post list).
A Daily Metrics Recap cron appends a snapshot every night and pings your alert channel explicitly when the account crosses Phase A → Phase B.
### Manual override (advanced)
You can force Phase B before the 30-day threshold by appending a line `YYYY-MM-DD - phase=B (manual override)` to `tiktok-state.md`. The override line takes priority over the threshold. Use when:
- You have an established brand on other platforms and an audited account history (imported followers, verified domain) and want immediate brand-aware posting.
- You accept the risk of harder shadowban triggers in the first 30 days. The auto-freeze on 2 successive comment removals (see §11) still applies.
Document the decision in `tiktok-learnings.md`.
---
## 4. Qualification of an inbound DM or comment
A DM or comment is repliable only if **all** of:
- The conversation thread has at least one message from the user (not a pure share/forward).
- The message is in a language you can answer in (otherwise: skip, do not auto-translate).
- The message is a real question or qualified intent in your domain (not a vent, not a meme, not a request for free expert-grade work).
- Phase B authorized for brand-mentioning replies (see §3).
- The same user has not been sent a templated reply in the last 7 days (`tiktok-reply-log.md`).
- Not a competitor or a troll on first read of the user's recent posts.
If any check fails: skip. Document why in the recap.
### Read the full context before answering
For DMs: read **the entire conversation** (`[data-e2e="chat-item"]`), not just the latest message. People often add critical context across 2-3 messages.
For comments: read the parent video caption AND the comment thread up to the target comment. A reply that ignores the obvious context reads as a bot.
---
## 5. Reply templates
### Phase A (warming, NO brand mention)
- 1-3 sentences, conversational, genuinely helpful.
- Match the comment / DM tone.
- Never expert-grade advice (medical, legal, financial).
- Never link to anything.
- Never sign with anything that hints at a profession.
Example structure (comment):
```
[Direct answer or empathy hook]. [One concrete tip from common sense].
```
### Phase B (brand-aware, expert)
Default mode = **pedagogical**, not promotional.
DM reply structure (≤ 6000 chars but aim for ≤ 600):
```
[Acknowledge the situation in 1 sentence, neutral tone.]
[General rule / framework in 2-3 short paragraphs. No exact quote of statute. No price.]
[Concrete next step — point to <PRIMARY_CTA>. NEVER list multiple channels.]
```
Comment reply structure (≤ 200 chars):
```
[1 line of contextual acknowledgement]. [Indirect signal — "feel free to DM us" / "form on bio" — never paste a URL].
```
### Brand link policy
**ZERO outgoing links inside DMs or comments themselves.** TikTok actively dampens reach for posts containing third-party URLs in any user-visible text. Indirect signaling only:
- Bio link in your profile is the only authorized URL surface.
- Comment / DM body: never paste a URL, never paste a brand name in a way that triggers the "may be unsafe" toast.
- Never paste an external URL in a video caption either — the FYP penalty is real.
- Maximum 1 indirect mention per reply (e.g. "we have an app for that" — no name).
### The encoding trap (critical — see §9 for the full story)
If your sending path uses `cliclick t:'...'` for comment posting, **strip all accented characters and emoji** from the rendered string before sending. The macOS keystroke path silently drops or replaces non-ASCII characters in some locales. Replace `é → e`, `à → a`, `' → '`, `… → ...` etc. before the type call.
Forbidden in any reply:
- Any URL on `<BRAND_DOMAIN>` (and `www.`, subdomains, shorteners).
- "DM me", "click my profile", "check our site", "more info here", phone numbers, email addresses (in the reply body — bio is the only authorized URL surface).
- Brand name in the comment body (Phase A) or more than once (Phase B).
- Promises ("you will win", "guaranteed", "in 24h I'll fix it").
- Quoting specific past client / customer cases.
- Emojis (encoding trap + sober tone in regulated fields).
---
## 6. Original posting cadence
Posting cadence is **outside the scope of the live operational crons** described in §9 (those handle reactive DM + comment replies). The recommended rhythm for an account in Phase B is:
- **3-5 short videos / week** (15-45 s), one idea per video, hook in the first 1.5 s.
- **1 longer video / week** (45-90 s) explaining one concept end-to-end.
- **Avoid burst posting** (3+ videos within 6 h) — TikTok throttles the burst.
- **Respect the sound policy**: original sounds and verified-license sounds only. Copyrighted commercial tracks on a Business account = silent mute on the FYP.
Hashtag discipline:
- 3-5 hashtags per video, max.
- 1 broad (#fyp, #foryou) + 2-3 niche (#yourdomain).
- Never use banned or shadowbanned hashtags — re-check the `<WORKSPACE_DIR>/memory/tiktok-hashtag-state.md` registry monthly.
Apply a posting calendar in `<WORKSPACE_DIR>/memory/tiktok-ideas.md`. Run a Weekly Planning cron that picks the next 5-7 hooks from `tiktok-ideas.md` and confirms none of them duplicates a post in `tiktok-post-log.md` over the last 30 days.
---
## 7. Quotas (hard limits)
| Action | Limit |
|--------|-------|
| Replies (DM) per 24 h | 30 max |
| Replies (DM) per Reply Pass run | 4 max |
| Replies (comment) per 24 h | 40 max |
| Replies (comment) per Reply Pass run | 6 max |
| Outbound first-contact DMs / day | 0 (Phase A) — 5 (Phase B, only if user opted-in) |
| Follow / day | 20 max (Phase B), 5 max (Phase A) |
| Unfollow / day | 20 max |
| Actions in same video thread | min 30 min apart |
| Actions globally (any) | min 1 min apart |
| Same opening phrase across replies | never (see §8) |
Quota tracking: read `tiktok-reply-log.md` at the start of every run, count entries with timestamps in the last 24 h, abort early if quotas already met.
### Cron interleaving
To avoid two crons hammering the Business Suite at the same time:
- **DM Check** runs at `:00, :15, :30, :45`.
- **Comment Check** runs at `:03, :18, :33, :48` (offset by 3 min).
Never fuse the two into one cron — a single long-running cron is more likely to hit a TikTok soft block.
---
## 8. Anti-spam triggers (words and patterns to avoid)
### Content
| Avoid | Use instead |
|-------|-------------|
| "DM me", "check my profile", "link in bio" | (bio link does the work — don't point at it) |
| `<BRAND_NAME>` repeated > 1 time in same reply | (mention once, max) |
| "free consultation", "first call free" | (no marketing language) |
| Phone number, email, URL | (never in body) |
| Emojis | (regulated / sober fields — drop them. Also: encoding trap on macOS — see §9) |
| All caps for emphasis | (use italics sparingly, prefer plain text) |
| Bit.ly / tinyurl / shorteners | (TikTok flags shorteners aggressively) |
### Behavioral
- Replying within 30 s of comment creation → bot-like, wait ≥ 2 min.
- Same opening phrase across multiple replies → flagged (vary openings).
- > 6 actions in 10 min → temporary rate limit.
- Posting > 3 videos within 6 h → burst penalty on the FYP.
- Following > 25 users / day → 24 h action block.
- Mass-mentioning users (`@user @user @user` in a single reply) → spam classifier triggers.
- Mass-DMing identical messages → near-instant temporary ban.
---
## 9. Human-like UI flow (the only thing that actually works)
TikTok's Business Suite ships heavy front-end protections: iframes everywhere, two stacked textboxes after a "Reply" click, IME-aware focus management, and a non-trivial composer that ignores synthetic events. The only flow that survives is **physical-input simulation** — real mouse clicks via `cliclick` on macOS (or `xdotool` on Linux), real keystrokes, real dwells. This is the TikTok equivalent of Reddit's reCAPTCHA dodge: not a bypass, just real human signals.
### DM send flow (validated)
URL: `https://www.tiktok.com/messages?lang=<your-lang>` (redirects to `business-suite` if Business account, with an iframe wrapping the conversation pane).
1. **Pre-flight session check** (see §2). Confirm `uniqueId` is not null.
2. **Navigate to `/messages?lang=<lang>`**. Wait for the redirect to complete (≥ 1.5 s dwell).
3. **Detect iframe** via JS: `document.querySelectorAll('iframe').length > 0`. If yes, scope all subsequent queries to that iframe.
4. **List unread conversations**: query `[data-e2e="chat-list-item"]` (within iframe).
5. For each unread:
a. Click the conversation tile.
b. **Read all visible messages** `[data-e2e="chat-item"]` — full context before answering.
c. Qualify the lead (see §4).
d. Draft the reply (≤ 6000 chars).
6. **Send the reply** (the sequence below is the only one validated end-to-end):
```
1. subprocess.run(["pbcopy"], input=msg.encode("utf-8"))
2. Page.bringToFront via CDP
3. Compute the textbox's screen coordinates (see §9.3)
4. cliclick c:X,Y ← MANDATORY physical click in textbox
5. time.sleep(0.5)
6. osascript -e 'tell app "System Events" to keystroke "v" using command down'
7. time.sleep(1) ← wait for the Send button to become enabled
8. Compute screen coords of [data-e2e="message-send"]
9. cliclick c:screenX,screenY
10. Verify textbox is empty
```
The **physical click on step 4 is non-negotiable**: without it, the macOS clipboard paste lands in whichever frame currently has focus, which is rarely the message textbox.
### Comment reply flow (nested — validated)
URL: `https://www.tiktok.com/business-suite/comments`.
Structure of the page:
- Video list **in the main DOM** on the left, with a badge "N nouveaux commentaires" / "N new comments" per video.
- Selected video pane on the right, with **two iframes**: `iframe[0]` = messages (always present in background), `iframe[1]` = comments.
1. Scan the video list in the main DOM, regex `/(\d+) (nouveau|new)/` (not "Aucun" / "No").
2. For each video with new comments:
a. Click the video tile.
b. Wait 2 s for `iframe[1]` to load.
c. Read `[data-e2e="comment-level-1"]` + usernames `[data-e2e="comment-username-1"]`.
d. Qualify the comment (see §4).
e. **Click `[data-e2e="comment-reply-1"]`** of the target comment via JS scoped to `iframe[1]`. Wait 1.5 s.
3. **The two-textbox trap**: after clicking "Reply", two textboxes exist on the page:
- **INLINE textbox** (small `vpY`) = the nested reply textbox **← USE THIS ONE**.
- **GENERAL textbox** (large `vpY` near the bottom) = the page-level "Add a comment" box. Posting here creates a *new* top-level comment, not a reply.
4. Always pick the textbox with the **smallest** `vpY`. Sample selector:
```python
textboxes = [...] # all contenteditable nodes with their viewport coords
inline_tb = min(textboxes, key=lambda t: t['vpY'])
post_btns = [b for b in all_post_btns if not b['disabled']]
closest = min(post_btns, key=lambda b: abs(b['vpY'] - inline_tb['vpY']))
```
5. **Type the message** (not paste) for comments:
```
1. Compute screen coords of inline_tb
2. cliclick c:X,Y ← physical click in INLINE textbox
3. time.sleep(0.5)
4. cliclick t:'message_ascii_only' ← TYPE, do not paste
5. time.sleep(1)
6. Compute screen coords of the closest enabled comment-post button
7. cliclick c:X,Y
```
6. **Verify the reply landed under the parent comment** — re-snapshot and look for your username in the immediate children of the parent.
### 9.3 Computing screen coordinates from viewport
The browser CLI returns viewport coordinates (`vpX`, `vpY`) inside the page. To click via `cliclick`, you need screen coordinates:
```
screenX = window.screenX + viewportX
screenY = window.screenY + (outerHeight - innerHeight) + viewportY
```
The `(outerHeight - innerHeight)` term accounts for the Chrome top bar. On a typical macOS setup with the default Chrome chrome: `chrome_bar ≈ 87`.
You can fetch these via:
```js
const m = { sX: window.screenX, sY: window.screenY, ih: window.innerHeight, oh: window.outerHeight };
```
### What does NOT work (and why)
| Method | Why it fails |
|---|---|
| `document.execCommand('insertText', false, msg)` | Visually empties the textbox after typing but the message never reaches the send pipeline (TikTok's composer reads from a controlled React state, not from the DOM). |
| `cliclick kd:cmd t:v ku:cmd` | Types the literal letter "v" instead of pasting (the cmd keydown is released before the t:v fires). |
| `cliclick kp:return` to send | TikTok's composer ignores the Enter key in DMs and comments; you must click the send button. |
| `osascript ... keystroke "v" using command down` for comments | Pastes into the **page-level** focus, which is rarely the inline reply textbox after a "Reply" click. Use `cliclick t:'...'` for comments. |
| Picking the GENERAL textbox (largest `vpY`) for a nested reply | Posts a top-level comment instead of a nested reply. |
### Exit codes (recommended convention for your script)
| Code | Meaning | Recap action |
|------|---------|--------------|
| 0 | DM / comment sent, verified | `status: ok`, log the reply |
| 1 | Fatal error (UI ref missing, iframe absent, send button never enabled) | `status: error`, attach screenshot |
| 2 | Soft block / "Tap to retry" toast | `status: blocked`, stop, alert user |
| 3 | Session / login KO | `status: blocked`, alert user to re-login |
| 4 | Comment posted but landed in wrong textbox (top-level instead of nested) | `status: partial`, mark for manual cleanup |
### Gotchas (real ones, costly to discover)
- **The `cliclick t:` accent encoding trap**: `cliclick t:'message'` on macOS drops or replaces accented characters in some locales (notably `fr_FR.UTF-8`). Strip accents and emoji from the rendered message before typing. Same family of bug as Reddit's `LC_NUMERIC` trap, different symptom: instead of dwells failing silently, characters disappear from the visible reply.
- **iframe focus drift**: clicking inside the iframe via JS does not always give focus to the inner document. Always do a physical `cliclick` after navigating, before pasting/typing.
- **Two iframes on the comments page**: `iframe[0]` is the messages background, `iframe[1]` is the comments. Always scope queries to `iframe[1]` on `/business-suite/comments`.
- **The "Send" button has multiple instances**: pick the one with the smallest absolute `vpY` distance from your textbox, and verify it is `enabled` before clicking.
- **Cron timeout**: bump per-job timeout to **at least 1200 s** for posting crons — a 120 s default will kill a successful flow.
- **Save debug artifacts**: screenshots + page snapshots at each step in a scratch dir (e.g. `/tmp/<your-namespace>/tiktok/`). They are gold when a comment silently lands in the wrong box.
### When the UI changes
TikTok ships UI changes regularly. When the `data-e2e` attributes start returning null on selectors that used to work:
1. Take a manual snapshot via your browser CLI.
2. Inspect the new attribute names.
3. Update the selector map.
4. Re-test on your own account's most recent post (safe, low traffic) before re-enabling the live cron.
---
## 10. Hashtag / sound state management
File: `<WORKSPACE_DIR>/memory/tiktok-hashtag-state.md`
For each hashtag used in `<NICHE_KEYWORDS>`:
- Last 5 videos that used it + reach (views, watch-time %).
- Shadowban check (search the hashtag in incognito — does your video appear?).
- Banned / sensitive flag (cross-check with [hashtagsforlikes](https://hashtagsforlikes.co) or similar — keep a manual override list).
File: `<WORKSPACE_DIR>/memory/tiktok-sound-state.md`
For each sound used:
- Original or licensed (Business accounts can ONLY use commercial-license sounds).
- Reach with that sound vs your baseline.
- Sound ID / link.
Update at the end of every posting run.
---
## 11. Recovery & blockers
| Issue | Action |
|-------|--------|
| `status: stopped` | Report in recap: "Chrome <BROWSER_PROFILE> stopped, user action required (relaunch Chrome on port <BROWSER_PORT>)". Stop. |
| Session API returns `ok: false` | Report login expired. Stop. |
| Business Suite redirects to `/login` | Same as login expired. Stop. |
| HTTP 429 from `/api/user/detail/self/` | Stop the run, report "rate limited", wait next scheduled run. |
| Captcha / "puzzle" challenge | Stop. Never attempt to solve. Report for user action. |
| "Tap to retry" toast after sending | Soft block. Pause the role for ≥ 1 h. Report. |
| Comment posted but lands as a top-level (not a reply) | Auto-flag for human cleanup. Pause comment cron for 30 min. |
| Comment removed by TikTok < 10 min after publish | Auto-freeze the entire comment cron for 6 h. Append cause to `tiktok-learnings.md`. |
| `evaluate` returns null with no error | Likely off-tiktok.com page → navigate first, retry once. |
| Account banner "your account is at risk of restriction" | Flip to Phase A immediately. Disable all reply crons until manual review. |
---
## 12. Mandatory recap (alert channel + memory)
At the end of each cron:
**Alert channel (Telegram / Slack / Discord) — final run message**:
```
[Job name] — [status: ok|partial|blocked|skipped]
DMs handled: [N or "—"]
Comments handled: [N or "—"]
Hot leads: [list short OR "—"]
Phase: [A|B]
Blockers: [text OR "—"]
Next action: [1 line]
```
**Memory** — append to `<WORKSPACE_DIR>/memory/tiktok-recaps.md`:
```
## YYYY-MM-DD HH:MM TZ — <job-id> — status: <status>
- Job: <description>
- Phase: A|B
- DMs sent: <N>
- Comments sent: <N>
- Hot leads: <list or "—">
- Blockers: <text or "—">
- Next useful action: <1 line>
```
If no action was taken: say so clearly (`no DM/comment sent, reason`).
---
## 13. Memory files inventory
Located at: `<WORKSPACE_DIR>/memory/`
| File | Purpose | Update cadence |
|------|---------|----------------|
| `tiktok-recaps.md` | Per-run logs (mandatory append) | Every cron run |
| `tiktok-post-log.md` | Videos published (URL, hook, date, views, watch %) | Each posting run |
| `tiktok-reply-log.md` | Replies sent (target user, type DM/comment, URL, date, removed?) | Each reply pass |
| `tiktok-state.md` | Daily phase + warning flags + follower count | Daily Metrics Recap |
| `tiktok-hashtag-state.md` | Per-hashtag reach + shadowban check | After any posting run |
| `tiktok-sound-state.md` | Per-sound reach + license info | After any posting run |
| `tiktok-ideas.md` | Content backlog for new videos | Weekly Planning + ad hoc |
| `tiktok-learnings.md` | Patterns observed (what got reach / removed) | Weekly Planning + ad hoc |
| `tiktok-alerts-sent.md` | Anti-doublon log for hot-lead alerts (one row per user) | Each alert |
---
## 14. Account identity guardrails
This account exists to be **useful in public** with the brand as a *background* signal.
- The handle can carry the brand if your brand is the operating identity (legal/medical/B2B). Personal accounts: keep it neutral.
- Profile bio: one short generic line + the single `<PRIMARY_CTA>` link. No phone, no email in the bio.
- Never reply to a DM that asks for expert-grade advice on a specific case — direct the person to `<PRIMARY_CTA>` via a standard polite reply.
- Never share private case details, names, or numbers — even if the user shares theirs first.
- Never reuse strict templates from your CRM as TikTok content (they read as marketing).
- Never promise an outcome.
- Never charge or solicit payment via TikTok DM.
If someone asks "are you a bot?": answer once with a sober, ambiguous line (`"I'm part of the team — happy to help"`) and stop. Do not confirm AI use — and never lie outright if pressed.
---
## 15. Phase A → Phase B transition
When the Daily Metrics Recap detects (account_age_days ≥ 30) AND (last_warning_days_ago > 14) AND (followers ≥ 500):
1. Append to `tiktok-state.md`: `YYYY-MM-DD - phase=A - PHASE_B_THRESHOLD_REACHED`.
2. Send a distinct alert: `🎉 TikTok account in Phase B — enable comment-reply + outbound-DM crons (jobs.json: flip enabled=true)`.
3. Do **NOT** auto-flip the crons (manual user action — reduces risk of premature B-phase posting).
Once Phase B is enabled by the user:
- First week: cap reply pass at **2 replies/run** (not the full 6) — soft ramp.
- Keep posting cadence steady (no burst).
- Once 14 days in Phase B without warning: full doctrine quotas (§7).
---
## 16. Stability discipline
- Read the UI before clicking.
- One click → verify with a snapshot.
- Do not stack clicks.
- Close stale tabs at the end of every run.
- Return to the role's default page when done.
- Never silently fake a successful action — always verify the reply landed in the right thread / textbox.
**Better silence than spam. Better a blockage report than a fake success.**
---
## 17. First-run checklist
Before enabling any cron, run through this checklist:
- [ ] Section 0 placeholders filled in your local copy / agent memory.
- [ ] TikTok account is **Business** or **Creator** (Business Suite UI requires it).
- [ ] Profile bio is one short line + a SINGLE `<PRIMARY_CTA>` link.
- [ ] Browser profile launched at `http://127.0.0.1:<BROWSER_PORT>` and logged in.
- [ ] `/api/user/detail/self/` returns `ok:true` and a non-null `uniqueId` (login verified).
- [ ] `<WORKSPACE_DIR>/memory/` directory exists with the 9 memory files (see §13) — empty is fine, scripts append.
- [ ] Alert channel (Telegram / Slack / Discord) webhook tested with a "hello" message.
- [ ] `cliclick` installed (`brew install cliclick`) and macOS Accessibility permission granted to the terminal that runs the cron.
- [ ] Phase A confirmed: account < 30 d OR < 500 followers OR recent warning → only inbound-DM cron enabled.
- [ ] At least 14 days of organic posting (one video/day, no automation) **before** enabling reply crons, even if Phase B-eligible by metrics.
A bash one-liner to init the memory files:
```bash
mkdir -p "<WORKSPACE_DIR>/memory" && cd "$_" && touch tiktok-recaps.md tiktok-post-log.md tiktok-reply-log.md tiktok-state.md tiktok-hashtag-state.md tiktok-sound-state.md tiktok-ideas.md tiktok-learnings.md tiktok-alerts-sent.md
```
---
## 18. FAQ
**Q: Do I need OpenClaw to use this skill?**
A: No. OpenClaw browser CLI is the example stack — the doctrine works with Playwright, Puppeteer, Chrome MCP, or any CDP-capable tool. The `cliclick` step in §9 is macOS-specific; on Linux replace with `xdotool`; on Windows use the `SendInput` win32 API or a wrapper like AutoHotkey.
**Q: Can I use this skill for multiple TikTok accounts?**
A: Yes — clone the workspace dir per account. Each account gets its own `memory/` and its own browser profile. Use a different `<BROWSER_PORT>` per account so they don't collide.
**Q: Does this work with the official TikTok API (Display / Content Posting)?**
A: Partially. The official API covers video upload and basic profile reads but does NOT cover comment-reply or DM-reply — exactly the surfaces this skill operates on. The UI flow in §9 is the only reliable path until TikTok ships an official DM/comment-reply API.
**Q: What about TikTok Shop / Affiliate / Live?**
A: Out of scope. This skill covers DMs, comment replies, and FYP-eligible organic posting only. Shop and Live have separate UI and separate sanction patterns.
**Q: How does this interact with the "Action blocked" toasts?**
A: Treat them as soft blocks (exit code 2 in §9). Pause the role for ≥ 1 h, alert the user, do not retry inside the same cron run. If the toast says "we'll let you know when you can do this again", flip the account to Phase A and require a manual re-evaluation.
**Q: Can I run multiple crons in parallel?**
A: Within one account, no — the cron interleave in §7 (3-minute offset) is there because the Business Suite session can only handle one operation at a time without producing inconsistent UI state. Across accounts, yes, but use different browser profiles and different `<BROWSER_PORT>`.
**Q: What if my account is banned or shadowbanned?**
A: Stop everything. Do not appeal automatically — TikTok's appeal flow is sensitive to repeated automated submissions. Manual review only. Document the exact last 20 actions in `tiktok-learnings.md` so the post-mortem can identify the trigger.
don't have the plugin yet? install it then click "run inline in claude" again.