Tiktok Profile Scraper avatar

Tiktok Profile Scraper

Pricing

$14.99/month + usage

Go to Apify Store
Tiktok Profile Scraper

Tiktok Profile Scraper

TikTok Profile Scraper extracts detailed data from any public TikTok profile, including username, bio, followers, following, likes, and profile stats. Ideal for analytics, influencer research, and marketing insights. Get structured JSON or CSV outputs via Apify’s fast, reliable platform.

Pricing

$14.99/month + usage

Rating

5.0

(4)

Developer

Scraper Engine

Scraper Engine

Maintained by Community

Actor stats

0

Bookmarked

172

Total users

2

Monthly active users

3 days ago

Last modified

Share

TikTok Profile Scraper — Views, Likes, Captions and Video URLs

TikTok Profile Scraper extracts the public post feed of any TikTok creator and returns one structured JSON row per post: view, like, comment, share and save counts, the caption with parsed hashtags and mentions, sound name and artist, video dimensions and duration, media URLs, plus the creator's handle, nickname, verified flag and follower count. Thirty-six keys per row, no HTML parsing, no TikTok login. Paste usernames or profile URLs and press Start.

⚠️ This Actor writes one row per post, not one row per profile. There is no profile-only mode: the creator's identity and follower count are repeated on every post row, and a creator with no posts inside your window produces an uncharged error row instead of a profile record. If you want a single record per creator, take the first row per creatorUsername and ignore the post fields.

What is TikTok Profile Scraper?

TikTok Profile Scraper is an Apify Actor that loads a public TikTok profile page, reads the creator's secUid out of the page's own rehydration JSON, then paginates TikTok's /api/creator/item_list/ feed endpoint — and optionally the /api/repost/item_list/ reposts feed — to build one flat, typed dataset row per post.

No TikTok account, login, password or session cookie is required, and none is accepted. The Actor is anonymous by design: it sees only what a signed-out visitor is served. The verifyFp token and device_id it sends are generated fresh per run rather than lifted from a signed-in session.

It is built for influencer marketers vetting creators, social analysts benchmarking content performance, growth teams tracking hashtag and sound usage, and developers piping post-level engagement into a warehouse, dashboard or AI agent.

What TikTok profile data is publicly available to scrape?

TikTok serves a signed-out visitor a public creator's whole identity block and post grid — handle, nickname, avatar, bio, follower and like totals, and every post with its own caption, sound and engagement counters. What it withholds is relationship lists, private and friends-only content, age-gated posts and anything belonging to a private account.

Data CategorySigned-out visitorGated behind a TikTok login or the account owner
Handle, nickname, avatar, bio text✅ Public
Follower count, following count, total likes, post count✅ Public
Verified badge, TikTok Shop seller flag, commerce-account flag✅ Public
Post grid: caption, views, likes, comments, shares, saves✅ Public
Sound name, sound author, original-sound flag, sound cover✅ Public
Video dimensions, duration, cover images, subtitle tracks✅ Public
Reposted content the creator shared✅ Public
Place tags on the posts that carry one✅ Public
Full follower and following listsLogin required
Friends-only, private and draft postsAccount owner only
Age-restricted posts and region-blocked contentLogin and region eligibility
Anything on a private accountApproved follow required — never returned
Direct messages and Creator Center analyticsAccount owner only
Comment text under each postPublicly visibleNot part of this Actor's output — see TikTok Comments Scraper

TikTok Profile Scraper only returns publicly visible data — what any signed-out visitor sees on the profile page. Nothing behind a login wall.

⚠️ Sorting and filters apply only to the posts this run fetched

This is the single most important thing to understand before you set resultsPerPage, and it belongs here rather than buried in the input table.

TikTok's profile feed endpoint always returns posts newest-first and offers no server-side sorting. The Actor probed that endpoint's type parameter with values 0, 1 and 2 and got the same newest-first window back every time, so it pins type=1 and does all ordering itself, in memory, over the posts it actually downloaded.

The consequence: profileSorting and every filter — oldestPostDateUnified, newestPostDate, leastDiggs, mostDiggs — are applied after fetching, to that window only.

  • "popular" gives you the highest-playCount posts among the newest resultsPerPage posts. It does not find a creator's all-time top video.
  • "oldest" gives you the oldest post in the fetched window, not the creator's first-ever post.
  • Setting oldestPostDateUnified to a date older than the window reaches does not extend the window — it simply filters nothing out.

To reach further back, raise resultsPerPage. TikTok's feed endpoint hands back 10 posts per request (its count parameter is hard-capped at 10 and answers HTTP 400 above that), so each extra 10 posts is one more sequential request with a 1–2 second pause in front of it.

What data can I extract with TikTok Profile Scraper?

Every charged row carries five things: which post it is, what the caption says, how it performed, what sound and video file sit behind it, and who posted it.

