tiktok · v2.0 · 2026-08-01 · sha256 16f26f09486392c8
tiktok v2.0A
Immutable. This exact content is served forever at /api/v1/blob/16f26f09486392c8.
---
name: tiktok
description: Post videos to the user's TikTok — either published directly (with caption and privacy the user chooses) or uploaded to their drafts/inbox — and read their TikTok profile, stats and video list. Use when the user wants to publish or post a generated video to TikTok, send a video to their TikTok drafts, or check their TikTok account and performance.
when_to_use: |
Trigger when the user wants to push a video (e.g. one generated by
the video skills) to TikTok, or to read their TikTok account and
post performance. Direct Post publishes immediately with a caption
and a privacy level the user picks; Upload drops it into drafts for
them to finish in the app. Always confirm caption and privacy with
the user before posting — never post unattended.
connections: [tiktok]
allowed_tools: [Bash]
license: Apache-2.0
metadata:
author: acedatacloud
version: "2.0"
---
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
# Profile + stats (needs user.info.profile / user.info.stats)
curl -sS -H "$AUTH" "$T/user/info/?fields=open_id,display_name,avatar_url,username,is_verified,follower_count,following_count,likes_count,video_count" \
| jq '.data.user'
```
## List the user's videos
`fields` goes in the **query string**, pagination in the **body** (easy to get
wrong). `max_count` max is 20; `cursor` is a **millisecond** UTC timestamp,
while each video's `create_time` is in **seconds**.
```bash
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
"$T/video/list/?fields=id,title,video_description,create_time,duration,cover_image_url,share_url,view_count,like_count,comment_count,share_count" \
-d '{"max_count":20}' | jq '{videos:.data.videos, cursor:.data.cursor, has_more:.data.has_more}'
```
Only **public** videos are returned, newest first.
## Direct Post — publish straight to the account
Three steps, in this order. **Step 1 is mandatory** and its result drives what
you may offer the user.
### 1. Query creator info (required before every post)
```bash
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
"$T/post/publish/creator_info/query/" | jq '.data, .error.code'
```
Returns `creator_nickname`, `privacy_level_options`, `comment_disabled`,
`duet_disabled`, `stitch_disabled`, `max_video_post_duration_sec`.
Use it to:
- **Show the user `creator_nickname`** so they know which account will receive
the post.
- **Offer only the returned `privacy_level_options`** — a public account gets
`PUBLIC_TO_EVERYONE` / `MUTUAL_FOLLOW_FRIENDS` / `SELF_ONLY`; a private one
gets `FOLLOWER_OF_CREATOR` / `MUTUAL_FOLLOW_FRIENDS` / `SELF_ONLY`.
- **Reject a too-long video** against `max_video_post_duration_sec`.
- Not offer comment/duet/stitch toggles that come back disabled.
- If it returns a spam/cap error, **stop** and tell the user to try later.
### 2. Ask the user to confirm
Before initializing, the user must explicitly confirm — this is a TikTok
requirement, not just good manners:
- the **caption** (max 2200 UTF-16 chars; `#tag` and `@mention` work),
- the **privacy level**, **chosen by them from the options above — never
default to one, never assume `PUBLIC_TO_EVERYONE`**,
- and tell them: *"By posting, you agree to TikTok's Music Usage Confirmation."*
### 3. Initialize the post
```bash
VIDEO_URL="https://cdn.acedata.cloud/….mp4" # verified-domain public URL
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
-d "$(jq -n --arg t "$CAPTION" --arg p "$PRIVACY" --arg u "$VIDEO_URL" '{
post_info: {title:$t, privacy_level:$p, is_aigc:true},
source_info: {source:"PULL_FROM_URL", video_url:$u}}')" \
"$T/post/publish/video/init/" | jq '{publish_id:.data.publish_id, error}'
```
**Set `is_aigc: true` whenever the video was AI-generated** (anything from our
video skills). TikTok labels it "Creator labeled as AI-generated" — required
disclosure, and omitting it risks the post being taken down.
Optional `post_info` fields: `disable_comment`, `disable_duet`,
`disable_stitch`, `video_cover_timestamp_ms`. If the user says the video is a
paid partnership, set `brand_content_toggle: true` (which cannot be combined
with `SELF_ONLY`); for promoting their own business use `brand_organic_toggle`.
## Upload to drafts instead
When the user would rather finish in the app, use the inbox endpoint — no
caption or privacy, and **no `creator_info` call needed**:
```bash
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}'
```
## Poll status (both flows)
```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, publicaly_available_post_id}'
```
Status: `PROCESSING_DOWNLOAD` / `PROCESSING_UPLOAD` → `SEND_TO_USER_INBOX`
(drafts flow) or `PUBLISH_COMPLETE` (direct post), or `FAILED` with
`fail_reason`. Tell the user 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.
## Gotchas
- **Never hardcode `privacy_level`.** TikTok rejects values outside
`privacy_level_options` (`privacy_level_option_mismatch`) and treats a
defaulted privacy setting as a guideline violation for the whole app.
- `publicaly_available_post_id` (TikTok's own spelling — don't "fix" it) is a
**list**, and is **empty for non-public posts and for public posts still in
moderation**. Empty ≠ failure.
- `PULL_FROM_URL` needs the URL's domain verified in the developer app, HTTPS,
and **no redirects** (any 3xx fails). Verification covers subdomains
downward only. If the video isn't on a verified domain, use chunked
`FILE_UPLOAD` (init with `video_size`/`chunk_size`/`total_chunk_count`, then
`PUT` byte ranges to `upload_url`, which expires in 1 hour).
- 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.