- 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`.
```bash
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
```bash
# 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.
```bash
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)
```bash
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
```bash
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.
Auf GitHub ansehen