tiktok · v2.2 · 2026-08-01 · sha256 40ac60db17199576
tiktok v2.2A
Immutable. This exact content is served forever at /api/v1/blob/40ac60db17199576.
---
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) requires an
audit this app has not passed yet; do not use it.
connections: [tiktok]
allowed_tools: [Bash]
license: Apache-2.0
metadata:
author: acedatacloud
version: "2.2"
---
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.
Confirm with the user before uploading.
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.
```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
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
```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 — 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_level`** if Direct Post is ever enabled. TikTok
rejects values outside `privacy_level_options` and treats a defaulted privacy
setting as a guideline violation for the whole app.
- 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.