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`).

Zur Installation springen

Quellinformationen

Repository
ericrisco/rsc-harness
Letzte Quellaktivität
29. Juli 2026 um 23:35
Erkannte Sprache von SKILL.md
Englisch
Sterne
110
Forks
9

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
7 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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. ```
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen