How to Get TikTok Audience Demographics with an API (2026)
Pull followers, US audience share, engagement rate, and median video views for any public TikTok handle — three GET requests, one research row. Country-level audience demographics included.
Creator research usually starts the same way: open a TikTok profile, note the follower count, guess whether the audience is in your market, scroll recent videos, and punch numbers into a spreadsheet. That works for five handles. It falls apart for fifty.
This guide shows how to pull the four numbers most shortlists actually need — followers, US audience %, engagement rate, and median video views — with three Social Fetch requests and one research row per handle.
You'll need an API key and curl or the TypeScript SDK. New here? Start with the Quickstart.
The research brief
| Metric | Where it comes from |
|---|---|
| Followers | GET /v1/tiktok/profiles/{handle} → data.metrics.followers |
| US audience % | GET /v1/tiktok/profiles/{handle}/audience → row with countryCode === "US" |
| Median video views | GET /v1/tiktok/profiles/{handle}/videos → median of last 20 stats.views |
| Engagement rate | Same videos + followers → avg(likes + comments + shares) ÷ followers × 100 |
The short version
Three GET requests, one research row. Profile and videos are 1 credit each; audience is 30. Budget about 32 credits per creator on completed lookups.
The audience call is the piece most teams are missing — country-level demographics for a public handle without owning the account. Full reference: Get TikTok profile audience.
Why manual creator research breaks
Hand-rolled shortlists fail in predictable ways:
- Followers are vanity. A million followers with almost none in your shipping markets is a bad buy for a US-focused brand.
- Geo is owner-only in TikTok Studio. Third parties cannot open another creator's analytics panel. Guessing from comments or language is noisy.
- Engagement math drifts. Some tools divide by views, some by followers, some include saves. Without a fixed formula, agencies cannot compare creators.
- Means lie. One breakout video inflates "average views." Median over a fixed recent window is stabler for vetting.
An API does not remove judgment — it removes the copy-paste loop so your team can apply the same brief to every handle.
Metric 1: Followers
Start with the profile. data.metrics.followers is the denominator for engagement and the size signal on every shortlist.
curl -sS \
-H "x-api-key: $SOCIALFETCH_API_KEY" \
"https://api.socialfetch.dev/v1/tiktok/profiles/charlidamelio"| What | Cost |
|---|---|
| Profile lookup | 1 credit |
Check data.lookupStatus before trusting metrics. found means you have a public profile; private or not_found means skip or flag the row. Details: Get TikTok profile.
Metric 2: US audience share
Audience location is the new piece. Pass the same handle and read country rows under audienceLocations:
const response = await fetch(
"https://api.socialfetch.dev/v1/tiktok/profiles/charlidamelio/audience",
{
headers: {
"x-api-key": process.env.SOCIALFETCH_API_KEY,
},
}
);
const body = await response.json();
console.log(response.status, body);| What | Cost |
|---|---|
| Audience lookup | 30 credits |
Country-level only
Today this endpoint returns audience countries — not age or gender. Each row has country, countryCode, count, and percentage. Treat percentage as a string (for example "6.00%") and parse it for math. The count field is an upstream sample size, not total followers.
For a US-focused brief, find the row where countryCode is US and parse the percentage:
const usRow = data.audienceLocations?.find((r) => r.countryCode === "US");
const usAudiencePct = usRow ? Number.parseFloat(usRow.percentage) : null;
// usAudiencePct === 6 from "6.00%"Swap US for any ISO code when the brief targets another market.
Reading the audience response
A trimmed successful response looks like this:
{
"data": {
"lookupStatus": "found",
"profile": {
"platform": "tiktok",
"handle": "charlidamelio",
"profileUrl": "https://www.tiktok.com/@charlidamelio"
},
"audienceLocations": [
{
"country": "United States",
"countryCode": "US",
"count": 18,
"percentage": "6.00%"
},
{
"country": "Brazil",
"countryCode": "BR",
"count": 12,
"percentage": "4.00%"
}
]
},
"meta": {
"requestId": "req_01example",
"creditsCharged": 30,
"version": "v1"
}
}Fields that matter:
data.lookupStatus—foundornot_found. HTTP 200 alone is not enough.data.audienceLocations— country rows when found;nullwhen the profile could not be resolved.meta.creditsCharged— 30 on a completed audience lookup.meta.requestId— keep this for support if a row looks wrong.
Metrics 3 and 4: Median views and engagement rate
List recent videos, then derive reach and engagement from stats:
const response = await fetch(
"https://api.socialfetch.dev/v1/tiktok/profiles/charlidamelio/videos",
{
headers: {
"x-api-key": process.env.SOCIALFETCH_API_KEY,
},
}
);
const body = await response.json();
console.log(response.status, body);| What | Cost |
|---|---|
| Videos page | 1 credit |
Pass sortBy=latest when you want a chronological window (recommended for the median). Each video includes stats.views, likes, comments, and shares. Reference: List TikTok profile videos.
The formulas
Median video views — take the last 20 videos (or fewer if the page is shorter), collect stats.views, sort ascending, take the median. Prefer median over mean so one viral outlier does not dominate the shortlist score.
Engagement rate — for those same videos, average likes + comments + shares, divide by followers, multiply by 100:
engagementRate = (avg(likes + comments + shares) / followers) * 100Worked example with 3 videos and 100,000 followers:
| Video | Likes | Comments | Shares | Sum |
|---|---|---|---|---|
| A | 1,200 | 80 | 40 | 1,320 |
| B | 900 | 50 | 30 | 980 |
| C | 1,500 | 100 | 60 | 1,660 |
Average engagement = (1,320 + 980 + 1,660) / 3 = 1,320.
Engagement rate = 1,320 / 100,000 × 100 = 1.32%.
One script, one research row
Parallel-fetch all three routes and print the four fields:
import { SocialFetchClient } from "@socialfetch/sdk";
const client = new SocialFetchClient({
apiKey: process.env.SOCIALFETCH_API_KEY!,
});
const handle = "charlidamelio";
const VIDEO_WINDOW = 20;
const [profileRes, audienceRes, videosRes] = await Promise.all([
client.tiktok.getProfile({ handle }),
client.tiktok.getProfileAudience({ handle }),
client.tiktok.getProfileVideos({ handle, sortBy: "latest" }),
]);
if (!profileRes.ok) {
console.error(profileRes.error.code, profileRes.error.requestId);
process.exit(1);
}
if (!audienceRes.ok) {
console.error(audienceRes.error.code, audienceRes.error.requestId);
process.exit(1);
}
if (!videosRes.ok) {
console.error(videosRes.error.code, videosRes.error.requestId);
process.exit(1);
}
const profile = profileRes.value.data;
const audience = audienceRes.value.data;
const videos = videosRes.value.data.videos;
if (profile.lookupStatus !== "found" || audience.lookupStatus !== "found") {
console.log({ handle, lookupStatus: profile.lookupStatus });
process.exit(0);
}
const followers = profile.metrics?.followers ?? 0;
const usRow = audience.audienceLocations?.find(
(row) => row.countryCode === "US",
);
const usAudiencePct = usRow
? Number.parseFloat(usRow.percentage)
: null;
const window = videos.slice(0, VIDEO_WINDOW);
const views = window.map((v) => v.stats.views).sort((a, b) => a - b);
const medianViews =
views.length === 0
? null
: views.length % 2 === 1
? views[Math.floor(views.length / 2)]!
: (views[views.length / 2 - 1]! + views[views.length / 2]!) / 2;
const avgEngagement =
window.length === 0
? 0
: window.reduce((sum, v) => {
const s = v.stats;
return sum + s.likes + s.comments + s.shares;
}, 0) / window.length;
const engagementRate =
followers > 0 ? (avgEngagement / followers) * 100 : null;
console.log({
handle,
followers,
usAudiencePct,
medianViews,
engagementRate,
creditsCharged:
profileRes.value.meta.creditsCharged +
audienceRes.value.meta.creditsCharged +
videosRes.value.meta.creditsCharged,
});That is the whole brief: same inputs every time, same formulas, meta.creditsCharged summed so you can budget a roster run.
Batching tips
- Loop handles, fail soft. Check
lookupStatusper creator; writenot_foundinto the sheet instead of aborting the batch. - Cache audience. At 30 credits it is the expensive call — re-use it when you only refresh views and engagement weekly.
- Do not confuse
countwith followers. Audiencecountis a sample segment size; size the creator withmetrics.followers. - Keep the window fixed. Always use the same N (20) so engagement and median views stay comparable across the roster.
- Preflight private accounts. Videos has no
lookupStatus— call profile first when empty lists would mislead a client.
For broader TikTok scraping patterns (pagination, DIY tradeoffs), see How to scrape TikTok data. For multi-network identity before you spend, see Cross-platform creator profiles.
What you can build
- Influencer shortlist exporter — CSV or Notion row per handle with the four metrics and a geo threshold filter (for example US audience ≥ 40%).
- Agency pre-brief — auto-fill the research section of a pitch deck before a human reviews creative fit.
- CRM enrichment — attach followers, US %, median views, and engagement to every TikTok handle in your pipeline.
- Scheduled re-score — cron the same three calls weekly; flag creators whose US share or engagement drifts past a threshold.
FAQ
How is engagement rate calculated?
Average likes + comments + shares across the last N videos (we use 20), divided by follower count, multiplied by 100. That matches how most creator-research tools compute profile-level engagement. Saves are not included in this formula.
Why median video views instead of the mean?
One viral outlier can pull the average far above what a typical post reaches. The median of the last 20 videos is a stabler estimate of "usual" reach for shortlisting.
Does this include age or gender demographics?
Not yet. The audience endpoint returns country-level location rows only (country, countryCode, count, percentage). Age and gender splits are not in the response today.
How many credits does a full research row cost?
About 32 credits per creator on completed lookups: 1 for profile, 30 for audience, 1 for a page of videos. Audience is the expensive call — cache it when you re-score the same handles often. See the capability matrix.
Is this the same as TikTok Studio audience analytics?
No. TikTok Studio shows the account owner first-party age, gender, and location panels. This API returns third-party country-level audience signals for any public handle you pass — useful for vetting creators you do not control.
Can I use a country other than the US?
Yes. Find the row whose countryCode matches the market you care about (for example BR, GB, or DE) and parse that percentage the same way.
Next steps: Audience endpoint reference · Scrape TikTok profiles & videos · Cross-platform creator profiles · Quickstart · Pricing