Field NameDescription
idTikTok's numeric post id, as a string. A row without one is never pushed
inputThe normalised handle this row was collected for — @ stripped, URL resolved. Not the literal string you passed
fromProfileSection"videos" or "reposts" — which feed the post came from
creatorUsernameThe creator's handle from the post's author object, falling back to your input
textFull caption text, line breaks and emoji preserved. "" when the post has no caption
textLanguageTikTok's own language code for the caption, or null. Never defaulted to "en"
createTimePost timestamp as a Unix epoch integer
createTimeISOThe same timestamp as real UTC ISO-8601 with a Z suffix, or null if unparseable
isAdTikTok's branded-content flag
isSponsoredThe same isAd value under a second name, for consumers that expect this key
isSlideshowtrue when the post is a photo carousel. Derived from the presence of imagePost — TikTok emits no isSlideshow key of its own
webVideoUrlCanonical post URL, rebuilt as https://www.tiktok.com/@{handle}/video/{id}
mediaUrlsArray of media URLs: every carousel image on a photo post, otherwise the video's playAddr and downloadAddr. [] when the payload carries neither
creatorNicknameDisplay name
creatorVerifiedVerified-badge flag
creatorFollowersFollower count as an integer
creatorAvatarUrlMedium-resolution avatar URL
durationSecondsVideo length in seconds, 0 on photo posts
soundNameSound title
soundArtistSound author name
soundIsOriginaltrue for a creator's own original sound
playCountViews
diggCountLikes (hearts)
commentCountComments
shareCountShares
collectCountSaves / bookmarks
repostCountReposts
hashtagsArray of { "name": ... } objects, built from TikTok's textExtra entities — not regex-matched out of the caption
mentionsArray of { "id", "uniqueId", "nickname" } objects for accounts tagged in the caption
effectStickersArray of { "ID", "name", "stickerStats" } objects for effects used in the post
locationMetaPlace-tag object when the post carries one, {} otherwise
authorMeta18-key creator object — see the nested shapes below
musicMeta7-key sound object
videoMeta8-key video-file object including subtitle tracks
scrapedAtUTC ISO-8601 timestamp for when the row was written
runIdThe Apify run id that produced the row, or null if unavailable

Thirty-six keys are written on every charged post row. errorReason is the only key that appears on uncharged rows and never on a charged one, which is what makes it the clean thing to filter on.

Post, caption and entity fields

id, webVideoUrl and createTimeISO identify the post; text, hashtags, mentions and effectStickers describe it. hashtags and mentions are built from TikTok's textExtra entity list rather than from a regex over the caption, so a tag containing punctuation or a non-Latin script survives intact and a mention resolves to a real id and uniqueId instead of just a string.

input and creatorUsername are deliberately separate. input is the handle the Actor normalised from your entry; creatorUsername is the handle TikTok's own author object returned. When the two disagree, the handle you asked for has been renamed and TikTok served you the new one — assert on that pair rather than trusting your input list.

Engagement counters, flags and timing

playCount, diggCount, commentCount, shareCount, collectCount and repostCount are integers, read from TikTok's statsV2 block in preference to stats. That distinction matters: statsV2 carries the exact counters as strings while stats carries rounded display integers, so these are the precise numbers, coerced to int for you.

durationSeconds and creatorFollowers are integers too, and all count fields fall back to 0 rather than null when TikTok omits the key. isAd, isSponsored, isSlideshow, creatorVerified and soundIsOriginal are booleans that fall back to false. createTime is the raw epoch and createTimeISO is genuine UTC — not local time with a Z bolted on.

Nested objects: creator, sound, video and location

authorMeta carries 18 keys: id, name, profileUrl, nickName, verified, signature, originalAvatarUrl, avatar, commerceUserInfo, ttSeller, roomId, privateAccount, following, friends, fans, heart, video, digg. signature is the profile bio, falling back from the post's author object to the profile payload. heart is the creator's total likes across all posts, video the total post count, fans the follower count, friends the mutual-follow count. commerceUserInfo defaults to {"commerceUser": false} when TikTok omits it, and roomId is non-empty only while the creator is live.

musicMeta carries 7 keys: musicId, musicName, musicAuthor, musicOriginal, playUrl, coverMediumUrl, durationSeconds. Note that musicMeta.durationSeconds is the length of the sound, not of the video.

videoMeta carries 8 keys: height, width, duration, coverUrl, originalCoverUrl, definition, format (defaulting to "mp4") and subtitleLinks. Each subtitleLinks entry has 6 keys: language, downloadLink, tiktokLink, source ("ASR" or "MT"), sourceUnabbreviated ("automatic speech recognition" or "machine translation") and version. downloadLink and tiktokLink hold the same URL. Tracks are read from claInfo.captionInfos when present and from the older subtitleInfos block otherwise, so both payload generations map onto the same shape.

locationMeta carries 6 keys when the post is place-tagged — address, city, cityCode, countryCode, locationName, locationId — and is {} otherwise. The Actor checks the six different keys TikTok has used for this (poi, poiInfo, location, geoInfo, locationInfo, contentLocation) and takes the first populated one. Most posts are not place-tagged, so expect {} on the majority of rows; the run log's closing MEASURED COVERAGE THIS RUN line reports how many rows in your specific run carried a place tag, a language code and media URLs.

Three naming quirks to map around deliberately. The top-level row is camelCase, but authorMeta.nickName uses a capital N and effectStickers[].ID is fully uppercase — both preserved from TikTok's own payload rather than silently tidied. Several values are mirrored: creatorFollowers equals authorMeta.fans, creatorAvatarUrl equals authorMeta.avatar, soundName/soundArtist/soundIsOriginal mirror musicMeta.musicName/musicAuthor/musicOriginal, durationSeconds mirrors videoMeta.duration, and isSponsored mirrors isAd. Those mirrors exist because Apify dataset views can only select top-level keys — a dotted path like authorMeta.avatar renders an empty column — so the flat copies drive the output table while the nested objects stay for full-fidelity exports.

