Reading a TikTok account's follower list through an API is a two-call job. The user-followers endpoint is keyed on a numeric account ID, and that ID is not something you can read off a profile URL - tiktok.com/@handle gives you the handle and nothing else. So step one is resolving the handle with userinfo-by-username, and step two is paging the follower list with the ID that first call returns. This guide walks both steps with working code, lists the fields you actually get in a follower record, explains the count plus time cursor paging model, and works out what a full crawl of a large account costs in credits and wall-clock time.
Why it takes two calls
TikTok uses three separate identifiers for one account, and they are not interchangeable. The handle (uniqueId) is what people type and what the user can change. The numeric ID (id) is permanent. The secUid is an opaque string that some content endpoints expect. The follower endpoint wants the numeric ID specifically, which is why the handle you started with is not enough on its own. If the difference between those identifiers is still fuzzy, the walkthrough on what a TikTok secUid is and how to get one pulls them apart in detail.
The good news is that the resolution step is a one-off. Numeric IDs do not change when someone renames their account, so once you have stored the ID for a creator you can re-crawl their followers indefinitely without spending another credit on the lookup. Only new accounts entering your pipeline need the first call.
Step 1: resolve the handle to a numeric ID
The base URL is https://api.primeapi.co/ and authentication is a single header, X-PrimeAPI-Key, which you copy from your profile page after registering. There is no OAuth flow and no TikTok developer platform account involved - PrimeApi is an independent service. New accounts include 50 free credits, which is enough to test this whole flow end to end.
curl -s "https://api.primeapi.co/userinfo-by-username?username=taylorswift" \
-H "X-PrimeAPI-Key: YOUR_API_KEY"
Two fields matter here. user.id is the numeric string you feed to the follower endpoint. stats.followerCount tells you in advance how big the job is going to be, which is the number you use to budget credits before you start. The same call also returns user.secUid, user.nickname, user.signature, user.verified, user.privateAccount and user.secret - keep the last two, because a private account has no readable follower list and there is no point queueing one. The rest of the profile payload is covered in the guide to fetching TikTok user data via API, and if the follower total is the only thing you need, reading a follower count stops at this single call for one credit.
Step 2: page the follower list
The follower endpoint takes one required parameter, userid, and two optional ones: count for the page size and time for the cursor. On the first call leave time empty.
curl -s "https://api.primeapi.co/user-followers?userid=6763595241277129734&count=50&time=" \
-H "X-PrimeAPI-Key: YOUR_API_KEY"
The response has four top-level keys: followers (the array of records), total, time and hasMore. Those last three are the entire paging contract, so it is worth being precise about each one.
total, time and hasMore
total is the follower figure reported alongside the page. Use it for progress reporting and sanity checks, but do not treat it as a promise about how many records you will eventually collect, and do not assume it matches stats.followerCount from step one to the digit - they are two different counters read at two different moments.
time is an integer cursor. Whatever value comes back in the response body is what you send as the time parameter on the next request. It is a position marker, not a filter you can pick arbitrarily, so never construct one yourself.
hasMore is a boolean and it is the only correct loop condition. Stop when it turns false. A loop written as "keep going until I have collected total records" will hang on a large account, because the list can end before the counter says it should. Belt and braces: stop on hasMore being false, on an empty followers array, or on a time value identical to the previous one.
What a follower record contains
Each entry in followers is a compact profile, not just an ID. That matters for cost, because it means you usually do not need a follow-up lookup per follower.
| Field | Type | What it holds |
|---|---|---|
id | string | Numeric account ID of the follower. |
unique_id | string | The public handle, for example toms.paulo97. |
nickname | string | Display name. |
sec_uid | string | The secUid for this follower, ready to pass to content endpoints. |
signature | string | Bio text. |
avatar | string | CDN URL for the profile picture. |
region | string | Two-letter country code, for example PT. |
verified | bool | Verification badge. |
secret | bool | Whether the follower's own account is locked down. |
aweme_count | int | How many videos this follower has posted. |
follower_count | int | The follower's own audience size. |
following_count | int | How many accounts they follow. |
favoriting_count | int | Likes they have given out. |
total_favorited | int | Likes their own videos have received. |
ins_id, twitter_name, twitter_id, youtube_channel_title, youtube_channel_id | string | Linked external accounts. Empty strings when nothing is linked. |
Those numeric fields are what make the endpoint useful beyond a raw name dump. region gives you an audience geography breakdown with no extra calls. aweme_count, follower_count and total_favorited together let you separate real accounts from the long tail of empty profiles - a record with zero videos, zero followers and zero likes received is not an audience member you should be counting. And because sec_uid ships inside every record, you can hand a follower straight to a content endpoint without resolving them again, which is the cheap path into pulling every video from a TikTok user.
A complete pager
Both versions below do the same thing: resolve the handle, then loop pages until hasMore is false or a page cap is reached. The page cap is deliberate. It is the difference between a script that spends 200 credits and one that spends 20,000 because someone pointed it at the wrong account.
Node.js (Axios)
const axios = require("axios");
const KEY = "YOUR_API_KEY";
const client = axios.create({
baseURL: "https://api.primeapi.co/",
headers: { "X-PrimeAPI-Key": KEY },
timeout: 20000
});
async function resolveId(username) {
const res = await client.get("userinfo-by-username", { params: { username } });
return res.data.user.id;
}
async function getFollowers(userid, { count = 50, maxPages = 20 } = {}) {
const out = [];
let time = "";
for (let page = 0; page < maxPages; page++) {
const res = await client.get("user-followers", { params: { userid, count, time } });
const body = res.data;
const batch = body.followers || [];
out.push(...batch);
console.log(`page ${page + 1}: +${batch.length} (${out.length}/${body.total}) credits left ${res.headers["x-primeapi-balance"]}`);
if (!body.hasMore || batch.length === 0 || body.time === time) break;
time = body.time;
}
return out;
}
(async () => {
const id = await resolveId("taylorswift");
const followers = await getFollowers(id);
console.log(followers.slice(0, 3).map(f => `${f.unique_id} (${f.region})`));
})().catch(e => console.error(e.response ? e.response.data : e.message));
Python (Requests)
import time as clock
import requests
KEY = "YOUR_API_KEY"
BASE = "https://api.primeapi.co/"
HEADERS = {"X-PrimeAPI-Key": KEY}
def resolve_id(username):
r = requests.get(BASE + "userinfo-by-username",
params={"username": username}, headers=HEADERS, timeout=20)
r.raise_for_status()
return r.json()["user"]["id"]
def get_followers(userid, count=50, max_pages=20):
out, cursor = [], ""
for page in range(max_pages):
r = requests.get(BASE + "user-followers",
params={"userid": userid, "count": count, "time": cursor},
headers=HEADERS, timeout=20)
r.raise_for_status()
body = r.json()
batch = body.get("followers") or []
out.extend(batch)
print(page + 1, len(batch), len(out), body.get("total"),
r.headers.get("X-PrimeAPI-Balance"))
if not body.get("hasMore") or not batch or body.get("time") == cursor:
break
cursor = body["time"]
clock.sleep(0.7) # stay under 100 requests per minute
return out
if __name__ == "__main__":
uid = resolve_id("taylorswift")
rows = get_followers(uid)
for f in rows[:3]:
print(f["unique_id"], f["region"], f["follower_count"])
Note the balance header in both. Every response carries X-PrimeAPI-Balance, so a long-running crawl can watch its own quota drain and stop itself before it hits zero rather than discovering the problem in an error message.
Credit math for large accounts
Every request costs exactly 1 credit, so the arithmetic is simple: total calls equals followers divided by page size, plus one for the handle lookup. The default rate limit of 100 requests per minute sets the floor on how long the job takes.
| Follower base | Calls at count=50 | Credits | Time at 100 req/min |
|---|---|---|---|
| 5,000 | 100 | 101 | about 1 minute |
| 50,000 | 1,000 | 1,001 | about 10 minutes |
| 100,000 | 2,000 | 2,001 | about 20 minutes |
| 1,000,000 | 20,000 | 20,001 | about 3 hours 20 minutes |
Convert that to money with the tier rates on the pricing page. Pro is $59.90 for 50,000 credits, about $0.0012 per call; Ultra is $99.90 for 150,000, about $0.00067; Enterprise is $279.00 for 500,000, about $0.00056. So the million-follower crawl above is roughly $24 of Pro credits or about $13 on Ultra. The 100,000-follower job is around $2.40 on Pro. Basic, at $9.90 for 2,500 credits, comfortably covers a few mid-size accounts or a lot of first-page sampling.
The cheapest optimisation is to ask whether you need the whole list at all. One page of 50 records costs 1 credit and already gives you a region spread and an activity profile of recent followers. Sampling the first few pages answers most audience-quality questions for a fraction of the cost, and you can reserve full crawls for the handful of accounts that justify them. Where a full list does pay off is overlap analysis - crawl two creators, intersect on id, and you have a hard number for shared audience instead of a guess.
Limits worth knowing before you start
Private accounts are the first hard stop: check privateAccount and secret in step one and skip those handles. Beyond that, expect the list to be an ordered slice rather than a guaranteed complete census - hasMore can turn false before your running total reaches total, and your code should treat that as a normal ending, not an error. Responses are fetched live with no caching and average around a second, so a crawl reflects the follower list at the moment you ran it; re-running it next week will not produce a byte-identical result even if the count looks unchanged.
On the failure side, four conditions come back as a JSON message rather than data: a missing header gives "Please sign up to primeapi.co", a bad key gives "PrimeAPI-Key is not available", too many calls gives the per-minute limit message, and an empty wallet gives "Your balance has been exhausted...". Branch on that message body, because a pager that blindly writes an error object into its results array will corrupt a dataset quietly. The full reference lives in the API documentation, and you can try either endpoint with your own key, no code required, in the interactive playground.
Put together, the pattern is short: resolve once, store the numeric ID, page on time until hasMore is false, cap your pages, and watch the balance header. That is a follower list you can rebuild on a schedule with predictable, one-credit-per-page economics.
Frequently asked questions
Can I pass a username straight to user-followers?
No. The userid parameter is required and it is the numeric account ID, not the @handle. Call userinfo-by-username first, read user.id from the response, and pass that value as userid. Store the ID once - it does not change when the handle does, so you never have to repeat the lookup for the same account.
How many followers come back in one request?
The count parameter sets the page size, and a request with count=50 returns a followers array of 50 records. Treat count as a hint rather than a guarantee: always loop over the array you actually received instead of assuming its length, and use the hasMore flag to decide whether to make another call.
What is the time parameter for?
It is the paging cursor. Leave time empty on the first call, then read the time value from the response body and send it back as the time parameter on the next request. Each response therefore hands you the cursor for the page after it, and you stop when hasMore is false.
How many credits does a full follower crawl cost?
One credit per request, plus one for the initial handle lookup. At 50 followers per page that is roughly 2,000 credits for a 100,000-follower account and 20,000 credits for a million-follower account. At the default limit of 100 requests per minute those two jobs take about 20 minutes and about three and a half hours respectively.
Can I read the follower list of a private account?
No. If userinfo-by-username reports privateAccount or secret as true for the profile, treat the account as off limits and skip it rather than burning credits on calls that return nothing useful. Check those two flags before you queue a crawl.
Why are the follower field names different from the profile endpoint?
The two endpoints come from different upstream shapes. A profile from userinfo-by-username uses camelCase - uniqueId, secUid, followerCount - while a follower record uses snake_case: unique_id, sec_uid, follower_count. Normalise both into your own field names at the ingest layer so the rest of your code only sees one naming style.