tiktok
SkillMediaPublish organic TikTok content (videos, photos, carousels) through the TikTok-compliant interactive posting form via the Hyper MCP. Use when the user wants to post a video to TikTok, share photos on TikTok, upload to TikTok, or any phrase like "post this to TikTok" / "share on TikTok" / "put this on
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the tiktok skill
What this skill tells your AI
The instructions your AI receives, as published by hyperfx-ai/marketing-skills in skills/tiktok/SKILL.md and read by ahel’s review.
Call the TikTok API v2 with curl + jq. The user's OAuth bearer token is in
$TIKTOK_TOKEN; every call needs Authorization: Bearer $TIKTOK_TOKEN. Base
URL: https://open.tiktokapis.com/v2.
T="https://open.tiktokapis.com/v2"; AUTH="Authorization: Bearer $TIKTOK_TOKEN"
Responses wrap everything in {"data":…,"error":{"code","message","log_id"}}.
Check error.code, not just the HTTP status — several real failures
(spam_risk_too_many_posts, spam_risk_user_banned_from_posting,
reached_active_user_cap) come back as HTTP 200 with a non-ok code. Show
error.message verbatim. 401 / access_token_invalid = re-connect TikTok.
Read the account
# Basic profile — open_id identifies the user for every posting call
curl -sS -H "$AUTH" "$T/user/info/?fields=open_id,display_name,avatar_url" \
| jq '.data.user'
Follower / like counts, bio, and the user's video list need the Display API
scopes (user.info.stats, user.info.profile, video.list), which this app
has not been granted — don't call /v2/video/list/ or request those fields,
they will fail with scope_not_authorized.
Upload the video to the user's drafts
This is the working path. The video lands in the creator's TikTok inbox / drafts; they open the TikTok app to add caption, sound and privacy, then post.
Required order:
- Download the exact video locally, but do not upload it yet.
- Call
request_action_confirmationwithkind: "generic", the real video preview, and a summary that says this uploads to drafts rather than publishing. - If cancelled, stop. If confirmed, run exactly one upload:
python3 skills/tiktok/scripts/tiktok.py upload video.mp4. - Poll with
python3 skills/tiktok/scripts/tiktok.py status PUBLISH_IDuntil terminal. Never calluploadagain while polling.
Two source modes. FILE_UPLOAD is the reliable one — PULL_FROM_URL only
works from a domain verified in the developer portal, and ours is not verified
yet (it returns url_ownership_unverified).
FILE_UPLOAD (default)
Download the video locally first, then init with its exact byte size. Files under 64 MB go up as a single chunk.
SIZE=$(stat -f%z video.mp4 2>/dev/null || stat -c%s video.mp4)
INIT=$(curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
-d "$(jq -n --argjson s "$SIZE" \
'{source_info:{source:"FILE_UPLOAD", video_size:$s, chunk_size:$s, total_chunk_count:1}}')" \
"$T/post/publish/inbox/video/init/")
echo "$INIT" | jq '{publish_id:.data.publish_id, error:.error.code}'
UPLOAD_URL=$(echo "$INIT" | jq -r '.data.upload_url')
curl -sS -X PUT "$UPLOAD_URL" \
-H "Content-Type: video/mp4" \
-H "Content-Length: $SIZE" \
-H "Content-Range: bytes 0-$((SIZE-1))/$SIZE" \
--data-binary @video.mp4 -o /dev/null -w '%{http_code}\n'
The PUT returns 201 when the whole file landed (206 for intermediate
chunks). upload_url expires in 1 hour.
PULL_FROM_URL (only from a verified domain)
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
-d "$(jq -n --arg u "$VIDEO_URL" '{source_info:{source:"PULL_FROM_URL", video_url:$u}}')" \
"$T/post/publish/inbox/video/init/" | jq '{publish_id:.data.publish_id, error}'
Needs HTTPS and no redirects (any 3xx fails). Domain verification covers subdomains downward only.
Poll status
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
-d "$(jq -n --arg id "$PUBLISH_ID" '{publish_id:$id}')" \
"$T/post/publish/status/fetch/" | jq '.data | {status, fail_reason}'
PROCESSING_UPLOAD / PROCESSING_DOWNLOAD → SEND_TO_USER_INBOX = done,
the video is waiting in the creator's TikTok inbox. Tell them to open the
TikTok app to finish posting, and that processing takes a few minutes.
Don't retry on auth_removed, spam_risk_text, spam_risk, or
spam_risk_user_banned_from_posting — those are terminal. internal is
retryable.
Direct Post — NOT available yet
Direct Post (/v2/post/publish/video/init/) publishes straight to the profile
with a caption and privacy level. Do not call it. It needs a Content
Posting API audit this app has not passed, so every attempt fails:
403 unaudited_client_can_only_post_to_private_accounts
The video.publish scope is granted and creator_info/query works, which
makes it look available — it is not. Until the audit clears, use the drafts
path above.
Gotchas
- Never hardcode
privacy_levelif Direct Post is ever enabled. TikTok rejects values outsideprivacy_level_optionsand treats a defaulted privacy setting as a guideline violation for the whole app. - Rate limits per user token:
creator_info/query20/min,video/init/6/min,status/fetch/30/min. TikTok also caps posts at roughly 15/day per creator, shared across all apps — another app can exhaust it. SEND_TO_USER_INBOXis success for this flow, not an intermediate state. Don't wait forPUBLISH_COMPLETE— that only happens once the user posts from the app.
Signals
- GitHub stars
- 86
- Forks
- 15
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
tiktok- Source
- github.com/hyperfx-ai/marketing-skills