🤖 Add-on: Need additional TikTok data?

A post feed answers "what did this creator publish, and how did it do". TikTok Comments Scraper By Real Comment Filter takes the webVideoUrl values from these rows and pulls the discussion under each one, filtered down to real comments. Tiktok Video Scraper (Creator Profile & Contact) works the other way around — start from individual video URLs and get the creator's profile and public contact details attached. For discovery rather than a known handle, TikTok Trending Hashtags Scraper By Multiple Countries and Tiktok Trending Hashtags Analytics Scraper By Country surface what is rising before you pick creators to track.

How does TikTok Profile Scraper differ from TikTok's official APIs?

TikTok publishes two developer APIs, and neither reads an arbitrary third-party creator's profile on demand. The Display API returns profile and video data only for users who have completed an OAuth login with your own registered app. The Research API is application-only, oriented toward academic and non-profit research, and access depends on an eligibility review. TikTok Profile Scraper reads the public profile page and its feed endpoint instead, which is why it accepts any public handle with no approval step.

FeatureTikTok Display APITikTok Research APITikTok Profile Scraper
Reads a creator you do not own and who has not authorised your app❌ Out of scopeOnly within an approved research scope✅ Any public profile
Approval before you can startApp registration and reviewApplication plus eligibility review❌ None
Credential you supplyA registered app plus a user OAuth tokenApproved research credentialsYour Apify token only
Per-post views, likes, comments, shares and savesFor the authorising user's own postsWithin the approved scope✅ For every post fetched
Output shapeTikTok's documented API schemaTikTok's documented research schemaOne flat row per post, stable key names
Setup pathImplement an OAuth flowSubmit an application and waitPaste handles, press Start
Request budgetPublished by TikTok — check its docsPublished by TikTok — check its docsBounded by your Apify run

Verify every constraint above against TikTok's current published developer documentation before choosing — those terms change, and no quotas or rate figures are reproduced here. Use the official APIs when the accounts belong to your own users and you need a supported contract, or when you qualify for research access. Use TikTok Profile Scraper for competitive and creator research across accounts you have no authorisation over.

How to use TikTok Profile Scraper

TikTok Profile Scraper runs on Apify. Start it from the Apify Console or call it through the Apify API — the only credential involved is your Apify token.

  1. Open TikTok Profile Scraper on Apify and click Try for free
  2. Add one or more entries to TikTok profiles (profiles) — this is the only required input. A bare handle, an @handle and a full profile URL all work
  3. Set Maximum posts per profile (resultsPerPage) — default 20, maximum 500. This is the size of the window everything else operates on
  4. Optionally tick Reposts under Profile sections (profileScrapeSections), pick an Order results value (profileSorting), and add date or like-count filters
  5. Leave Proxy (proxyConfiguration) alone unless you see repeated blocks — the Actor escalates on its own
  6. Click Start, then export the dataset as JSON, CSV, Excel or XML, or read it through the Apify API

A run with no profiles does not crash and does not fail. The Actor logs No TikTok profiles supplied. Nothing to do., writes nothing at all — not even an error row — exits successfully, and charges nothing. The same happens if every entry you pass is blank. The Console blocks an empty submission because the field is required, so this only reaches callers that assemble the input JSON themselves.

How to scale to bulk TikTok profile extraction

profiles is a list, so bulk is the normal mode — add as many handles as you need in one run. Neither the input schema nor the code caps how many profiles a single run may contain; the practical ceiling is the memory and timeout you give the run on Apify. A comma-separated or newline-separated single string is also accepted and split for you, so a handle list produced by another tool usually feeds in unchanged.

Profiles are processed strictly one after another through one warm HTTP session, and every row for a profile is written before the next handle starts. Duplicates are not removed across your input list: the same handle twice is scraped twice and charged twice, so deduplicate before you run.

What can you do with TikTok profile and post data?

  • 📊 An influencer marketer vetting a creator shortlist divides diggCount and commentCount by creatorFollowers across a 100-post window to get a real engagement rate, then drops accounts whose playCount is carried by a single viral outlier.
  • 🎵 A social strategist planning a campaign counts soundName and soundIsOriginal across a competitor's recent posts to see which sounds they ride versus produce, and reads hashtags[].name to reconstruct their tag strategy.
  • 💰 A brand-safety analyst auditing paid partnerships filters on isAd and reads text and mentions to catalogue every disclosed collaboration a creator ran inside a range set with oldestPostDateUnified.
  • 📈 A content researcher testing a posting hypothesis exports createTimeISO, playCount and durationSeconds for a creator's last 500 posts and correlates length and posting time against views.
  • 🖼️ A creative team building a swipe file pulls mediaUrls and videoMeta.coverUrl for the strongest posts of ten creators in one run, using profileSorting: "popular" over a wide window.
  • 🤖 An AI engineer building a creator-research agent indexes text, hashtags and soundName into a vector store with creatorUsername, playCount and createTimeISO as metadata filters, so the agent can answer "which creators in this niche posted about X last month, and how did it perform".

