TikTok Top Videos API. Top videos, on request.
Ranked by country, period and metric. We handle the proxies, blocks and platform changes.
TikTok Top Videos API at a glance
Returns TikTok Creative Center's top videos for a country over 7 or 30 days, ranked by views, organic views, engagement or six-second view rate. Each has its rank, caption, hashtags, cover, a playable video link, author with follower count, views in the window and lifetime and how many were organic, engagement rate and six-second view rate. With no parameters you get the US board for the latest 7 days, by views.
GET /v1/tiktok/top_posts
- 1 credit per page, $0.75–$1.97 per 1,000 pages
- 22 typed fields null when unknown
- 20 videos per page, Creative Center's rank by the rank_by metric (1 = top)
- 100 credits free when you sign up, no card, so you can build before you spend
Send one request. Get up to 20 videos back.
Copy the request in your language, or hand it to your agent. Sign up for an API key and your first 100 credits are free.
$ npm install socialtapconst params = new URLSearchParams({
});
const res = await fetch(`https://socialtap.dev/v1/tiktok/top_posts?${params}`, {
headers: { "x-api-key": process.env.SOCIALTAP_API_KEY ?? "" },
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
const { data, page, meta } = body;
console.log(data, meta.credits_charged);import os
import requests
res = requests.get(
"https://socialtap.dev/v1/tiktok/top_posts",
params={},
headers={"x-api-key": os.environ["SOCIALTAP_API_KEY"]},
)
body = res.json()
if not res.ok:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
data, page, meta = body["data"], body["page"], body["meta"]
print(data, meta["credits_charged"])require "json"
require "net/http"
uri = URI("https://socialtap.dev/v1/tiktok/top_posts")
uri.query = URI.encode_www_form()
req = Net::HTTP::Get.new(uri)
req["x-api-key"] = ENV.fetch("SOCIALTAP_API_KEY")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }
body = JSON.parse(res.body)
raise "#{body["error"]["code"]}: #{body["error"]["message"]}" unless res.is_a?(Net::HTTPSuccess)
pp body["data"]
puts body["meta"]["credits_charged"]<?php
$query = http_build_query([
]);
$ch = curl_init('https://socialtap.dev/v1/tiktok/top_posts?' . $query);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('SOCIALTAP_API_KEY')],
]);
$body = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) >= 400) {
throw new RuntimeException($body['error']['code'] . ': ' . $body['error']['message']);
}
print_r($body['data']);
echo $body['meta']['credits_charged'], PHP_EOL;package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"os"
)
func main() {
q := url.Values{}
req, err := http.NewRequest("GET", "https://socialtap.dev/v1/tiktok/top_posts?"+q.Encode(), nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("x-api-key", os.Getenv("SOCIALTAP_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
var body struct {
Data json.RawMessage `json:"data"`
Page json.RawMessage `json:"page"`
Meta map[string]any `json:"meta"`
Error *struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
log.Fatal(err)
}
if body.Error != nil {
log.Fatalf("%s: %s", body.Error.Code, body.Error.Message)
}
fmt.Println(string(body.Data), body.Meta["credits_charged"])
}curl 'https://socialtap.dev/v1/tiktok/top_posts' \
-H "x-api-key: $SOCIALTAP_API_KEY"{
"data": [
{
"approximate_fields": [],
"author": {
"avatar_url": "https://p19-common-sign.tiktokcdn-us.com/tos-useast8-avt-0068-tx2/7d6ee80f6e94a044a88786eec4ef128f~tplv-tiktokx-cropcenter:300:300.jpeg?dr=9638&refresh_token=9cb420e5&x-expires=4070908800&x-signature=SCRUBBED&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=429d27ce&idc=useast5",
"display_name": "Meg Ann Co.",
"id": "6880574638150697990",
"is_verified": null,
"url": "https://www.tiktok.com/@megannco",
"username": "megannco"
},
"coauthors": [],
"duration_ms": null,
"hashtags": [
"ad",
"shopifypartner"
],
"id": "7678479724896587038",
"is_nsfw": null,
"is_pinned": null,
"is_sponsored": true,
"language": null,
"media": [
{
"alt_text": null,
"duration_ms": null,
"expires_at": "2026-10-25T18:00:00Z",
"height": null,
"thumbnail_url": "https://p16-common-sign.tiktokcdn-us.com/tos-useast8-p-0068-tx2/ocK3E0iPwBkRoAA5XXAPvCEAiBoiaIcqlA9Cp~tplv-tiktokx-origin.image?dr=9643&nonce=98074&refresh_token=524f684d00c1e5e8de7e7fa338f0765c&x-expires=4070908800&x-signature=SCRUBBED&idc=useast5&ps=13740610&shcp=89439a58&shp=c…",
"type": "video",
"url": "https://v16m-default.tiktokcdn-us.com/SCRUBBED/f2a52380/video/tos/useast8/tos-useast8-pve-0068-tx2/o86niOqRAgxIAIiN1HEau2iLCbiPq7fcAVQ2B4/?a=2043&bti=NjQ7OGYwLzA6&&bt=1479&ft=GS-jnInz7ThiWdyGXq8Zmo&mime_type=video_mp4&rc=ODc4Mzk5M2g8aTY1OzpoNEBpM288ZWs5cndxZDMzaTczNEBfMmMuMWE0NjE…",
"width": null
}
],
"mentions": [],
"metrics": {
"comment_count": null,
"like_count": null,
"quote_count": null,
"repost_count": null,
"save_count": null,
"share_count": null,
"view_count": 60982257
},
"platform": "tiktok",
"platform_specific": {
"tiktok": {
"author_follower_count": 252785,
"author_sec_uid": null,
"board": {
"engagement_rate": 0.002308245167114756,
"organic_view_count": 6815757,
"period_end_at": "2026-10-31T00:00:00Z",
"period_engagement_rate": 0.0016317098947514215,
"period_organic_view_count": 4827523,
"period_six_second_view_rate": 0.03203383844307908,
"period_view_count": 38901523,
"rank": 1,
"six_second_view_rate": 0.03900531365810675
},
"country_code": null,
"is_ad": true,
"is_ai_generated": null,
"location": null,
"music": null
}
},
"platform_type": "video",
"published_at": "2026-08-26T23:03:07Z",
"text": "Five years ago, I started my small business with no idea what it would grow into. From working out of my parents’ spare room to now having my own commercial space, @Shopify has been there every step of the way! Having the freedom to build a storefront that actually feels like my …",
"thumbnail_url": "https://p16-common-sign.tiktokcdn-us.com/tos-useast8-p-0068-tx2/ocK3E0iPwBkRoAA5XXAPvCEAiBoiaIcqlA9Cp~tplv-tiktokx-origin.image?dr=9643&nonce=98074&refresh_token=524f684d00c1e5e8de7e7fa338f0765c&x-expires=4070908800&x-signature=SCRUBBED&idc=useast5&ps=13740610&shcp=89439a58&shp=c…",
"title": null,
"type": "video",
"unknown_fields": [
"mentions"
],
"url": "https://www.tiktok.com/@megannco/video/7678479724896587038"
},
{
"approximate_fields": [],
"author": {
"avatar_url": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/bd593306c9ed3c915a4835f18e0b8cd0~tplv-tiktokx-cropcenter:300:300.jpeg?dr=9638&refresh_token=a99bc633&x-expires=4070908800&x-signature=SCRUBBED&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=429d27ce&idc=useast5",
"display_name": "Fefe",
"id": "6532037588893564929",
"is_verified": null,
"url": "https://www.tiktok.com/@fefe",
"username": "fefe"
},
"coauthors": [],
"duration_ms": null,
"hashtags": [
"Ruffles",
"DêPlayNaSuaOnda"
],
"id": "7701364596807912734",
"is_nsfw": null,
"is_pinned": null,
"is_sponsored": true,
"language": null,
"media": [
{
"alt_text": null,
"duration_ms": null,
"expires_at": "2026-10-25T18:00:00Z",
"height": null,
"thumbnail_url": "https://p16-common-sign.tiktokcdn-us.com/tos-useast8-p-0068-tx2/oUEGAfEweXOAGIXAQVQMYM84gAoJAzavIqCLfj~tplv-tiktokx-origin.image?dr=9643&nonce=43595&refresh_token=47f05003f2004fe32f1c3c52afcd4ccd&x-expires=4070908800&x-signature=SCRUBBED&idc=useast5&ps=13740610&shcp=89439a58&shp=…",
"type": "video",
"url": "https://v16m-default.tiktokcdn-us.com/SCRUBBED/f2a52380/video/tos/useast8/tos-useast8-ve-0068c001-tx2/o0XRDEADQZqgjsEbIBBUBVFDk4yIfEfL40A55W/?a=2043&bti=NjQ7OGYwLzA6&&bt=343&ft=GS-jnInz7ThiWdyGXq8Zmo&mime_type=video_mp4&rc=aTk0OWk6NDNpNTRmZDkzNkBpajM6ZnY5cm9lZTMzaTczNEAyYjVeMjAyX…",
"width": null
}
],
"mentions": [],
"metrics": {
"comment_count": null,
"like_count": null,
"quote_count": null,
"repost_count": null,
"save_count": null,
"share_count": null,
"view_count": 36746955
},
"platform": "tiktok",
"platform_specific": {
"tiktok": {
"author_follower_count": 17082503,
"author_sec_uid": null,
"board": {
"engagement_rate": 0.0018365603354073827,
"organic_view_count": 202204,
"period_end_at": "2026-10-31T00:00:00Z",
"period_engagement_rate": 0.0018365603354073827,
"period_organic_view_count": 202204,
"period_six_second_view_rate": 0.05010065159666401,
"period_view_count": 36746955,
"rank": 2,
"six_second_view_rate": 0.05010065159666401
},
"country_code": null,
"is_ad": true,
"is_ai_generated": null,
"location": null,
"music": null
}
},
"platform_type": "video",
"published_at": "2026-10-27T15:08:23Z",
"text": "Medo? Só se for de acabar RUFFLES®️ @br_ruffles #Ruffles #DêPlayNaSuaOnda",
"thumbnail_url": "https://p16-common-sign.tiktokcdn-us.com/tos-useast8-p-0068-tx2/oUEGAfEweXOAGIXAQVQMYM84gAoJAzavIqCLfj~tplv-tiktokx-origin.image?dr=9643&nonce=43595&refresh_token=47f05003f2004fe32f1c3c52afcd4ccd&x-expires=4070908800&x-signature=SCRUBBED&idc=useast5&ps=13740610&shcp=89439a58&shp=…",
"title": null,
"type": "video",
"unknown_fields": [
"mentions"
],
"url": "https://www.tiktok.com/@fefe/video/7701364596807912734"
},
{
"approximate_fields": [],
"author": {
"avatar_url": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/bd593306c9ed3c915a4835f18e0b8cd0~tplv-tiktokx-cropcenter:300:300.jpeg?dr=9638&refresh_token=a99bc633&x-expires=4070908800&x-signature=SCRUBBED&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=429d27ce&idc=useast5",
"display_name": "Fefe",
"id": "6532037588893564929",
"is_verified": null,
"url": "https://www.tiktok.com/@fefe",
"username": "fefe"
},
"coauthors": [],
"duration_ms": null,
"hashtags": [
"publicidade",
"Ruffles",
"DêPlayNaSuaOnda"
],
"id": "7684011124739656991",
"is_nsfw": null,
"is_pinned": null,
"is_sponsored": true,
"language": null,
"media": [
{
"alt_text": null,
"duration_ms": null,
"expires_at": "2026-10-25T18:00:00Z",
"height": null,
"thumbnail_url": "https://p16-common-sign.tiktokcdn-us.com/tos-useast8-p-0068-tx2/ogejjeAR8AEFQ7CvAMjVT3EDFQIqWIAHeiAIxD~tplv-tiktokx-origin.image?dr=9643&nonce=17399&refresh_token=bb7d5053292654876361c80a65173799&x-expires=4070908800&x-signature=SCRUBBED&idc=useast5&ps=13740610&shcp=89439a58&shp=…",
"type": "video",
"url": "https://v16m-default.tiktokcdn-us.com/SCRUBBED/f2a52380/video/tos/useast8/tos-useast8-pve-0068-tx2/okEeIsIAVLAFKExFimRK8ofO9dDrEBBEHQ4qjj/?a=2043&bti=NjQ7OGYwLzA6&&bt=408&ft=GS-jnInz7ThiWdyGXq8Zmo&mime_type=video_mp4&rc=OGQ0NWVnNzg7aTNmZTlnM0BpM2Ryc3g5cnVrZDMzaTczNEBhMi4xYF4wNmMx…",
"width": null
}
],
"mentions": [],
"metrics": {
"comment_count": null,
"like_count": null,
"quote_count": null,
"repost_count": null,
"save_count": null,
"share_count": null,
"view_count": 70021489
},
"platform": "tiktok",
"platform_specific": {
"tiktok": {
"author_follower_count": 17082503,
"author_sec_uid": null,
"board": {
"engagement_rate": 0.0013541271594495797,
"organic_view_count": 3816798,
"period_end_at": "2026-10-31T00:00:00Z",
"period_engagement_rate": 0.0012484027579240744,
"period_organic_view_count": 1170836,
"period_six_second_view_rate": 0.024768645613821798,
"period_view_count": 35180954,
"rank": 3,
"six_second_view_rate": 0.024519193030438022
},
"country_code": null,
"is_ad": true,
"is_ai_generated": null,
"location": null,
"music": null
}
},
"platform_type": "video",
"published_at": "2026-09-10T20:47:47Z",
"text": "Existe combinação mais perfeita do que RUFFLES®️ e um bom filme? Impossível! Todo filme pede aquela crocância única para entrar no clima. 🎬✨ @br_ruffles #publicidade #Ruffles #DêPlayNaSuaOnda",
"thumbnail_url": "https://p16-common-sign.tiktokcdn-us.com/tos-useast8-p-0068-tx2/ogejjeAR8AEFQ7CvAMjVT3EDFQIqWIAHeiAIxD~tplv-tiktokx-origin.image?dr=9643&nonce=17399&refresh_token=bb7d5053292654876361c80a65173799&x-expires=4070908800&x-signature=SCRUBBED&idc=useast5&ps=13740610&shcp=89439a58&shp=…",
"title": null,
"type": "video",
"unknown_fields": [
"mentions"
],
"url": "https://www.tiktok.com/@fefe/video/7684011124739656991"
}
],
"page": {
"has_more": false,
"next_cursor": null,
"next_cursor_expires_at": null,
"end_reason": "platform_limit"
},
"meta": {
"request_id": "req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f",
"platform": "tiktok",
"source": "api",
"is_cached": false,
"credits_charged": 1,
"latency_ms": 1096,
"fetched_at": "2026-10-25T12:00:00Z",
"input": null,
"credits_remaining": 9999,
"client_request_id": null,
"warnings": []
}
}import { SocialTap } from "socialtap";
const client = new SocialTap(); // apiKey defaults to process.env.SOCIALTAP_API_KEY
const res = await client.tiktok.topPosts.list({});
console.log(res.data);
console.log(res.credits.charged, res.meta.request_id);import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) throws Exception {
String query = "";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://socialtap.dev/v1/tiktok/top_posts?" + query))
.header("x-api-key", System.getenv("SOCIALTAP_API_KEY"))
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
// An error is {"error": {"code", "message", ...}}; success is {data, meta}, meta.credits_charged its price.
if (response.statusCode() >= 400) throw new RuntimeException(response.body());
System.out.println(response.body());
}
static String encode(String value) {
return URLEncoder.encode(value, StandardCharsets.UTF_8);
}
}using System.Text.Json;
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("x-api-key", Environment.GetEnvironmentVariable("SOCIALTAP_API_KEY"));
var query = string.Join("&",);
var res = await http.GetAsync("https://socialtap.dev/v1/tiktok/top_posts?" + query);
using var body = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
if (!res.IsSuccessStatusCode)
{
var error = body.RootElement.GetProperty("error");
throw new Exception($"{error.GetProperty("code")}: {error.GetProperty("message")}");
}
Console.WriteLine(body.RootElement.GetProperty("data"));
Console.WriteLine(body.RootElement.GetProperty("meta").GetProperty("credits_charged"));// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }, serde_json = "1"
use std::env;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let res = reqwest::blocking::Client::new()
.get("https://socialtap.dev/v1/tiktok/top_posts")
.query(&[])
.header("x-api-key", env::var("SOCIALTAP_API_KEY")?)
.send()?;
let ok = res.status().is_success();
let body: serde_json::Value = res.json()?;
if !ok {
return Err(format!("{}: {}", body["error"]["code"], body["error"]["message"]).into());
}
println!("{} {}", body["data"], body["meta"]["credits_charged"]);
Ok(())
}200 OK · GET https://socialtap.dev/v1/tiktok/top_postsLong arrays and strings are shortened here. Real responses are complete.Docs One schema on 21 platforms.
The response reference lists every field, its type and when it is null. See the full schema.
Null means unknown
A field TikTok doesn't show is null, never zero or an empty string, so a real zero always means zero.
No input needed
Every parameter is optional: the plain request returns the default list.
Write one parser
TikTok, Instagram, Facebook and 18 more platforms answer in these fields, so the code that reads one reads them all.
platformstring- Platform ID (tiktok, youtube, …).
idstring- Platform-native post ID (string; Instagram shortcode, Reddit with its t3_ prefix).
urlstring- Canonical post URL.
typestring- Normalized content shape. New values may be added; treat unknown values as other.
platform_typestring- The platform's own product label, lowercase (reel, short, story, live, tweet, …).
titlestring- Headline (YouTube title, Reddit title); null on caption-only platforms.
textstring- Caption, description or body as plain text.
languagestring- BCP-47 language of text, when the platform states it.
Live data from 33 platforms.
What teams build with the TikTok Top Videos API.
See which videos lead a market this week
Find formats with outsized engagement
Tell organic hits from paid reach
Study winning videos in a category
Good to know before you build.
- Logged out, Creative Center shows only the top videos of each board and keeps the rest behind its login, so the page ends with page.end_reason platform_limit. Each ranking and category is its own board: read several for more videos.
- The window is Creative Center's latest published one, not the days up to now. platform_specific.tiktok.board.period_end_at says where it ends.
- metrics.view_count is the video's lifetime views as Creative Center counts them, paid ones included. Likes, comments, shares and duration aren't on the board and are null (the TikTok Post API has them).
- A video that drew paid views is marked is_ad and is_sponsored, and the board says nothing more about the ad.
- A country Creative Center publishes no video board for returns invalid_input naming the countries it does.
1 credit per page. Failed requests are free.
Buy credits once and spend them on any endpoint, on any platform.
| Source | Price per successful page | Per 1,000 pages |
|---|---|---|
Platform API api | 1 credit | $0.75–$1.97 |
- Cache hits are free.
- Credits from packs and top-ups never expire.
- A monthly plan's credits are spent first, and what is left of them ends at each renewal.
- Prices exclude tax: sales tax is added at checkout where it applies.
- Outside the US, plans and packs are sold to businesses: checkout asks for your VAT, GST or other business tax ID.
Questions about the TikTok Top Videos API.
More in the API reference.
How much does the TikTok Top Videos API cost?
How do I get the next page?
See what's on the list right now.
Run it in the playground, or copy the request into your code.
- No credit card
- Failed requests are free
- Works on all 218 endpoints