界面已本地化;技能介绍与说明文档按所选语言由 AI 自动翻译,代码、命令、路径和专业术语保留原文,以原作者文档为准。
探索技能
效率与协作 · SKILL.md

tiktok

tiktok

【效率与协作】Upload videos to the user's TikTok drafts/inbox, where they finish posting in the TikTok app. Use when the user wants to publish or post a generated video to TikTok, or send a video to their TikTok drafts.

独立 GitHub 快照 · 自动分类 · 以原作者文档为准

Aacedatacloud / skillsApache-2.0

技能说明

GitHub

name: tiktok description: Upload videos to the user's TikTok drafts/inbox, where they finish posting in the TikTok app. Use when the user wants to publish or post a generated video to TikTok, or send a video to their TikTok drafts. when_to_use: | Trigger when the user wants to push a video (e.g. one generated by the video skills) to TikTok. Upload drops it into their drafts and they finish posting in the app — this is the working path. Direct Post (publishing immediately with caption + privacy) is restricted until audit. Use it only for an explicit private-account audit demonstration. connections: [tiktok] allowed_tools: [Bash, request_action_confirmation] license: Apache-2.0 metadata: author: acedatacloud version: "2.3"

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:

  1. Identify the exact video and its source: a creator's device-local file or a server-hosted URL under the verified acedata.cloud domain. Preview it.
  2. Call request_action_confirmation with kind: "generic", the real video preview, and a summary that says this uploads to drafts rather than publishing.
  3. If cancelled, stop. If confirmed, run exactly one upload. For a device-local file use python3 skills/tiktok/scripts/tiktok.py upload video.mp4. For a server-hosted video use python3 skills/tiktok/scripts/tiktok.py upload-url "$VIDEO_URL".
  4. Poll with python3 skills/tiktok/scripts/tiktok.py status PUBLISH_ID until terminal. Never call upload again while polling.

Two source modes are available. The developer portal verified acedata.cloud on 2026-10-03, covering its subdomains. Server-hosted videos on that domain must use PULL_FROM_URL; use FILE_UPLOAD only for a creator's device-local file. Verify that the exact URL is public HTTPS and does not redirect.

FILE_UPLOAD (creator device-local file)

Use this only for a file the creator selected from their own device. 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)

python3 skills/tiktok/scripts/tiktok.py upload-url "$VIDEO_URL"

The helper requires public HTTPS under the verified acedata.cloud domain, video/mp4, and no redirects. Domain verification covers subdomains downward.

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 audit demonstration — private test account only

The app has not passed Content Posting audit. Ordinary user requests continue through the inbox flow above. Use review-post-url only when the owner explicitly requests an audit demonstration on a private test account. TikTok restricts unaudited Direct Post to private accounts, SELF_ONLY visibility, and at most five posting users per 24 hours. A public account will fail even if video.publish is granted. Do not change an account's privacy setting as part of this skill.

Required sequence:

  1. Verify the owner-selected video is on a verified first-party HTTPS domain, has no redirect, and has a measured duration. This script checks the URL again before posting. Do not turn a server-hosted asset into FILE_UPLOAD to avoid domain verification.
  2. Query the connected test creator with python3 skills/tiktok/scripts/tiktok.py creator-info. Confirm its creator_username is the owner-approved private test account, not another account selected as the connector default. Copy the returned creator_nickname, creator_avatar_url, privacy_level_options, account interaction restrictions, and max_video_post_duration_sec into request_action_confirmation with kind: "tiktok.publish" and the actual video preview and duration. Do not invent or preselect a privacy option.
  3. If the creator cancels, stop. If confirmed, use exactly the returned card values: title, privacy_level, disable_comment, disable_duet, disable_stitch, brand_organic_toggle, and brand_content_toggle. Add is_aigc: true for AI-generated video. For an Ace Data Cloud brand promotion, the creator must select the own-brand promotional disclosure. Store these values in a temporary JSON file, not in the public repository.
  4. Only after that confirmation, initialize exactly once: python3 skills/tiktok/scripts/tiktok.py review-post-url "$VIDEO_URL" --duration-sec "$DURATION" --expected-creator-username "$CONFIRMED_CREATOR_USERNAME" --values-file "$VALUES_JSON" --confirmed. The helper re-queries creator info, refuses a different destination account, validates the card values, and uses PULL_FROM_URL with TikTok's Direct Post endpoint. Never substitute fields after the creator confirmed them.
  5. Poll the returned publish_id with status. Do not initialize again while a post is processing. Record the actual terminal status and the corresponding private video on the test account for the audit recording.

The Maestro promotional video is the content posted during the demonstration. The audit form requires a separate recording of the Studio authorization, posting controls, user confirmation, and actual TikTok outcome.

Gotchas

  • Never preselect privacy_level in the confirmation card. The review helper requires the creator's explicit SELF_ONLY choice and checks it against fresh privacy_level_options before making the one Direct Post call.
  • Rate limits per user token: creator_info/query 20/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_INBOX is success for this flow, not an intermediate state. Don't wait for PUBLISH_COMPLETE — that only happens once the user posts from the app.
我的收藏