Every one of these is callable from an agent framework over the Apify API, since the Actor is a standard HTTP-triggered run.

How does TikTok Profile Scraper handle rate limits and blocking?

TikTok's WAF does not answer a blocked request with an HTTP error — it answers with HTTP 200. A challenged profile page comes back as a roughly 1.4 KB body carrying a _wafchallengeid marker; a refused signed API call comes back as HTTP 200 with zero bytes. So this Actor never keys block detection on status code. It checks for the WAF markers, for the presence of the page's rehydration script, and for a body shorter than 20,000 bytes — a real profile document is around 200 KB.

Transport is curl_cffi with a TLS-and-HTTP fingerprint impersonation ladder led by chrome99_android, falling through chrome131_android, safari18_0_ios, safari17_2_ios and chrome131. A plain desktop HTTP client is exactly what TikTok's WAF challenges, which is why no aiohttp-style transport is used — and why no browser is needed either.

Escalation is cheapest-rung-first. Every run starts on the container's direct IP with no proxy at all. On a block the Actor rotates the fingerprint; once it has cycled the whole ladder it climbs a proxy tier — direct → Apify datacenter → Apify residential — logging each switch. A profile page gets up to 5 attempts, with an escalation and a growing pause between each; each feed page gets up to 3. There is no CAPTCHA solving in this Actor, and none is claimed.

When a profile cannot be read at all, the run does not stop. The Actor writes one uncharged error row naming the reason and moves on to the next handle.

⬇️ Input

Nine parameters, one of them required. Everything below is read from the input schema as shipped.

