Skip to main content

tiktok-api

Use when connecting a real TikTok account to code via the Content Posting, Display and Business Account APIs — OAuth, chunked video publish with status polling, and pulling views, watch time and impression sources, then logging that performance into the wiki as a dated feedback record. Covers short-lived tokens breaking a cron, unverified pull-from-URL ownership, and rate limits. NOT what to post or how to package it (that is `shortform-strategy` and `shortform-packaging`).

跳到安装

来源信息

仓库
ericrisco/rsc-harness
最近来源活动
2026年7月29日 23:35
检测到的 SKILL.md 语言
英语
星标
110
分支
9

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
7 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
tiktok-api
description
Use when connecting a real TikTok account to code via the Content Posting, Display and Business Account APIs — OAuth, chunked video publish with status polling, and pulling views, watch time and impression sources, then logging that performance into the wiki as a dated feedback record. Covers short-lived tokens breaking a cron, unverified pull-from-URL ownership, and rate limits. NOT what to post or how to package it (that is `shortform-strategy` and `shortform-packaging`).
tags
["tiktok","content-posting-api","tiktok-display-api","tiktok-business-api","oauth2","chunked-upload","video-publish","watch-time","completion-rate","shortform-feedback-log"]
recommends
["shortform-strategy","shortform-packaging","shortform-ideation","shortform-editing","social-publisher","instagram-api","youtube-api","api-connector-builder","automation-flows","knowledge-ops"]
origin
risco
# TikTok API — Transport + Ingestion for a Real Account *You own the wire: authenticate to a TikTok account, publish video, pull the numbers, and write those numbers into the wiki as a durable feedback log. You do not decide what to make, when to post it, or how to caption it — that is the shortform strategy/packaging family. Deliver clean transport and a queryable log; let the siblings interpret.* TikTok splits across **three** separate APIs, and a real account touches all three: - **Content Posting API** — `https://open.tiktokapis.com/v2/post/publish/...` — the *write* side: init a publish, transfer the file, poll status. Audit-gated. - **Display API** — `https://open.tiktokapis.com/v2/video/...` — the *cheap read* side: your own profile and basic per-video counters (`view_count, like_count, comment_count, share_count`). - **TikTok API for Business** — the *rich read* side: watch time, completion, impression sources. Enabled through a **separate business portal**, not the standard developer app. Auth is **user OAuth v2 via Login Kit**, never a service token. A human owns the account; you act on their behalf with a refresh token. There is **no official TikTok SDK** — you call the REST endpoints directly with any HTTP client. Treat the access token as a short-lived, refreshable credential object, never a hardcoded literal. ## When to use / When NOT Use when: - Wiring a script or agent to publish to an account: Direct Post or upload-to-draft via `/v2/post/publish/video/init/` (or `/inbox/` for a draft), then `FILE_UPLOAD` chunked PUT or `PULL_FROM_URL`, then poll `/v2/post/publish/status/fetch/`. - Pulling an account's own video stats: counters via Display `POST /v2/video/query/` (or `/v2/video/list/`); watch-time / completion / impression-source via the Business Account API. - Building the recurring "fetch performance → write to `02-DOCS/wiki/shortform/`" loop that turns API responses into an account feedback log siblings can read. - Debugging TikTok-specific failures: `scope_not_authorized`, `url_ownership_unverified`, `rate_limit_exceeded` (6 req/min), 24-hour access-token expiry, audit/`video.publish` not approved, unaudited-app private-only posting. Do NOT use when (route to the sibling that owns it): | You actually want | Go to | | --- | --- | | What to post / cadence / niche / hook strategy | `shortform-strategy` *(catalog id)* | | Clip ideas, hooks, a topic backlog | `shortform-ideation` *(catalog id)* | | Caption / cover / title packaging, A/B framing | `shortform-packaging` *(catalog id)* | | Cut/caption/render the actual clip file | `shortform-editing` *(catalog id)* | | Render a video file programmatically | `../remotion-video/SKILL.md` | | Post one asset to TikTok + IG + YouTube at once | `../social-publisher/SKILL.md` | | Instagram's Graph / Content Publishing API | `../instagram-api/SKILL.md` | | YouTube's two APIs (same family, other platform) | `../youtube-api/SKILL.md` | | Wrap an arbitrary REST provider with OAuth + retries | `api-connector-builder` *(catalog id)* | | Chain publish → Notion row → Slack across tools | `automation-flows` *(catalog id)* | One line: **this skill authenticates, calls, and ingests TikTok's Content Posting + Display + Business APIs into the wiki. What to post and how to package it belong to the shortform-strategy / shortform-ideation / shortform-packaging siblings; multi-network posting belongs to social-publisher.** ## 1. One-time setup (do this before any code) A checklist, because each missing step produces a distinct, confusing failure later: 1. Register a **TikTok developer app** in the developer portal. 2. **Add the products you need**: Login Kit (OAuth), Content Posting API (publish), Display API (read counts). Insights live in the **separate TikTok for Business portal** — enable that account access too if you need watch time/completion. 3. Set an exact **redirect URI** for the OAuth flow. 4. **Submit the app for audit before posting public content.** An unaudited app can only post **privately** (`SELF_ONLY`) and only to a limited set of test users. This is the #1 "works on my machine, breaks in prod" surprise — see rule below. 5. If you publish by URL (`PULL_FROM_URL`), **verify the domain / URL-prefix** in the portal (DNS TXT or URL-prefix), or every init returns `url_ownership_unverified`. The three gates are independent. Do not assume one approval covers everything: ```text Bad: "My app is approved, so publish + insights both work." Good: Content Posting *audit* gates public publish; Display *scope* (video.list) gates own-video counts; Business *portal* access gates watch time / completion / impression sources. Three separate gates — check each. ``` Scope table — request only what the job needs: | Scope | Grants | Use for | | --- | --- | --- | | `video.publish` | Direct Post to the public feed | `/post/publish/video/init/` (audit-gated) | | `video.upload` | Upload to drafts/inbox for the user to finish | `/post/publish/inbox/video/init/` | | `video.list` | Read your own videos + basic counters | Display `POST /v2/video/query/` | | `user.info.basic` | Read profile (open_id, display name, avatar) | `POST /v2/user/info/` | Full app-registration + product-enable walkthrough, the audit gate, and `scope_not_authorized` troubleshooting live in `references/oauth-setup.md`. ## 2. Get an authed client and keep the token alive OAuth v2: send the user to `https://www.tiktok.com/v2/auth/authorize/`, receive a `code` at your redirect URI, exchange it at `https://open.tiktokapis.com/v2/oauth/token/`, and **store the refresh token**. The lifecycle is the load-bearing fact: **access token expires in 24 hours** (`expires_in: 86400`); **refresh token lasts 365 days** (`refresh_expires_in: 31536000`) and renews without user re-consent. So a daily-pull cron **MUST refresh the access token every run**, and a long-idle account silently dies at the 365-day refresh boundary. ```python # python: raw REST, no official TikTok SDK. requests/httpx both fine. import time, json, os, requests TOKEN_URL = "https://open.tiktokapis.com/v2/oauth/token/" STORE = "tiktok_token.json" # gitignored — holds the rotating refresh_token def load(): return json.load(open(STORE)) if os.path.exists(STORE) else {} def save(t): t["obtained_at"] = int(time.time()); json.dump(t, open(STORE, "w")) def access_token(): t = load() fresh = t.get("access_token") and time.time() < t.get("obtained_at", 0) + t["expires_in"] - 60 if fresh: return t["access_token"] r = requests.post(TOKEN_URL, data={ # refresh every run, 24h expiry "client_key": os.environ["TIKTOK_CLIENT_KEY"], "client_secret": os.environ["TIKTOK_CLIENT_SECRET"], "grant_type": "refresh_token", "refresh_token": t["refresh_token"], # 365-day lifetime; rotates }, headers={"Content-Type": "application/x-www-form-urlencoded"}) r.raise_for_status() new = r.json() save(new) # persist the NEW refresh_token return new["access_token"] ``` ```javascript // node: built-in fetch, no SDK. import fs from "node:fs"; const STORE = "tiktok_token.json"; async function accessToken() { const t = JSON.parse(fs.readFileSync(STORE, "utf8")); if (t.access_token && Date.now() / 1000 < t.obtained_at + t.expires_in - 60) return t.access_token; const r = await fetch("https://open.tiktokapis.com/v2/oauth/token/", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ client_key: process.env.TIKTOK_CLIENT_KEY, client_secret: process.env.TIKTOK_CLIENT_SECRET, grant_type: "refresh_token", refresh_token: t.refresh_token, }), }); const n = await r.json(); n.obtained_at = Math.floor(Date.now() / 1000); fs.writeFileSync(STORE, JSON.stringify(n)); // persist rotated refresh_token return n.access_token; } ``` Rule: persist the **refresh token** and re-read it each run, never a bare `access_token`. A hardcoded `access_token=...` literal is a guaranteed failure within 24 hours — and is exactly what `verify.sh` flags. Full token-exchange flow (authorize URL params, PKCE, code exchange) is in `references/oauth-setup.md`. ## 3. Publish a video (init → transfer → poll) Publishing is always **three steps**: init the publish, transfer the bytes, poll until processing finishes (it is async). Pick the transfer mode first: | Situation | Source | Endpoint | | --- | --- | --- | | File is local / in your control | `FILE_UPLOAD` | `/post/publish/video/init/` | | File is at a **verified** HTTPS URL | `PULL_FROM_URL` | `/post/publish/video/init/` | | Should land as a draft the user finalizes | `FILE_UPLOAD` | `/post/publish/inbox/video/init/` | **Init** (FILE_UPLOAD) — returns `publish_id` and an `upload_url`: ```python import math, requests CHUNK = 10 * 1024 * 1024 # 10 MB, inside the 5–64 MB window size = os.path.getsize("clip.mp4") chunk_count = 1 if size < 5 * 1024 * 1024 else math.ceil(size / CHUNK) init = requests.post( "https://open.tiktokapis.com/v2/post/publish/video/init/", headers={"Authorization": f"Bearer {access_token()}", "Content-Type": "application/json; charset=UTF-8"}, json={ "post_info": {"title": "caption #fyp", "privacy_level": "SELF_ONLY"}, # public needs audit "source_info": { "source": "FILE_UPLOAD", "video_size": size, "chunk_size": CHUNK if size >= 5 * 1024 * 1024 else size, "total_chunk_count": chunk_count, }, }).json() publish_id = init["data"]["publish_id"] upload_url = init["data"]["upload_url"] ``` **Transfer** — PUT chunks **sequentially** to `upload_url` with a `Content-Range` header. Chunk **min 5 MB, max 64 MB** (final chunk up to 128 MB), **1–1000 chunks**; a file under 5 MB is one chunk equal to the file size. Each PUT returns **206** (more to send) or **201** (last chunk accepted): ```python with open("clip.mp4", "rb") as f: for i in range(chunk_count): first = i * CHUNK data = f.read(CHUNK) last = first + len(data) - 1 r = requests.put(upload_url, data=data, headers={ "Content-Type": "video/mp4", "Content-Range": f"bytes {first}-{last}/{size}", # exact byte span }) assert r.status_code in (206, 201), r.text # 206 = continue, 201 = done ``` **Poll** — TikTok processes asynchronously; check status until `PUBLISH_COMPLETE`. Respect the cap below — do not tight-loop: ```python import time while True: s = requests.post( "https://open.tiktokapis.com/v2/post/publish/status/fetch/", headers={"Authorization": f"Bearer {access_token()}", "Content-Type": "application/json; charset=UTF-8"}, json={"publish_id": publish_id}).json() status = s["data"]["status"] if status in ("PUBLISH_COMPLETE", "FAILED"): break time.sleep(10) # 6/min cap — sleep, never spin ``` **Rate limit: 6 requests/minute per user access token** → `rate_limit_exceeded`. Throttle init/status calls and back off; a tight status-poll loop blows the budget in seconds. `PULL_FROM_URL` requires the domain/URL-prefix to be verified in the portal (HTTPS only, no redirects, 1-hour download timeout) or init returns `url_ownership_unverified`. Full PULL_FROM_URL init body and verification steps are in `references/metrics-and-publish.md`. ## 4. Pull performance (two APIs, one rule) The load-bearing distinction: **Display gives you counters; only the Business API gives you watch time, completion, and traffic.** ```python # (a) Display API — basic counters only. scope video.list, up to 20 ids/request. counts = requests.post( "https://open.tiktokapis.com/v2/video/query/", params={"fields": "id,title,view_count,like_count,comment_count,share_count,duration,create_time"}, headers={"Authorization": f"Bearer {access_token()}", "Content-Type": "application/json"}, json={"filters": {"video_ids": ["<id1>", "<id2>"]}}).json() # returns: view_count, like_count, comment_count, share_count, duration, title, create_time ``` ```python # (b) Business Account API — the real engagement signal. # Returns the metrics Display CANNOT: average_time_watched, total_time_watched, # full_video_watched_rate (completion), impression_sources (FYP / Following / profile / # search), audience_countries. (Endpoint shape in references/metrics-and-publish.md.) ``` ```text Bad: expect average_time_watched / full_video_watched_rate from /v2/video/query/ Good: counters from Display /v2/video/query/; watch time + completion + impression_sources from the Business Account API. ```
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看