VidNavigator offers two ways to collect TikTok videos:
Profile Scrape
Walk a single creator’s account and return their videos, filtered by date and likes.
Keyword Search
Search across TikTok by keyword and return matching videos from many creators.
Both are asynchronous: you submit a task, get a task_id back immediately, then poll a GET endpoint until the task is completed. Results are retained for about 1 hour, and large result sets can be downloaded as a single JSON file via a temporary download_url.
These endpoints don’t have dedicated SDK helpers yet, so the examples below use raw HTTP (cURL, Python requests, and fetch).
Poll GET /tiktok/profile/{task_id} while task_status is processing. Once completed, walk the pages using the next_cursor from data.pagination.
import timedef get_profile_page(task_id, cursor=None, limit=50): params = {"limit": limit} if cursor: params["cursor"] = cursor r = requests.get(f"{BASE}/tiktok/profile/{task_id}", headers=HEADERS, params=params) return r.json()["data"]# Wait for completionwhile True: data = get_profile_page(task_id, limit=1) if data["task_status"] != "processing": break time.sleep(3)if data["task_status"] == "failed": raise RuntimeError(data.get("error_message", "scrape failed"))# Page through all videosvideos, cursor = [], Nonewhile True: page = get_profile_page(task_id, cursor=cursor, limit=100) videos.extend(page["videos"]) pagination = page["pagination"] if not pagination["has_next"]: break cursor = pagination["next_cursor"]print(f"Collected {len(videos)} videos")for v in videos[:5]: print(v["published_at"], v["likes"], v["url"])
async function getProfilePage(taskId, { cursor, limit = 50, includeUsage = false } = {}) { const params = new URLSearchParams({ limit: String(limit) }); if (cursor) params.set("cursor", cursor); if (includeUsage) params.set("include_usage", "true"); const r = await fetch(`${BASE}/tiktok/profile/${taskId}?${params}`, { headers: HEADERS }); return (await r.json()).data;}// Wait for completionlet status;do { status = await getProfilePage(taskId, { limit: 1 }); if (status.task_status === "processing") await new Promise((res) => setTimeout(res, 3000));} while (status.task_status === "processing");if (status.task_status === "failed") throw new Error(status.error_message || "scrape failed");// Page through all videosconst videos = [];let cursor = null;let hasNext = true;while (hasNext) { const page = await getProfilePage(taskId, { cursor, limit: 100 }); videos.push(...page.videos); hasNext = page.pagination.has_next; cursor = page.pagination.next_cursor;}console.log(`Collected ${videos.length} videos`);
For very large profiles, skip pagination and download the whole result at once: once the task is completed, data.download_url holds a short-lived signed URL to the full JSON. Call the GET endpoint again to mint a fresh URL if it expires.
Keyword search returns videos across many creators, sorted by published_at (newest first). Use parallel_search_slices (1–4) to lift the result ceiling, and after_datetime / before_datetime for time windows.
The polling flow is identical to the profile scrape — just swap the path to /tiktok/search/{task_id} and read data.results instead of data.videos. Pass include_usage=true once the task is completed to see what was billed.
import timedef get_search_page(task_id, cursor=None, limit=50, include_usage=False): params = {"limit": limit} if cursor: params["cursor"] = cursor if include_usage: params["include_usage"] = "true" r = requests.get(f"{BASE}/tiktok/search/{task_id}", headers=HEADERS, params=params) return r.json()# Wait for completionwhile True: body = get_search_page(task_id, limit=1) if body["data"]["task_status"] != "processing": break time.sleep(3)# First completed page, with usage disclosurebody = get_search_page(task_id, limit=50, include_usage=True)data = body["data"]print(f"{data['stats']['results_count']} results across {data['stats']['pages_fetched']} pages")for item in data["results"][:5]: print(item["published_at"], item["stats"]["likes"], item["url"])# Usage is at the top level of the responseif "usage" in body: print("Credits charged:", body["usage"]["total_credits"])
async function getSearchPage(taskId, { cursor, limit = 50, includeUsage = false } = {}) { const params = new URLSearchParams({ limit: String(limit) }); if (cursor) params.set("cursor", cursor); if (includeUsage) params.set("include_usage", "true"); const r = await fetch(`${BASE}/tiktok/search/${taskId}?${params}`, { headers: HEADERS }); return await r.json();}// Wait for completionlet body;do { body = await getSearchPage(taskId, { limit: 1 }); if (body.data.task_status === "processing") await new Promise((res) => setTimeout(res, 3000));} while (body.data.task_status === "processing");// First completed page with usage disclosurebody = await getSearchPage(taskId, { limit: 50, includeUsage: true });const { results, stats } = body.data;console.log(`${stats.results_count} results across ${stats.pages_fetched} pages`);results.slice(0, 5).forEach((item) => console.log(item.published_at, item.stats.likes, item.url));if (body.usage) console.log("Credits charged:", body.usage.total_credits);
Don’t pass include_usage on the submit (POST) call — it’s ignored because billing happens after the 202 response. Pass it on the polling GET, and only expect a usage block once task_status=completed (failed tasks have all charges refunded).