ParameterRequiredTypeDescriptionExample Value
profilesYesarrayTikTok usernames or profile URLs, one per line (stringList editor). A leading @ and a full https://www.tiktok.com/@handle URL are both accepted. Prefilled with ["mrbeast"]. No minimum or maximum count.["mrbeast", "@khaby.lame", "https://www.tiktok.com/@zachking"]
profileScrapeSectionsNoarrayWhich feeds to collect (select editor). Enum: videos (the creator's own posts), reposts (content the creator reposted — a much smaller feed; many profiles have none). Default ["videos"].["videos", "reposts"]
profileSortingNostringOrder applied to the posts this run fetched (select editor). Enum: latest (Newest first), popular (Most viewed), oldest (Oldest first) — all three within the fetched window only. Default "latest"."popular"
resultsPerPageNointegerUpper bound on posts collected per profile. Minimum 1, maximum 500, default 20. TikTok's feed endpoint returns 10 posts per request, so higher values mean more sequential requests and longer runs.100
oldestPostDateUnifiedNostringKeep only posts published on or after this date (datepicker editor, absolute or relative). Absolute YYYY-MM-DD, or a number of days ago. No default — no lower bound when omitted."2026-01-01"
newestPostDateNostringKeep only posts published on or before this date (datepicker editor, absolute or relative). Absolute YYYY-MM-DD, or a number of days ago. No default."2026-07-20"
leastDiggsNointegerKeep only posts with at least this many likes. Minimum 1, no default. Inclusive.10000
mostDiggsNointegerKeep only posts with fewer than this many likes. Minimum 1, no default. Exclusive — a post with exactly this many likes is dropped.5000000
proxyConfigurationNoobjectApify Proxy settings (proxy editor). Prefilled with {"useApifyProxy": false} — the direct container IP. The Actor escalates to a datacenter and then a residential proxy on its own if a profile is blocked.{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}

Six honest notes on how these behave, all of which matter more to API callers than to Console users:

  • Every advertised profiles format really parses. A bare handle (mrbeast), an @handle (@khaby.lame), a full profile URL, a URL with a query string, and a URL with a trailing slash all resolve to the same handle. So does an individual post URL — https://www.tiktok.com/@zachking/video/7301… collapses to zachking — and so does the short tiktok.com/mrbeast form. The one thing to avoid is a non-profile TikTok URL: a hashtag or sound page reduces to its last path segment, which is then treated as a handle and produces an error row.
  • resultsPerPage does double duty, and reposts can surprise you. It is both the per-feed fetch target and the final per-profile row cap. With both sections enabled, the Actor fetches up to resultsPerPage videos and up to resultsPerPage reposts, merges them, filters, orders, then slices the combined list back down to resultsPerPage. Under the default latest ordering, recent reposts can therefore displace the creator's own posts. Run the two sections as separate runs when you need a guaranteed count of each.
  • Missing or unusable numbers fall back silently. Omit resultsPerPage and you get 20. Send 0, null or a non-numeric value and you also get 20. Out-of-range values are clamped to 1–500 by the code rather than rejected, so a caller that bypasses schema validation with resultsPerPage: 5000 gets 500 and no error.
  • An unparseable date silently disables that filter. oldestPostDateUnified and newestPostDate accept YYYY-MM-DD, YYYY-MM-DD HH:MM:SS, a plain number of days ago (7, 7 days, 7d), or a raw epoch. Anything else — 2026/07/01, last week, July 1 — parses to nothing and the filter is skipped without a warning. Both bounds are inclusive against the post's createTime, and a relative value resolves to midnight UTC on that day.
  • Unknown enum values are dropped rather than rejected. An entry in profileScrapeSections that is neither videos nor reposts is discarded, and if that empties the list it reverts to ["videos"]. A profileSorting value outside the three enums reverts to "latest". A bare string is accepted where the schema asks for an array.
  • proxyConfiguration genuinely defaults to no proxy. The shipped prefill is {"useApifyProxy": false}, and that really does mean a direct connection on the container IP — runs cleared TikTok's WAF from there in testing. If your own proxy configuration fails to build, the Actor logs a warning and continues on the direct IP rather than failing the run.

Example input

{
"profiles": [
"mrbeast",
"@khaby.lame",
"https://www.tiktok.com/@zachking"
],
"profileScrapeSections": ["videos"],
"profileSorting": "popular",
"resultsPerPage": 100,
"oldestPostDateUnified": "2026-01-01",
"newestPostDate": "2026-07-20",
"leastDiggs": 10000,
"mostDiggs": 5000000,
"proxyConfiguration": {
"useApifyProxy": false
}
}

⬆️ Output

Typed, normalized JSON with a consistent shape across runs — one row per post, 36 top-level keys on every charged row, with the nested authorMeta, musicMeta, videoMeta and locationMeta objects carried alongside flat mirrors of their most-used values. Rows are written as each profile completes, so the dataset fills while the run is still going. Export as JSON, CSV, Excel or XML from the Console, or read the dataset through the Apify API. The default dataset view shows 23 columns — avatar, handle, display name, verified flag, followers, caption, language, the six engagement counters, the branded and photo-post flags, duration, the three sound fields, posted date, section, post URL and errorReason — while the full row carries everything documented above.

On charging and uncharged rows. Each post row is pushed with one row_result charged event. A profile that cannot be read, or that yields nothing after filtering, instead produces a single uncharged accounting row, pushed with no charged event at all. Those rows are the only ones that carry errorReason, so that key is the filter:

items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
posts = [r for r in items if not r.get("errorReason")]
errors = [r for r in items if r.get("errorReason")]

An uncharged row carries 14 keys and omits every nested object: id (as error-<handle>), input, creatorUsername, fromProfileSection (null), text ("No data collected for @handle"), errorReason, webVideoUrl (the profile URL, not a post URL), createTimeISO, diggCount, playCount, commentCount and shareCount (all null), scrapedAt and runId. The errorReason values the Actor emits are profile_not_found, profile_private, profile_unavailable, tiktok_status_<code> for any other TikTok status, waf_challenge (last body N bytes), user_detail_missing, sec_uid_missing, no_posts_in_window, and unexpected_error: <message>.

Example output

One charged post row:

{
"id": "7492188104477312811",
"input": "mrbeast",
"fromProfileSection": "videos",
"creatorUsername": "mrbeast",
"text": "We built the world's largest LEGO tower and it took 40 days #lego #build #satisfying",
"textLanguage": "en",
"createTime": 1752408311,
"createTimeISO": "2026-07-13T11:25:11Z",
"isAd": false,
"isSponsored": false,
"isSlideshow": false,
"webVideoUrl": "https://www.tiktok.com/@mrbeast/video/7492188104477312811",
"mediaUrls": [
"https://v16-webapp-prime.tiktok.com/video/tos/useast2a/tos-useast2a-ve-0068c004/oQBAfeIzTgN/?a=1988&br=3506&mime_type=video_mp4",
"https://v16-webapp-prime.tiktok.com/video/tos/useast2a/tos-useast2a-ve-0068c004/oQBAfeIzTgN/?a=1988&dl=1&br=3506&mime_type=video_mp4"
],
"creatorNickname": "MrBeast",
"creatorVerified": true,
"creatorFollowers": 118402771,
"creatorAvatarUrl": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/8f4c1b2d9e7a3f5c~c5_720x720.jpeg",
"durationSeconds": 47,
"soundName": "original sound",
"soundArtist": "MrBeast",
"soundIsOriginal": true,
"playCount": 41288319,
"diggCount": 3912774,
"commentCount": 48211,
"shareCount": 191204,
"collectCount": 402887,
"repostCount": 1204,
"hashtags": [
{ "name": "lego" },
{ "name": "build" },
{ "name": "satisfying" }
],
"mentions": [
{
"id": "6725190283746152450",
"uniqueId": "chandlerhallow",
"nickname": "Chandler Hallow"
}
],
"effectStickers": [],
"locationMeta": {},
"authorMeta": {
"id": "6614519576030036486",
"name": "mrbeast",
"profileUrl": "https://www.tiktok.com/@mrbeast",
"nickName": "MrBeast",
"verified": true,
"signature": "SUBSCRIBE FOR A COOKIE",
"originalAvatarUrl": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/8f4c1b2d9e7a3f5c~c5_1080x1080.jpeg",
"avatar": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/8f4c1b2d9e7a3f5c~c5_720x720.jpeg",
"commerceUserInfo": { "commerceUser": false },
"ttSeller": false,
"roomId": "",
"privateAccount": false,
"following": 6,
"friends": 3,
"fans": 118402771,
"heart": 2214880031,
"video": 1842,
"digg": 118
},
"musicMeta": {
"musicId": "7492188091114438444",
"musicName": "original sound",
"musicAuthor": "MrBeast",
"musicOriginal": true,
"playUrl": "https://sf16-ies-music-va.tiktokcdn.com/obj/tos-useast2a-ve-2774/oIAe7bDcQ2f.mp3",
"coverMediumUrl": "https://p16-amd-va.tiktokcdn.com/img/tos-maliva-avt-0068/8f4c1b2d9e7a3f5c~c5_720x720.jpeg",
"durationSeconds": 47
},
"videoMeta": {
"height": 1024,
"width": 576,
"duration": 47,
"coverUrl": "https://p16-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/o0Qd2eBaFm~tplv-dmt-logom.image",
"originalCoverUrl": "https://p16-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/o0Qd2eBaFm~tplv-tiktokx-origin.image",
"definition": "720p",
"format": "mp4",
"subtitleLinks": [
{
"language": "eng-US",
"downloadLink": "https://v16-webapp.tiktok.com/subtitle/eng-US/7492188104477312811.webvtt",
"tiktokLink": "https://v16-webapp.tiktok.com/subtitle/eng-US/7492188104477312811.webvtt",
"source": "ASR",
"sourceUnabbreviated": "automatic speech recognition",
"version": "4"
}
]
},
"scrapedAt": "2026-07-25T09:41:07Z",
"runId": "kPq2xR7bNvL4mTz1a"
}

And one uncharged row, for a handle that turned out to be private:

{
"id": "error-privatecreator99",
"input": "privatecreator99",
"creatorUsername": "privatecreator99",
"fromProfileSection": null,
"text": "No data collected for @privatecreator99",
"errorReason": "profile_private",
"webVideoUrl": "https://www.tiktok.com/@privatecreator99",
"createTimeISO": null,
"diggCount": null,
"playCount": null,
"commentCount": null,
"shareCount": null,
"scrapedAt": "2026-07-25T09:41:22Z",
"runId": "kPq2xR7bNvL4mTz1a"
}

How does it work?

TikTok builds a profile page from a JSON blob embedded in the HTML, and it builds the post grid from a separate feed endpoint. TikTok Profile Scraper fetches the profile page over an impersonated mobile TLS session, reads webapp.user-detail out of the page's __UNIVERSAL_DATA_FOR_REHYDRATION__ script to get the creator's secUid and profile payload, then pages /api/creator/item_list/ with a descending timestamp cursor — and /api/repost/item_list/ with an integer index cursor when you ask for reposts — until it has the number of posts you requested or TikTok reports no more.

Each raw post is mapped onto fixed key names by explicit path, with a documented fallback per field, so a key TikTok moves degrades one field instead of breaking the row. Because the data comes from JSON rather than CSS selectors, a TikTok front-end redesign does not change your field names.

Only publicly visible data is returned. There is no login, no cookie and no session anywhere in the flow, so private accounts, friends-only posts and follower lists are out of reach by design rather than by omission.

Integrations

TikTok Profile Scraper is an Apify Actor, so it works with anything that can call the Apify API or read a dataset.

Calling TikTok Profile Scraper from Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<YOUR_USERNAME>/tiktok-profile-scraper").call(run_input={
"profiles": ["mrbeast", "@khaby.lame", "https://www.tiktok.com/@zachking"],
"resultsPerPage": 100,
"profileSorting": "popular",
"leastDiggs": 10000,
})
for post in client.dataset(run["defaultDatasetId"]).iterate_items():
if post.get("errorReason"):
print("skipped", post["input"], post["errorReason"])
continue
print(post["creatorUsername"], post["playCount"], post["diggCount"], post["webVideoUrl"])

Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request. Every charged row has the same 36 keys, so the errorReason check above is the only branch a consumer needs.

