Install
openclaw skills install tiktok-pageTikTok manager: post videos, list content & check account stats. Requires: powershell/pwsh. Reads ~/.config/tiktok-page/credentials.json (TIKTOK_ACCESS_TOKEN...
openclaw skills install tiktok-pageConstructs and executes TikTok API calls inline based on what the user wants. No scripts needed.
API base URL: https://open.tiktokapis.com/v2
Credentials are stored in ~/.config/tiktok-page/credentials.json.
$cfg = Get-Content "$HOME/.config/tiktok-page/credentials.json" -Raw | ConvertFrom-Json
$accessToken = $cfg.TIKTOK_ACCESS_TOKEN
$refreshToken = $cfg.TIKTOK_REFRESH_TOKEN
$clientKey = $cfg.TIKTOK_CLIENT_KEY
$clientSecret = $cfg.TIKTOK_CLIENT_SECRET
$openId = $cfg.TIKTOK_OPEN_ID
If the file does not exist, guide setup:
| Field | Purpose |
|---|---|
| TIKTOK_ACCESS_TOKEN | OAuth2 access token — used for all API calls |
| TIKTOK_REFRESH_TOKEN | Used to refresh access token when expired |
| TIKTOK_CLIENT_KEY | App Client Key from TikTok Developer Portal |
| TIKTOK_CLIENT_SECRET | App Client Secret — for token refresh only |
| TIKTOK_OPEN_ID | TikTok user open_id returned during OAuth |
One-time OAuth2 setup:
1. Go to https://developers.tiktok.com — create or select your app
2. Add redirect URI (e.g. https://localhost or your callback URL)
3. Note your Client Key and Client Secret
4. Direct user to:
https://www.tiktok.com/v2/auth/authorize/?client_key=CLIENT_KEY&redirect_uri=REDIRECT_URI&response_type=code&scope=user.info.basic,video.list,video.publish,video.upload,comment.list&state=random
5. After redirect, copy the code param from the callback URL
# Exchange auth code for tokens
$clientKey = "<your-client-key>"
$clientSecret = "<your-client-secret>"
$code = "<auth-code-from-redirect>"
$redirectUri = "<your-redirect-uri>"
$body = "client_key=$clientKey&client_secret=$clientSecret&code=$code&grant_type=authorization_code&redirect_uri=$redirectUri"
$r = Invoke-RestMethod "https://open.tiktokapis.com/v2/oauth/token/" -Method POST `
-Headers @{ "Content-Type" = "application/x-www-form-urlencoded" } -Body $body -ErrorAction Stop
New-Item -ItemType Directory -Force -Path "$HOME/.config/tiktok-page" | Out-Null
@{
TIKTOK_ACCESS_TOKEN = $r.access_token
TIKTOK_REFRESH_TOKEN = $r.refresh_token
TIKTOK_CLIENT_KEY = $clientKey
TIKTOK_CLIENT_SECRET = $clientSecret
TIKTOK_OPEN_ID = $r.open_id
} | ConvertTo-Json | Set-Content "$HOME/.config/tiktok-page/credentials.json" -Encoding UTF8
Restrict file permissions immediately after saving:
# Windows
icacls "$HOME/.config/tiktok-page/credentials.json" /inheritance:r /grant:r "$($env:USERNAME):(R,W)"
# macOS / Linux
# chmod 600 ~/.config/tiktok-page/credentials.json
Never commit this file to version control. It contains long-lived secrets. This skill makes no external calls other than to open.tiktokapis.com. No data is forwarded to third parties.
TikTok access tokens expire after 24 hours. Refresh before making calls if needed:
$cfg = Get-Content "$HOME/.config/tiktok-page/credentials.json" -Raw | ConvertFrom-Json
$body = "client_key=$($cfg.TIKTOK_CLIENT_KEY)&client_secret=$($cfg.TIKTOK_CLIENT_SECRET)&grant_type=refresh_token&refresh_token=$($cfg.TIKTOK_REFRESH_TOKEN)"
$r = Invoke-RestMethod "https://open.tiktokapis.com/v2/oauth/token/" -Method POST `
-Headers @{ "Content-Type" = "application/x-www-form-urlencoded" } -Body $body -ErrorAction Stop
$cfg.TIKTOK_ACCESS_TOKEN = $r.access_token
$cfg.TIKTOK_REFRESH_TOKEN = $r.refresh_token
$cfg | ConvertTo-Json | Set-Content "$HOME/.config/tiktok-page/credentials.json" -Encoding UTF8
Write-Host "Tokens refreshed."
| What user wants | Method | Endpoint |
|---|---|---|
| Get account info | POST | /user/info/ |
| List own videos | POST | /video/list/ |
| Get video detail | POST | /video/query/ |
| Get comments | GET | /video/comment/list/?video_id={id} |
| Publish video from URL | POST | /post/publish/video/init/ with PULL_FROM_URL |
| Upload video from file | POST then PUT | /post/publish/video/init/ then upload_url |
| Check publish status | GET | /post/publish/status/fetch/?publish_id={id} |
GET account info:
$cfg = Get-Content "$HOME/.config/tiktok-page/credentials.json" -Raw | ConvertFrom-Json
$headers = @{ "Authorization" = "Bearer $($cfg.TIKTOK_ACCESS_TOKEN)"; "Content-Type" = "application/json; charset=UTF-8" }
$body = @{ fields = "display_name,avatar_url,follower_count,following_count,likes_count,video_count" } | ConvertTo-Json
$result = Invoke-RestMethod "https://open.tiktokapis.com/v2/user/info/" -Method POST -Headers $headers -Body $body -ErrorAction Stop
$result.data.user
List videos:
$cfg = Get-Content "$HOME/.config/tiktok-page/credentials.json" -Raw | ConvertFrom-Json
$headers = @{ "Authorization" = "Bearer $($cfg.TIKTOK_ACCESS_TOKEN)"; "Content-Type" = "application/json; charset=UTF-8" }
$body = @{ max_count = 20; fields = "id,title,create_time,cover_image_url,share_url,view_count,like_count,comment_count,share_count" } | ConvertTo-Json
$result = Invoke-RestMethod "https://open.tiktokapis.com/v2/video/list/" -Method POST -Headers $headers -Body $body -ErrorAction Stop
$result.data.videos | Format-Table id, title, view_count, like_count, create_time
Get video detail by ID:
$cfg = Get-Content "$HOME/.config/tiktok-page/credentials.json" -Raw | ConvertFrom-Json
$headers = @{ "Authorization" = "Bearer $($cfg.TIKTOK_ACCESS_TOKEN)"; "Content-Type" = "application/json; charset=UTF-8" }
$body = @{ filters = @{ video_ids = @("<video_id>") }; fields = "id,title,view_count,like_count,comment_count,share_count,embed_html" } | ConvertTo-Json -Depth 4
$result = Invoke-RestMethod "https://open.tiktokapis.com/v2/video/query/" -Method POST -Headers $headers -Body $body -ErrorAction Stop
$result.data.videos
Publish video from URL:
$cfg = Get-Content "$HOME/.config/tiktok-page/credentials.json" -Raw | ConvertFrom-Json
$headers = @{ "Authorization" = "Bearer $($cfg.TIKTOK_ACCESS_TOKEN)"; "Content-Type" = "application/json; charset=UTF-8" }
$body = @{
post_info = @{
title = "Your video caption"
privacy_level = "PUBLIC_TO_EVERYONE"
disable_duet = $false
disable_stitch = $false
disable_comment = $false
}
source_info = @{
source = "PULL_FROM_URL"
video_url = "https://example.com/video.mp4"
video_size = 12345678
chunk_size = 10000000
total_chunk_count = 1
}
} | ConvertTo-Json -Depth 5
$result = Invoke-RestMethod "https://open.tiktokapis.com/v2/post/publish/video/init/" -Method POST -Headers $headers -Body $body -ErrorAction Stop
Write-Host "Publish ID: $($result.data.publish_id)"
Upload video from local file:
$cfg = Get-Content "$HOME/.config/tiktok-page/credentials.json" -Raw | ConvertFrom-Json
$headers = @{ "Authorization" = "Bearer $($cfg.TIKTOK_ACCESS_TOKEN)"; "Content-Type" = "application/json; charset=UTF-8" }
$filePath = "C:\path\to\video.mp4"
$fileSize = (Get-Item $filePath).Length
$chunkSize = 10MB
$initBody = @{
post_info = @{
title = "Your caption"
privacy_level = "PUBLIC_TO_EVERYONE"
disable_duet = $false
disable_stitch = $false
disable_comment = $false
}
source_info = @{
source = "FILE_UPLOAD"
video_size = $fileSize
chunk_size = $chunkSize
total_chunk_count = [math]::Ceiling($fileSize / $chunkSize)
}
} | ConvertTo-Json -Depth 5
$initResult = Invoke-RestMethod "https://open.tiktokapis.com/v2/post/publish/video/init/" -Method POST -Headers $headers -Body $initBody -ErrorAction Stop
$uploadUrl = $initResult.data.upload_url
$publishId = $initResult.data.publish_id
# Upload chunks
$fileStream = [System.IO.File]::OpenRead($filePath)
$buffer = New-Object byte[] $chunkSize
$chunkIndex = 0
while (($bytesRead = $fileStream.Read($buffer, 0, $chunkSize)) -gt 0) {
$chunk = $buffer[0..($bytesRead - 1)]
$rangeStart = $chunkIndex * $chunkSize
$rangeEnd = $rangeStart + $bytesRead - 1
Invoke-RestMethod $uploadUrl -Method PUT -Headers @{
"Content-Range" = "bytes $rangeStart-$rangeEnd/$fileSize"
"Content-Type" = "video/mp4"
} -Body $chunk | Out-Null
$chunkIndex++
}
$fileStream.Close()
Write-Host "Upload complete. Publish ID: $publishId"
Check publish status:
$cfg = Get-Content "$HOME/.config/tiktok-page/credentials.json" -Raw | ConvertFrom-Json
$headers = @{ "Authorization" = "Bearer $($cfg.TIKTOK_ACCESS_TOKEN)" }
$result = Invoke-RestMethod "https://open.tiktokapis.com/v2/post/publish/status/fetch/?publish_id=<publish_id>" -Headers $headers -ErrorAction Stop
Write-Host "Status: $($result.data.status)"
Get comments:
$cfg = Get-Content "$HOME/.config/tiktok-page/credentials.json" -Raw | ConvertFrom-Json
$headers = @{ "Authorization" = "Bearer $($cfg.TIKTOK_ACCESS_TOKEN)" }
$result = Invoke-RestMethod "https://open.tiktokapis.com/v2/video/comment/list/?video_id=<video_id>&fields=id,text,create_time,like_count" -Headers $headers -ErrorAction Stop
$result.data.comments | Format-Table id, text, like_count, create_time
try {
# ... API call ...
} catch {
$err = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue
$code = $err.error.code
$message = $err.error.message
Write-Host "TikTok API Error $code: $message"
}
| Code | Meaning | Fix |
|---|---|---|
| access_token_invalid | Token revoked or invalid | Re-run OAuth2 setup in STEP 1 |
| access_token_expired | Access token expired (24h TTL) | Run token refresh in STEP 2 |
| spam_risk_too_many_requests | Rate limited | Wait and retry; reduce request frequency |
| scope_not_authorized | Missing OAuth scope | Re-authorize with the required scope (see below) |
| video_not_found | Video ID invalid or deleted | Verify the video ID |
| privacy_level_not_allowed | Privacy setting not permitted | Use PUBLIC_TO_EVERYONE or SELF_ONLY |
| file_size_check_failed | Video file too large | Must be under 4GB and 60 minutes |
| duration_check_failed | Video too short or too long | Min 1 second, max 10 minutes (60 min for some accounts) |
| Scope | Required for |
|---|---|
| user.info.basic | Get account info |
| video.list | List own videos |
| video.publish | Publish videos |
| video.upload | Upload video chunks |
| comment.list | Read comments on own videos |
| comment.list.manage | Hide or delete comments |
If a scope is missing: