Tiktok Video Scraper (Creator Profile & Contact) avatar

Tiktok Video Scraper (Creator Profile & Contact)

Pricing

$19.99/month + usage

Go to Apify Store
Tiktok Video Scraper (Creator Profile & Contact)

Tiktok Video Scraper (Creator Profile & Contact)

TikTok Video Scraper gathers video details like captions, hashtags, stats (views, likes, comments, shares), music info, and creator profiles from any TikTok link or search. Ideal for trend analysis, research, or marketing. Export clean structured data in JSON, CSV, or Excel formats.

Pricing

$19.99/month + usage

Rating

5.0

(2)

Developer

Scraper Engine

Scraper Engine

Maintained by Community

Actor stats

0

Bookmarked

39

Total users

1

Monthly active users

5 days ago

Last modified

Share

TikTok Video Scraper โ€” Creator Profile, Bio Email and Video Stats

TikTok Video Scraper (Creator Profile & Contact) takes TikTok video URLs and returns one structured JSON row per video โ€” views, likes, comments, shares, caption, hashtags and sound โ€” with the poster's full creator profile fetched separately and attached, plus a best-effort contactEmail parsed out of their public bio. Thirty top-level keys per row, no TikTok login, no HTML parsing on your side. Paste video URLs and press Start.

โš ๏ธ Every row this Actor writes is charged, including error rows

This is the single most important thing to know before you run it, so it is stated here rather than buried in the output section.

There is exactly one push_data call in the codebase and it always carries the item_result charged event. Nothing is exempt from it. In practice:

  • A URL that could not be fetched still costs you a row. When the video page request fails, the Actor writes a two-key row โ€” {"error": "...", "url": "..."} โ€” into the same result list as real videos, and that list is pushed with the charged event. There is no uncharged accounting row, no errorReason marker, no status field, no success flag.
  • The marker you filter on is error. It is the only key that appears on failure rows and never on a real video row, because a real row is built from a fixed skeleton that has no error key at all.
  • A failed creator lookup is still charged. If the profile page cannot be read, the Actor logs a warning, leaves creatorProfile at null, and pushes the video row anyway with the same charged event. You pay the full row price for a row with no enrichment on it.
  • Related videos are charged one row each. Switching scrapeRelatedVideos on multiplies your row count by up to resultsPerPage per input URL.
items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
videos = [r for r in items if not r.get("error")]
errors = [r for r in items if r.get("error")]

The one case that costs nothing is the silent one: if TikTok serves a page that loads but carries no video payload, the Actor returns nothing for that URL โ€” no row, no charge, and no error row either. Reconcile your input list against the returned submittedVideoUrl values rather than assuming every URL produced something.

What is TikTok Video Scraper (Creator Profile & Contact)?

TikTok Video Scraper (Creator Profile & Contact) is an Apify Actor that fetches each TikTok video URL you supply, reads the video record out of the page's own __UNIVERSAL_DATA_FOR_REHYDRATION__ rehydration JSON, then makes a second request to the poster's profile page to attach a creatorProfile object and, where the bio carries one, an inferred contactEmail.

No TikTok account, login, password or session cookie is required, and none is accepted. It reads what a signed-out visitor is served.

It is built for influencer marketers who already have video URLs and need the creator behind them, B2B and partnership teams building creator outreach lists, and developers piping video plus creator context into a warehouse, CRM or AI agent.

What TikTok video and creator data is publicly available to scrape?

TikTok serves a signed-out visitor the whole public record of a video โ€” caption, engagement counters, sound, video dimensions, hashtags โ€” and the whole identity block of the creator who posted it, including the bio text and the link in bio. What it withholds is relationship lists, private and friends-only content, and any contact route the creator has not typed into their own public bio.

Data CategorySigned-out visitorGated behind a TikTok login or the account owner
Video caption, hashtags, sound name and artistโœ… Publicโ€”
Views, likes, comments, shares, saves, repostsโœ… Publicโ€”
Video dimensions, duration, cover images, subtitle tracksโœ… Publicโ€”
Creator handle, nickname, avatar, verified badge, regionโœ… Publicโ€”
Creator follower, following, total-likes and video countsโœ… Publicโ€”
Creator bio text and the link in bioโœ… Publicโ€”
An email address the creator typed into their own bioโœ… Publicโ€”
A structured contact or business-enquiry blockโŒ TikTok serves none to the webNot exposed at all โ€” the bio is the only public contact route
Phone number, postal address, real nameโŒ Never returned by this ActorNot published on a public TikTok profile
Full follower and following listsโŒLogin required
Comment text under the videoโŒ Not part of this Actor's outputSee TikTok Comments Scraper By Real Comment Filter
Anything on a private accountโŒApproved follow required โ€” never returned

TikTok Video Scraper (Creator Profile & Contact) only returns publicly visible data โ€” what any signed-out visitor sees on the video page and the profile page. Nothing behind a login wall.

What data can I extract with TikTok Video Scraper (Creator Profile & Contact)?

Every successful row carries four things: what the video is and how it performed, who posted it, that creator's live account totals fetched from their profile page, and whatever contact route their bio published.

Field NameDescription
idTikTok's numeric video id, as a string
textFull caption text, line breaks and emoji preserved. "" when the video has no caption
textLanguageTikTok's language code for the caption. Falls back to "en" when TikTok carries no code โ€” so "en" is not proof of English
createTimePost timestamp as a Unix epoch integer, 0 when unparseable
createTimeISOThe same timestamp rendered from the container clock with a fixed .000Z suffix. Use createTime when exactness matters
locationCreatedFirst populated value among the video's location.countryCode, region or country, then its top-level region, locationCreated, ipLocation or countryCode. "" on most videos
isAdTikTok's branded-content flag
isSponsoredRead from the payload's own isSponsored key, false when absent
isSlideshowtrue when the payload marks the post a photo carousel
isPinnedtrue when the payload marks the video pinned on the creator's profile
authorMeta19-key creator object taken from the video payload โ€” see the nested shapes below
musicMeta7-key sound object
videoMeta8-key video-file object including subtitle tracks
webVideoUrlThe URL this row was collected from
submittedVideoUrlThe same URL again, under a second key โ€” use it to reconcile rows against your input list
mediaUrlsAlways []. This Actor returns no direct video file URL
commentsDatasetUrlAlways null โ€” no comment sub-dataset is produced
diggCountLikes (hearts)
shareCountShares
playCountViews
collectCountSaves / bookmarks
commentCountComments
repostCountReposts, read from the payload's statsV2 block
hashtagsArray of hashtag objects โ€” see the note on its two shapes below
mentionsAlways []
detailedMentionsAlways []
effectStickersEffects used in the video, copied from the payload. [] when absent
creatorProfile12-key object fetched from the creator's own profile page, or null โ€” see below
contactEmailInferred email address parsed out of the creator's bio text or bio link, lowercased. null when none was found
contactEmailSource"signature", "bioLink" or null โ€” which of the two sources the address came from

Thirty top-level keys are written on every successful row, and no key is ever omitted. Each row starts as a full skeleton, so a null genuinely means "this was not available", not "the parser gave up quietly". Failure rows are the exception and carry only two keys, error and url.

Five keys are placeholders that are never populated by this Actor: mediaUrls, mentions and detailedMentions are always [], and commentsDatasetUrl and authorMeta.followDatasetUrl are always null. They exist so the row shape matches the base TikTok video schema, not because data went missing.

Video, caption and engagement fields

id, webVideoUrl and createTime identify the video; text, hashtags and effectStickers describe it; diggCount, shareCount, playCount, collectCount, commentCount and repostCount are integers describing how it performed. All six counters fall back to 0 rather than null when TikTok omits the key, so a 0 and a missing value are indistinguishable โ€” sanity-check playCount against diggCount before treating a zero as real.

hashtags is built from two sources and therefore has two different shapes in the same array. Tags that came from the video's challenges block carry four keys โ€” id, name, title, cover โ€” while tags recovered from the caption's textExtra entity list carry only name. Read name and treat the rest as optional. Names are lowercased and deduplicated across both sources.

videoMeta carries 8 keys: height, width, duration, coverUrl, originalCoverUrl, definition (falling back to the payload's ratio), format (defaulting to "mp4") and subtitleLinks. musicMeta carries 7: musicName (defaulting to "original sound"), musicAuthor, musicOriginal (defaulting to true), playUrl, coverMediumUrl, originalCoverMediumUrl and musicId. Cover, avatar and sound URLs point straight at TikTok's own CDN โ€” nothing is re-hosted or downloaded, and TikTok controls how long those links stay valid, so mirror anything you need to keep.

Creator identity, account totals and contact fields

authorMeta carries 19 keys taken from the video payload: id, name (the handle), profileUrl, nickName, verified, signature (the bio text), bioLink, originalAvatarUrl, avatar, privateAccount, roomId, ttSeller, followDatasetUrl, following, friends, fans, heart, video and digg. The count fields here come from the video payload's authorStats block and default to 0.

creatorProfile is different โ€” it is fetched from the creator's own profile page in a separate request and carries 12 keys: secUid, uniqueId, nickName, verified, region, signature, bioLink, followerCount, followingCount, heartCount, videoCount and diggCount. Its numeric fields are null rather than 0 when TikTok did not carry them, which is the practical reason to prefer creatorProfile.followerCount over authorMeta.fans when you need to tell "zero" from "unknown". region is a two-letter country code and appears only here, never in authorMeta.

โš ๏ธ contactEmail is inferred from public bio text, not read from a contact field โ€” TikTok exposes no structured contact block to the web. The Actor runs an email pattern over the creator's signature and then their bioLink, first on the raw text and then on a lightly de-obfuscated copy, and returns the first match it finds, lowercased with any trailing full stop trimmed. De-obfuscation covers bracketed (at), [at], (dot) and [dot] forms plus a spaced dot; a bare spaced at is not rewritten, so name at brand dot com does not resolve. Matches ending in .png, .jpg, .jpeg, .gif or .webp are rejected as asset filenames. A bio listing two addresses yields one. contactEmailSource tells you which field it came from, and both keys are null when nothing matched โ€” which is the common case, because most creators route contact through a link-in-bio service rather than publishing an address in plain text. No phone number, postal address or third-party social handle is extracted, and no number is normalised or reformatted anywhere in this Actor.

๐Ÿค– Add-on: Need additional TikTok data?

This Actor starts from video URLs you already have. To build that URL list in the first place, TikTok Profile Scraper returns a creator's whole public post feed with webVideoUrl on every row, and TikTok Trending Hashtags Scraper By Multiple Countries surfaces what is rising before you pick creators to track. If the audience reaction matters more than the creator, TikTok Comments Scraper By Real Comment Filter pulls the discussion under each webVideoUrl.

Why not build this yourself?

TikTok's own developer APIs are not the shortcut they look like. The Display API returns video and profile data only for users who have completed an OAuth login with your own registered app, and the Research API is application-only with an eligibility review attached. Neither serves arbitrary third-party video lookup, and neither exposes a creator's contact details โ€” check TikTok's current published developer documentation before planning around either. A scraper is the realistic alternative, and three things make it expensive to own.

Two page types, two failure modes. The video record and the creator record live on different pages with different rehydration payloads, so every enriched row is two requests, not one. Getting the second one right โ€” resolving the handle, parsing webapp.user-detail, falling back to the older __SIGI_STATE__ UserModule shape when the newer one is absent, caching by handle so a creator with five videos in your list is fetched once โ€” is most of the work in this Actor.

Related videos need a real browser. TikTok's related rail is populated by an XHR after page load, so it cannot be read from the initial HTML. This Actor launches headless Chromium, intercepts the api/related/item_list and api/recommend responses, scrolls to force more, and falls back to reading ItemModule out of the rendered DOM when interception comes up short. That is a Playwright install, a Chromium download and a scroll heuristic you would otherwise write and maintain yourself.

Bios are hostile input. Contact extraction over free text means handling obfuscation, emoji and asset filenames that look like addresses. Every rule you add is a rule you then own.

Add the ongoing maintenance โ€” TikTok moves keys, renames scopes and changes what the signed-out page carries โ€” and the build is weeks of work followed by permanent upkeep.

How to use TikTok Video Scraper (Creator Profile & Contact)

TikTok Video Scraper (Creator Profile & Contact) 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 the Actor on Apify and click Try for free
  2. Add your video URLs to Video URLs (creators to enrich) (postURLs) โ€” this is the only required input. Enter them one per line, or upload or link a text file
  3. Leave Enrich creator profile (enrichCreatorProfile) and Find contact email (inferred) (findContactEmail) on, or switch both off if you only want the video record and no second request per creator
  4. Optionally switch on Also pull related videos (scrapeRelatedVideos) and set Related videos per URL (resultsPerPage) โ€” remember every related video is a charged row
  5. Click Start
  6. Export the dataset as JSON, CSV, Excel or XML, or read it through the Apify API

A run with no usable URL does not crash and does not fail. The Actor logs No valid URLs provided, writes nothing at all โ€” not even an error row โ€” exits successfully and charges nothing. The same happens if every entry you passed is empty or does not begin with http. The Console rarely shows this because the field ships prefilled with an example URL, so it mostly reaches callers that assemble the input JSON themselves.

How to scale to bulk TikTok video extraction

postURLs is a list, so bulk is the normal mode. Neither the schema nor the code caps how many URLs one run may contain; the practical ceiling is the memory and timeout you assign the run on Apify. All URLs are dispatched concurrently โ€” one task each, with no pacing and no concurrency limit โ€” so a long list is fast but hits TikTok hard from a single IP, and very long lists are better split across scheduled runs.

Two deduplication rules to handle yourself. Video rows are not deduplicated across your input list: the same URL twice is fetched twice and charged twice, and two input videos whose related rails overlap produce duplicate rows. Creator profile fetches are deduplicated, by handle, for the whole run โ€” five videos from one creator normally cost one profile request, not five โ€” but because input URLs run concurrently, two videos by the same creator that are in flight at the same moment can each trigger a fetch before the cache is populated. Group your input by creator, or accept the occasional duplicate request.

What can you do with TikTok video and creator data?

  • ๐Ÿ“ง A partnerships lead building an outreach list feeds a shortlist of webVideoUrl values in, then keeps only rows where contactEmail is non-null and contactEmailSource is "signature", so every record in the CRM has a creator-published address rather than a guess.
  • ๐Ÿ“Š An influencer marketer vetting a viral video divides diggCount and commentCount by creatorProfile.followerCount to get a real engagement rate for that specific post, then reads creatorProfile.videoCount and creatorProfile.heartCount to check whether the account has a track record or one lucky hit.
  • ๐ŸŒ A regional campaign planner filters on creatorProfile.region and creatorProfile.verified to separate creators inside a target market from lookalikes, using creatorProfile.bioLink to confirm each one runs a real business site.
  • ๐ŸŽต A content strategist researching a format switches on scrapeRelatedVideos for a handful of reference videos and reads musicMeta.musicName, hashtags[].name and playCount across the returned rail to see what TikTok itself considers adjacent to that content.
  • ๐Ÿ’ฐ A brand-safety analyst auditing disclosures filters on isAd and isSponsored, then reads text and authorMeta.signature to catalogue how each creator discloses paid work.
  • ๐Ÿค– An AI engineer building a creator-research agent indexes text, hashtags[].name and creatorProfile.signature into a vector store with creatorProfile.followerCount, creatorProfile.region and playCount kept as metadata filters, so the agent can answer "which creators in this niche published a contact email" without re-fetching anything.

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 Video Scraper (Creator Profile & Contact) handle rate limits and blocking?

Straight answer, because it changes how you should run it: there is no proxy in this Actor. The input schema has no proxyConfiguration parameter and the code configures none. Video pages and profile pages are fetched with a plain aiohttp session sending a fixed desktop Chrome user-agent string, from the Apify container's own IP. There is no IP rotation, no TLS fingerprint impersonation and no CAPTCHA solving โ€” none is claimed.

The related-videos path is the one that runs a browser: headless Chromium via Playwright, launched with --no-sandbox, --disable-dev-shm-usage and --disable-blink-features=AutomationControlled, plus an init script that hides navigator.webdriver. A fresh browser is launched per input URL and closed when that URL's related rail is done. That path genuinely retries โ€” up to three attempts, with a two-second pause between them.

The HTTP path does not, despite appearances. The retry helper is invoked with max_retries=3 for both the video page and the profile page, but it is written so that a failed attempt returns immediately rather than looping โ€” each video page and each profile page gets exactly one HTTP request, with a 30-second timeout. Plan for that: a transient blip on a video URL becomes a charged error row, and a blip on a profile page becomes a row with creatorProfile: null.

When a video fetch fails, the Actor logs the URL and the exception and writes an error row. When a profile fetch fails, it logs a warning, caches the failure for the rest of the run so that handle is not retried, and pushes the video row with the enrichment fields left null. Neither stops the run.

โฌ‡๏ธ Input

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

ParameterRequiredTypeDescriptionExample Value
postURLsYesarrayTikTok video URLs to scrape; each video's poster is enriched with their creator profile (stringList editor). Enter URLs one by one, or upload or link a text file. Prefilled with one example URL. No minimum or maximum count["https://www.tiktok.com/@apifyoffice/video/7200360993149553925"]
scrapeRelatedVideosNobooleanAdditionally scrape TikTok's related videos for each URL; each related video's creator is also enriched. Default falsetrue
resultsPerPageNointegerHow many related videos to fetch per URL when the related option is on. Minimum 1, maximum 1000000, default 10. The shipped schema notes that TikTok typically caps related videos at ~12-1612
enrichCreatorProfileNobooleanFetch each poster's profile page and attach creatorProfile (secUid, uniqueId, nickName, verified, region, signature, bioLink, followerCount, followingCount, heartCount, videoCount, diggCount). Default truetrue
findContactEmailNobooleanBest-effort: run an email pattern over the creator's bio/signature and bio link. The result is labelled inferred via contactEmailSource, and is null when none is found. Default truetrue

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

  • postURLs is barely validated. The only check is that the entry is a string beginning with http. A non-TikTok URL, a TikTok profile URL or a hashtag page is accepted, fetched, found to carry no video payload, and then dropped silently โ€” no row, no charge, no error row and no log line naming it. Only real video URLs produce rows.
  • resultsPerPage defaults differently depending on how you call the Actor. The schema default is 10, which the Console fills in for you. The code's own fallback when the key is absent is 1, so an API caller who omits resultsPerPage and switches scrapeRelatedVideos on gets one related video per URL, not ten. Send it explicitly.
  • resultsPerPage's maximum is oversold. Values are clamped into the range 1โ€“1,000,000, but nothing near that is reachable: TikTok's related rail is the real ceiling, and the Actor logs TikTok limit reached when it returns fewer than you asked for. Under the hood it scrolls up to max(20, resultsPerPage) times and gives up after five consecutive scrolls that produce nothing new.
  • resultsPerPage does nothing unless scrapeRelatedVideos is on. With the related option off it is read and then never used.
  • The two enrichment toggles share one request. The profile page is fetched if either enrichCreatorProfile or findContactEmail is true, because the email search prefers the profile's signature and bioLink over the video payload's copies. Switching only enrichCreatorProfile off does not save the request โ€” it just discards the parsed profile and returns creatorProfile: null. Set both to false to run one request per video instead of two.
  • There is an undocumented keywords input. The code reads input_data.get('keywords', []), and if you pass a list of search terms it launches a Playwright TikTok search per term and appends up to resultsPerPage discovered video URLs to the run. It is not in the input schema, so it does not appear in the Console and is not a supported surface โ€” treat it as unspecified behaviour rather than a feature, and supply postURLs instead.

Example input

{
"postURLs": [
"https://www.tiktok.com/@apifyoffice/video/7200360993149553925",
"https://www.tiktok.com/@zachking/video/7318924471082470689",
"https://www.tiktok.com/@khaby.lame/video/7301188745012183301"
],
"scrapeRelatedVideos": true,
"resultsPerPage": 12,
"enrichCreatorProfile": true,
"findContactEmail": true
}

โฌ†๏ธ Output

Typed, normalized JSON with a consistent shape across runs โ€” one row per video, 30 top-level keys on every successful row, with authorMeta, musicMeta, videoMeta and creatorProfile carried as nested objects. Export as JSON, CSV, Excel or XML from the Console, or read the dataset through the Apify API.

One thing to expect operationally: the dataset stays empty until the run is nearly over. Every URL is fetched and enriched first, and the whole result set is pushed in a single loop at the end, so rows do not stream in while the run is going.

The default dataset view shows 16 columns โ€” id, text, createTimeISO, authorMeta.name, authorMeta.verified, creatorProfile.followerCount, creatorProfile.heartCount, creatorProfile.videoCount, creatorProfile.region, contactEmail, contactEmailSource, diggCount, playCount, commentCount, musicMeta.musicName and webVideoUrl โ€” while the full row carries everything documented above. Export as JSON to see the rest.

Example output

One enriched video row, from a creator whose bio published an address:

{
"id": "7318924471082470689",
"text": "Behind the scenes on the floating table illusion โ€” three days, one take #magic #bts #vfx",
"textLanguage": "en",
"createTime": 1703462400,
"createTimeISO": "2023-12-25T00:00:00.000Z",
"locationCreated": "US",
"isAd": false,
"authorMeta": {
"id": "6569595380449902597",
"name": "zachking",
"profileUrl": "https://www.tiktok.com/@zachking",
"nickName": "Zach King",
"verified": true,
"signature": "Digital sleight of hand\nBookings: hello (at) zachkingcreative (dot) com",
"bioLink": null,
"originalAvatarUrl": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/7a1c9f4b2e8d~c5_1080x1080.jpeg",
"avatar": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/7a1c9f4b2e8d~c5_720x720.jpeg",
"privateAccount": false,
"roomId": "",
"ttSeller": false,
"followDatasetUrl": null,
"following": 412,
"friends": 87,
"fans": 81400000,
"heart": 1042883910,
"video": 892,
"digg": 1204
},
"musicMeta": {
"musicName": "original sound",
"musicAuthor": "Zach King",
"musicOriginal": true,
"playUrl": "https://sf16-ies-music-va.tiktokcdn.com/obj/tos-useast2a-ve-2774/oQAf3bTcR9m.mp3",
"coverMediumUrl": "https://p16-amd-va.tiktokcdn.com/img/tos-maliva-avt-0068/7a1c9f4b2e8d~c5_720x720.jpeg",
"originalCoverMediumUrl": "https://p16-amd-va.tiktokcdn.com/img/tos-maliva-avt-0068/7a1c9f4b2e8d~c5_720x720.jpeg",
"musicId": "7318924468091293185"
},
"webVideoUrl": "https://www.tiktok.com/@zachking/video/7318924471082470689",
"mediaUrls": [],
"commentsDatasetUrl": null,
"videoMeta": {
"height": 1024,
"width": 576,
"duration": 34,
"coverUrl": "https://p16-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/oQd2eBaFm4~tplv-dmt-logom.image",
"originalCoverUrl": "https://p16-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/oQd2eBaFm4~tplv-tiktokx-origin.image",
"definition": "720p",
"format": "mp4",
"subtitleLinks": [
{
"LanguageCodeName": "eng-US",
"Url": "https://v16-webapp.tiktok.com/subtitle/eng-US/7318924471082470689.webvtt",
"Source": "ASR"
}
]
},
"diggCount": 4128773,
"shareCount": 208114,
"playCount": 39281044,
"collectCount": 511208,
"commentCount": 51877,
"repostCount": 1420,
"mentions": [],
"detailedMentions": [],
"hashtags": [
{
"id": "5641",
"name": "magic",
"title": "Magic tricks and illusions",
"cover": "https://p16-sign.tiktokcdn-us.com/tos-useast5-p-0068/magic~c5_720x720.jpeg"
},
{ "name": "bts" },
{ "name": "vfx" }
],
"effectStickers": [],
"isSlideshow": false,
"isPinned": false,
"isSponsored": false,
"submittedVideoUrl": "https://www.tiktok.com/@zachking/video/7318924471082470689",
"creatorProfile": {
"secUid": "MS4wLjABAAAAmT8qkVSKcCVCUqTgKMoXxJHDwPFvGqjXK0Q9zTn4bYc",
"uniqueId": "zachking",
"nickName": "Zach King",
"verified": true,
"region": "US",
"signature": "Digital sleight of hand\nBookings: hello (at) zachkingcreative (dot) com",
"bioLink": "https://zachkingcreative.com",
"followerCount": 81400000,
"followingCount": 412,
"heartCount": 1042883910,
"videoCount": 892,
"diggCount": 1204
},
"contactEmail": "hello@zachkingcreative.com",
"contactEmailSource": "signature"
}

Two things to notice. contactEmail resolved from a bio written hello (at) zachkingcreative (dot) com, and contactEmailSource records that it came from the bio text rather than the bio link. mediaUrls and mentions are [] โ€” those are the permanent placeholders, not data that went missing. subtitleLinks entries are copied from TikTok's payload unchanged, so their keys keep TikTok's own capitalisation.

And one failure row, for a URL the Actor could not fetch. It carries two keys, no id and no creator context โ€” and it was charged exactly like the row above:

{
"error": "Cannot connect to host www.tiktok.com:443 ssl:default [Connection reset by peer]",
"url": "https://www.tiktok.com/@deletedcreator/video/7188392044120299522"
}

How does it work?

TikTok builds a video page from a JSON blob embedded in the HTML rather than from server-rendered markup. The Actor requests each video URL you supplied, pulls the __UNIVERSAL_DATA_FOR_REHYDRATION__ script out of the response โ€” falling back to the older __SIGI_STATE__ block โ€” and reads the video record out of webapp.video-detail. Each raw field is mapped onto a fixed key name with an explicit fallback, so a key TikTok moves degrades one field instead of breaking the row.

It then makes a second request to https://www.tiktok.com/@{handle} for the poster, reads webapp.user-detail for the profile object, and attaches it as creatorProfile. Handles are cached for the run, so a creator appearing on several of your videos is normally fetched once. When related videos are requested, a headless Chromium session loads the video page, intercepts TikTok's related-feed XHR responses, and scrolls until it has enough or TikTok stops serving more.

Only publicly visible data is returned. There is no login, no cookie and no session anywhere in the flow, so private accounts and follower lists are out of reach by design. Because the data comes from JSON rather than CSS selectors, a TikTok front-end redesign does not change your field names.

Integrations

TikTok Video Scraper (Creator Profile & Contact) is an Apify Actor, so it works with anything that can call the Apify API or read a dataset.

Calling TikTok Video Scraper (Creator Profile & Contact) from Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<YOUR_USERNAME>/tiktok-video-scraper-creator-profile-contact").call(run_input={
"postURLs": [
"https://www.tiktok.com/@zachking/video/7318924471082470689",
"https://www.tiktok.com/@apifyoffice/video/7200360993149553925",
],
"enrichCreatorProfile": True,
"findContactEmail": True,
"scrapeRelatedVideos": False,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
if row.get("error"):
print("failed", row["url"], row["error"])
continue
profile = row.get("creatorProfile") or {}
print(row["authorMeta"]["name"], profile.get("followerCount"), row.get("contactEmail"), row["playCount"])

Works in Go, Ruby, Node.js, cURL โ€” any language that can make an HTTP request. Every successful row has the same 30 keys, so the error check above and a creatorProfile null-guard are the only two branches 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 error to strip failure rows, then drop the flat fields into a Google Sheets, Airtable or Postgres node. In Make, the Apify module supports run-and-wait, so a weekly refresh of a creator list can feed a CRM step with no glue code โ€” filter on contactEmail being non-null if the CRM should only receive rows with a contact route. In LangChain, wrap the run endpoint as a tool and hand the rows to the model directly, since they arrive as typed JSON. Apify schedules and webhooks cover recurrence and completion triggers.

Scraping publicly visible video and profile data is broadly treated as permissible where no login is used and no private account is accessed, and this Actor does neither.

The output is personal data, and where contactEmail is populated it is personal contact data. authorMeta.name, nickName, signature, avatar, every creatorProfile field and often text relate to an identifiable person; contactEmail, contactEmailSource and bioLink are a direct route to them. The moment you store a row you are a data controller for it. Under GDPR and UK GDPR you need a lawful basis before storage or reuse; 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. Because the data was not collected from the creators themselves, the Article 14 transparency duties apply, and you must be able to honour access, objection and erasure requests, so keep the handle-to-row mapping you would need.

Direct marketing is a separate and stricter regime than data protection. In the EU and UK, unsolicited electronic marketing is governed by the ePrivacy rules and PECR alongside GDPR. In the US, commercial email falls under CAN-SPAM, and calls and texts under the federal and state calling and texting rules, including the do-not-call registries. "It was in their bio" is not a lawful basis for contacting someone, and running this Actor neither obtains nor conveys anyone's permission to be contacted.

A dedicated business-enquiry address a creator published for that purpose and a personal address they happened to type into a bio also carry materially different risk, and this Actor cannot tell them apart. Screen every extracted address against your suppression and do-not-contact lists before any use.

TikTok skews young, and some creators are minors. Processing children's data carries heightened obligations under GDPR Article 8, the UK Age Appropriate Design Code and COPPA in the US; this Actor returns no age field and cannot tell you which rows those are.

Consult qualified legal counsel before bulk storage of this data or any outbound marketing built on it.

โ“ Frequently asked questions

What TikTok video and creator fields does this Actor return?

The five most used are playCount, diggCount, creatorProfile.followerCount, contactEmail and text. Every successful row carries 30 top-level keys, including the nested authorMeta (19 keys), musicMeta (7), videoMeta (8) and creatorProfile (12) objects โ€” 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. Both the video page and the profile page are fetched exactly as a signed-out visitor would fetch them. 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, private accounts โ€” is out of reach.

Does it return the creator's email address, and how reliable is it?

It returns an email when the creator published one in their bio, and null otherwise โ€” which is the common case. contactEmail is inferred: an email pattern runs over the creator's signature and then their bioLink, both raw and lightly de-obfuscated, and the first match wins. Bracketed (at), [at], (dot) and [dot] forms resolve, as does a spaced dot; a bare spaced at does not. Many creators route contact through a link-in-bio service instead of publishing an address, and the Actor does not follow those links. contactEmailSource labels every hit as "signature" or "bioLink" so you can weight them differently. No phone number is extracted โ€” there is no phone field, and no number is normalised or reformatted anywhere in this Actor.

How many TikTok videos can I extract in one run?

There is no cap in the schema or the code on how many URLs postURLs may contain โ€” the practical ceiling is the memory and timeout you assign the run on Apify. Related videos are the one bounded part: resultsPerPage accepts 1 to 1,000,000, but TikTok's related rail is the real limit, and the shipped schema notes it typically caps at around 12-16. All input URLs are fetched concurrently, so a long list finishes quickly but produces a burst of traffic from one IP.

What happens if a video is removed, private, or the creator's profile cannot be loaded?

Three different outcomes, and only one of them is free.

A video URL that fails to fetch โ€” network error, refused connection, a response the parser chokes on โ€” produces a charged two-key row, {"error": "...", "url": "..."}. The URL you submitted is in url, so you can retry it. It gets one HTTP attempt, not three.

A page that loads but carries no video payload โ€” a removed video, a private account's video, or a URL that was never a video page โ€” produces nothing at all: no row, no charge, and no error row. This is the silent case. Reconcile the submittedVideoUrl values you got back against the list you sent to find these.

A creator profile that cannot be loaded does not fail the row. The Actor logs a warning, returns the video row with creatorProfile, contactEmail and contactEmailSource all null, and charges it in full. The failure is cached for the rest of the run, so that handle is not retried on its other videos.

Can I scrape multiple TikTok videos at once?

Yes. postURLs is a list and there is no cap on its length. All URLs are dispatched concurrently rather than one at a time. Two caveats: duplicates are not removed, so the same URL twice is scraped twice and charged twice, and any entry that does not start with http is discarded before the run begins.

Does it 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 two rules: skip rows where error is present, and treat creatorProfile as possibly null rather than assuming the nested keys exist.

How does it compare to other TikTok creator scrapers?

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

novi/tiktok-user-api is the most used of the three and works from a different starting point: its documented inputs are usernames, userIds and urls plus a limit, so you give it accounts and it returns their feeds, where this Actor takes individual video URLs. Its headline claim is watermark-free video URLs, and its published output sample is TikTok's raw payload in snake_case โ€” added_sound_music_info, play_addr_h264, video_control and so on โ€” with signed CDN links carrying x-expires and x-signature parameters. It documents a user signature field but no contact extraction of any kind. Its README documents limit as a soft per-target limit and is silent on error rows and on whether they are billed.

khadinakbar/tiktok-profile-scraper is profile-first, one row per creator: its documented output includes follower_count, heart_count, bio, bio_link, region, a computed engagement_rate, an avg_views_per_video and a proprietary influencer_score from 0 to 100, plus a recent_videos array. Two things its listing documents that this Actor does not do: it decodes TikTok's bio-link redirect to the real destination, and it states that a username that does not exist is skipped and not billed. It documents no contact-email extraction, and its README states pay-per-event pricing at "$0.003 per profile".

lanky_quantifier/tiktok-profile-scraper documents three inputs โ€” profiles, maxVideos, scrapeType โ€” and emits two record types distinguished by a type field, "profile" and "video", with roughly ten keys each. Its FAQ states plainly that it does not download video files. It documents no contact extraction, no error-row contract and no related-video feature, and it publishes an estimated cost table rather than a per-event price.

Where this Actor differs, observably: it is the only one of the four whose documented output carries contactEmail and contactEmailSource; it starts from individual video URLs rather than accounts; it attaches a separately fetched creatorProfile to each video rather than only nesting the copy embedded in the video payload; and it documents its charging behaviour including the fact that error rows are billed. What it does not do: no watermark-free or downloadable video URL, no bio-link redirect decoding, no engagement-rate or influencer-score computation, no comment text, no proxy configuration, and no deduplication of repeated video URLs.

Does it return data in a format LLMs can use directly?

Yes. Typed, normalized JSON with consistent field names across runs โ€” no HTML parsing, no selectors, and the same 30 top-level keys on every successful row. 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 creatorProfile.nickName keep TikTok's own capitalisation while the rest of the row is camelCase, and authorMeta and creatorProfile overlap on handle, nickname, verified flag and follower count โ€” drop one side before indexing to avoid duplicating the same creator text.

What happens when TikTok changes its layout or anti-bot system, and do I need to manage proxies or browsers?

You manage nothing โ€” there is nothing to configure, because the Actor exposes no proxy parameter and installs its own Chromium for the related-videos path. It runs on the Apify container's IP with no rotation, so if TikTok starts refusing that traffic you will see it as error rows rather than as a setting you can tune; that is the honest trade-off of a scraper with no proxy layer. On layout changes, the scraper is maintained and its structure absorbs most of the pressure by design: it reads TikTok's own rehydration JSON rather than rendered markup, so a front-end redesign generally does not change your field names or types, and it falls back to the older __SIGI_STATE__ payload shape when the newer one is absent. Every field is mapped by explicit path with a fallback, so a key TikTok moves degrades one field to null instead of failing the row.

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 creatorProfile.signature, hashtags[].name and musicMeta.musicName as topical metadata and creatorProfile.bioLink as an outbound reference. For training data and analysis, the most structurally consistent fields across records are playCount, diggCount, commentCount, shareCount, collectCount, repostCount, createTime, videoMeta.duration and creatorProfile.followerCount โ€” all typed primitives. Three things to handle before feeding any of them to a model: the video counters fall back to 0 rather than null when a key is missing, textLanguage falls back to "en" so it is not a trustworthy language label, and creatorProfile is null on any row whose profile lookup failed, so null-guard the nested access.

Scraper NameWhat it extracts
TikTok Profile ScraperA creator's whole public post feed, one row per post, with webVideoUrl on every row to feed into this Actor
TikTok Comments Scraper By Real Comment FilterComment threads under TikTok posts, filtered to real comments
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
YouTube Shorts Scraper With Creator Lead EnrichmentThe same short-form video plus creator contact job on YouTube
Instagram Verified Business ScraperInstagram profiles qualified by verification and account type, with bio email and phone

Which one suits which job: use TikTok Video Scraper (Creator Profile & Contact) when you already have specific video URLs and want the creator behind each one, contact route included. Use TikTok Profile Scraper when the creator's whole post feed is the record you want, or when you need the video URLs in the first place โ€” the two chain together in that order. Use TikTok Comments Scraper By Real Comment Filter when the audience reaction matters more than the creator.

๐Ÿ’ฌ Your feedback

Found a bug, or need a field that is in TikTok's video payload but not in this output โ€” a downloadable video URL, parsed mentions, comment text, or every bio email rather than the first? Open an issue on the Actor's Issues tab. Reports that include the exact input JSON and the video URLs you ran are the fastest to reproduce and fix.