No-code tools (n8n, Make, LangChain)

In n8n, use the Apify node — or an HTTP Request node pointed at the Actor's run endpoint with your token — and pass the same JSON input shown above; add an IF node on errorReason and the flat rows drop straight into a Google Sheets or Postgres node. In Make, the Apify module supports run-and-wait, so a weekly creator-performance refresh can feed an Airtable step with no glue code. In LangChain, wrap the run endpoint as a tool and hand the rows to the model directly, since they are already typed JSON. Apify schedules and webhooks cover recurrence and completion triggers, so a monthly snapshot of a creator list needs nothing of your own.

Scraping publicly visible profile and post data is broadly treated as permissible where no login is used and no private account is accessed, and TikTok Profile Scraper does neither — it reads only what a signed-out visitor is served, and private accounts are inaccessible to it.

This output contains personal data about identifiable individuals. creatorUsername, creatorNickname, creatorAvatarUrl, authorMeta.id, authorMeta.nickName, authorMeta.signature, authorMeta.avatar, authorMeta.originalAvatarUrl and authorMeta.profileUrl describe a real person, as do creatorFollowers, authorMeta.fans, authorMeta.heart and authorMeta.following when attached to them. The post fields widen that further: text frequently names people, mentions carries the id, uniqueId and nickname of third parties who never appeared in your input, mediaUrls and videoMeta.coverUrl point at images and video of identifiable individuals, videoMeta.subtitleLinks transcribes what they said, and locationMeta places them at a named address.

Under GDPR, UK GDPR and CCPA you need a lawful basis before you store or reuse any of it. If you rely on legitimate interests, document the balancing test — your specific purpose, why scraped public data is necessary for it, and why the creators' rights and expectations are not overridden — before ingestion rather than after. Apply data minimisation at ingestion too: for engagement benchmarking, keep creatorUsername and the counter fields and drop the heaviest personal-data payload first — creatorAvatarUrl, authorMeta.avatar, authorMeta.originalAvatarUrl, authorMeta.signature, mediaUrls, videoMeta.subtitleLinks, locationMeta and mentions. Set a retention period that matches that purpose instead of warehousing rows indefinitely, be transparent in your privacy notice about collecting public TikTok data, and keep the creatorUsername-to-row mapping you would need to answer an access, objection or erasure request.

Consult legal counsel if your use case involves bulk storage of personal data.

❓ Frequently asked questions

What TikTok profile fields does TikTok Profile Scraper return?

The five most used are creatorUsername, playCount, diggCount, text and createTimeISO. Every charged row carries 36 top-level keys, including the nested authorMeta, musicMeta, videoMeta and locationMeta objects and the parsed hashtags and mentions arrays — see the data fields table above for all of them.

No. There is no login, no password and no cookie anywhere in the input schema or the code, and none can be supplied. The verifyFp token and device_id the Actor sends are generated randomly per run rather than lifted from a signed-in session, so nothing about a run is tied to an account. The only credential you need is your Apify token. The trade-off is that anything TikTok serves only to a logged-in session — follower lists, friends-only posts, age-gated content, private accounts — is out of reach.

How many posts can I extract per profile in one run?

Up to 500 per profile. That ceiling is the Actor's own, set in both the input schema's maximum and the code, and values above it are clamped rather than rejected. TikTok imposes a separate, smaller cap underneath: its feed endpoint accepts a count of at most 10 per request and answers HTTP 400 above that, so 500 posts means 50 sequential requests with a 1–2 second randomised pause between them. The reposts endpoint accepts 20 per request. There is no cap at all on how many profiles one run may contain.

What happens if a profile is private, does not exist, or was renamed?

Every case produces an uncharged row rather than a crash or a silent gap, and the run continues with the next handle.

A private account returns TikTok status 10222, which the Actor maps to errorReason: "profile_private". A non-existent, deleted or banned handle returns 10221 or 10202, mapped to "profile_not_found"; 10223 becomes "profile_unavailable"; any other non-zero status becomes tiktok_status_<code> so you can see the raw value. A profile page the Actor could load but not parse yields "user_detail_missing" or "sec_uid_missing", and one that never cleared TikTok's WAF after five attempts yields "waf_challenge (last body N bytes)" with the body size that failed.

A renamed handle usually still resolves, because TikTok redirects the old handle — which is exactly why input and creatorUsername are separate keys. Compare them: when they disagree, the account was renamed. If the old handle no longer resolves at all, you get "profile_not_found".

A profile that exists and is public but has no posts left after your date and like filters produces "no_posts_in_window" — widen resultsPerPage or loosen the filters.

Am I charged for profiles that fail?

No. Only post rows are pushed with the row_result charged event. Error and accounting rows are pushed with no charged event at all, and they are the only rows carrying errorReason, so if not row.get("errorReason") separates what you paid for from what you did not. A run whose profiles list is empty pushes nothing and charges nothing.

Can I scrape multiple TikTok profiles at once?

Yes. profiles is a list and there is no cap on its length in the schema or the code. Bare handles, @handles, full profile URLs and even individual post URLs can be mixed freely in the same list, and a single comma- or newline-separated string is split for you. Two caveats: duplicates are not removed, so the same handle twice is scraped twice and charged twice, and profiles run sequentially rather than in parallel, so a very long list is better split across scheduled runs than pushed into one.

Does TikTok Profile Scraper work with Claude, ChatGPT and other AI agent tools?

Yes. It is callable as a standard HTTP-triggered Apify run, so LangChain, CrewAI, n8n or a hand-written tool definition can start it and read back typed JSON with no parsing step. Give the agent one rule: skip rows where errorReason is present, because those are uncharged failure markers rather than posts.

How does TikTok Profile Scraper compare to other TikTok profile scrapers?

All observations below were checked on the Apify Store on 25 July 2026, and describe what each listing documents — not measurements of our own.

clockworks/tiktok-profile-scraper is the most used in this category and the closest structural match: its published output sample is one row per video with the creator nested under authorMeta, and it shares a large part of this Actor's key layout — id, text, createTime, createTimeISO, isAd, mediaUrls, hashtags, mentions, effectStickers, musicMeta, videoMeta, diggCount, playCount, collectCount, input — so switching either way needs little remapping. Its listing documents fields this Actor does not return: authorMeta.bioLink, authorMeta.region, isPinned, and videoMeta.downloadAddr / originalDownloadAddr, plus four media-download toggles (shouldDownloadVideos, shouldDownloadCovers, shouldDownloadSubtitles, shouldDownloadSlideshowImages). It documents error items marked by an errorCode field with a 17-code reference table; its README is silent on whether those items are billed. Its documented input is usernames only — profile-URL input is not documented — and it names no date-range or like-count parameters, though its FILTER_NO_PASS code refers to "date/popularity filters". Its README states pay-per-result pricing at "$5 to scrape 1,000 results".

abe/tiktok-profile-scraper is the one genuine profile-per-row alternative: its documented sample has ten keys — username, displayName, bio, tagline, profileImage, profileUrl, followers, following, likes, videos — and its README states plainly, "This actor does not scrape individual videos or video metadata." It advertises support for private profiles via a browser-based "User" method, which this Actor cannot do. Its documented input is a usernames array without the @ symbol; its README shows a full profile URL under an "Incorrect" heading. It is silent on error rows, date filters, like filters and sorting, and states no price.

apidojo/tiktok-profile-scraper is also one row per post, with the creator nested under channel and different key names throughout (views, likes, comments, shares, bookmarks, song, postPage), so migrating to or from it does mean remapping. It documents startUrls and usernames inputs, since/until date filters, a maxItems total-run cap and a customMapFunction transform hook, plus a collabInfo collaborator block this Actor does not return. Its listing also documents input restrictions this Actor does not impose: "The profile should have at least 10 posts", and a prohibition on using customMapFunction for filtering. It states "Can I scrape private TikTok profiles? No", and prices at "$0.30 per 1,000 posts". Numeric speed, accuracy and success-rate claims on that listing are its own and are not reproduced or endorsed here.

Where this Actor differs, observably: like-count filters (leastDiggs, mostDiggs) alongside date filters, which none of the three documents; exact counters read from TikTok's statsV2 block rather than the rounded display values; a reposts feed selectable per run via profileScrapeSections; a documented uncharged-row contract with a single marker field and the exact filter expression; and a documented block-detection model that keys on the WAF marker and body length instead of HTTP status. What it does not do: no bioLink, no isPinned, no region, no collaborator block, no media re-hosting or download toggles, no private-profile access, no comment text, and no automatic deduplication of repeated handles.

Does TikTok Profile Scraper return data in a format LLMs can use directly?

Yes. Typed, normalized JSON with consistent field names across runs — no HTML parsing, no selectors. Pass a row straight into an LLM context window, index it into a vector store, or hand it to an agent tool. Two things to map deliberately: authorMeta.nickName and effectStickers[].ID keep TikTok's own capitalisation while the rest of the row is camelCase, and several flat fields are mirrors of nested values, so drop one side of each pair before indexing to avoid duplicate text.

What happens when TikTok changes its layout or anti-bot system?

The scraper is maintained, and because it reads TikTok's own rehydration JSON and feed endpoints rather than rendered markup, a front-end redesign generally does not affect your field names or types. Anti-bot pressure is the live surface, and it is handled by design rather than by hope: detection keys on the WAF marker and body length instead of HTTP status, and the response is a fingerprint rotation across five impersonation targets followed by a proxy-tier climb. Extraction paths TikTok has retired were removed rather than left as dead fallbacks, so a failure surfaces in the log and in errorReason instead of quietly returning empty rows.

Can I use TikTok Profile Scraper without managing proxies or browser infrastructure?

Yes, and there is nothing to configure. There is no browser at all — the Actor uses an impersonated HTTP session, which is why runs are lighter than a headless-Chromium scraper. Proxying is automatic and cheapest-first: it starts on the direct container IP and climbs to an Apify datacenter and then residential proxy only if TikTok blocks a profile. You never install a browser, create a proxy account or rotate an IP. The Actor does not solve CAPTCHAs, and it makes no claim to.

Which TikTok fields work best for AI training data and RAG indexing?

For RAG, text is the highest-information field per record and chunks cleanly, with hashtags[].name, soundName and authorMeta.signature as topical metadata and videoMeta.subtitleLinks available when you want spoken content rather than captions. For training data, the most structurally consistent fields across records are playCount, diggCount, commentCount, shareCount, collectCount, durationSeconds, createTime, isAd and isSlideshow — all typed primitives read from TikTok's exact statsV2 counters. Remember that count fields fall back to 0 rather than null when a key is missing, that textLanguage is genuinely null when TikTok does not label the caption, and that locationMeta is {} on most posts, so presence-check before feeding any of them to a model.

Scraper NameWhat it extracts
TikTok Comments Scraper By Real Comment FilterComment threads under TikTok posts, filtered to real comments
Tiktok Video Scraper (Creator Profile & Contact)Single-video data with the creator's profile and public contact details attached
TikTok Trending Hashtags Scraper By Multiple CountriesRising TikTok hashtags across several countries in one run
Tiktok Trending Hashtags Analytics Scraper By CountryTrending-hashtag analytics for a single country and industry
Instagram Profile ScraperThe same identity and follower fields for Instagram, one row per profile
Snapchat Profile ScraperPublic Snapchat profiles — display name, bio, subscriber count, snapcode

Which one suits which job: use TikTok Profile Scraper when a creator's post feed is the record you want, with per-post engagement attached. Use Tiktok Video Scraper (Creator Profile & Contact) when you already have specific video URLs and want the creator behind them. Use TikTok Comments Scraper By Real Comment Filter when the audience reaction matters more than the post metrics.

💬 Your feedback

Found a bug, or need a field that is in TikTok's post payload but not in this output — comment text, engagement-rate rollups, ranked hashtag and sound breakdowns, the profile bio link? Open an issue on the Actor's Issues tab. Reports that include the exact input JSON and the handle you ran are the fastest to reproduce and fix.