Call the TikTok Content Posting API 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.
Responses wrap everything in {"data":...,"error":{"code","message","log_id"}} —
error.code == "ok" means success; otherwise show error.message verbatim.
401/access_token_invalid = re-connect TikTok.
T="https://open.tiktokapis.com/v2"; AUTH="Authorization: Bearer $TIKTOK_TOKEN"
# Profile (account card)
curl -sS -H "$AUTH" "$T/user/info/?fields=open_id,display_name,avatar_url" \
| jq '.data.user'
Upload a video to the user's inbox / drafts (v1)
The simplest path is PULL_FROM_URL: TikTok fetches the video from a public URL
on a verified domain (AceData's cdn.acedata.cloud is verified for this app).
VIDEO_URL="https://cdn.acedata.cloud/...mp4" # must be on a verified domain
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}'
This drops the video into the user's TikTok inbox — they open the TikTok app to add caption / sound / privacy and post. Poll status:
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
-d '{"publish_id":"PUBLISH_ID"}' "$T/post/publish/status/fetch/" \
| jq '.data | {status, fail_reason}'
Gotchas
- v1 = inbox upload only. True "Direct Post" (set caption + privacy via API,
posts immediately) needs the
video.publishscope and a passed Content Posting audit — not available until then. Don't callcreator_info//post/publish/video/init/(Direct Post) here; they require that scope. PULL_FROM_URLonly works from a domain verified in the TikTok developer app. If the video isn't on a verified domain, use the chunkedFILE_UPLOADflow instead (init → PUT byte ranges → status).- Unaudited apps are rate-limited and may restrict visibility to
SELF_ONLY.