TikTok Profile Scraper avatar

TikTok Profile Scraper

Pricing

$0.90 / 1,000 videos

Go to Apify Store
TikTok Profile Scraper

TikTok Profile Scraper

Export every accessible video of public TikTok profiles to JSON: caption, views, likes, comments, shares, saves, music, duration, cover, pin flag and the creator's profile stats. Date filters, per-profile caps or the full history. Unavailable and private profiles return success:false and cost $0.

Pricing

$0.90 / 1,000 videos

Rating

0.0

(0)

Developer

apihq dev

apihq dev

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

0

Monthly active users

a day ago

Last modified

Security

Limited permissions

Share

Export the public videos of TikTok profiles as clean JSON from their handles or profile URLs: caption, views, likes, comments, shares, saves, posting date, sound, duration, cover, hashtags, and the creator's followers, following, and total likes on every row. Walk a creator's full accessible video history or cap it per profile, and filter by posting date. No TikTok account, no API key, no browser.

The contract is simple: a private or unavailable profile never fails your run, and you only pay for videos delivered. Paste a private account, a missing creator, or a bad URL, and it comes back as a success: false row while the other profiles keep going. $0.90 per 1,000 videos. Failed profiles and profile rows cost $0, and there is no start fee.

See one run

Input:

{
"profiles": ["apifytech", "m9_no_such_creator_20261103"],
"resultsPerPage": 0
}

What comes back (counts are from pages captured on 2026-11-03; live numbers move):

  • 57 itemType: "video" records for apifytech, newest first, all billed. That is every video TikTok lists for the creator, from 2026-10-29 back to 2024-04-03, walked to TikTok's natural end of the list. The profile itself reports 63 videos; TikTok serves 57 (see below). The newest one: 780 views, 7 likes, 0 comments, 1 share, 2 saves, 55.68 seconds, five hashtags. Every row carries the creator's profile snapshot: 3,591 followers, 1 following, 4,145 likes
  • 1 itemType: "error" row, errorCode: TIKTOK_PROFILE_UNAVAILABLE, for the handle TikTok does not serve, not billed
  • Run status: SUCCEEDED
  • Charge: 57 delivered videos x $0.0009 = $0.0513

Good profiles return billed video rows, bad profiles return free error rows in the same dataset, and the run still succeeds, so one missing creator never costs you a batch.

Why this Actor

  • The full list when you ask for it. resultsPerPage: 0 walks each profile until TikTok's own list ends. Every profile tells you why its walk stopped (stopReason) and whether it reached the end (complete), so a capped or interrupted walk never looks like a full history.
  • You pay only for videos. Billing fires on an explicit per-video event, not on dataset writes. Profile rows, error rows, and the run summary are free, date filters cost nothing extra, and there is no per-run start fee or minimum.
  • No video billed twice. Aliases of the same handle (apifytech, @ApifyTech, the profile URL) run once, two inputs that turn out to be the same creator are walked once, and a video is never charged twice in a run.
  • Drop-in field names. Inputs (profiles, resultsPerPage, oldestPostDateUnified, newestPostDate) and video records (text, playCount, diggCount, commentCount, shareCount, collectCount, createTimeISO, webVideoUrl, isPinned, authorMeta, musicMeta, videoMeta, input) use the names most TikTok profile scrapers already use, so an existing pipeline can switch without a remap.
  • No made-up values. A field TikTok does not report is null, never a guessed 0 or false.

Best for

Data and AI workflows that need what creators post and how it performs: influencer vetting and creator discovery, competitor and brand-account monitoring, campaign reporting over a date window, tracking a roster of creators on a schedule, and building creator video datasets for analytics or LLM pipelines. The free error rows and explicit stop reasons matter most when the walk runs unattended inside a larger pipeline, where one private or deleted account should never take down the batch. It works fine for one-off manual pulls too.

Input example

One profile, its full accessible video history:

{
"profiles": ["apifytech"],
"resultsPerPage": 0
}

Several profiles, the 40 newest videos of each, given as a handle, an @handle, and a profile URL copied from the browser (the query string is ignored, and the URL is the same creator as the first entry, so it runs once):

{
"profiles": [
"apifytech",
"@scout2015",
"https://www.tiktok.com/@apifytech?lang=en"
],
"resultsPerPage": 40
}

Every video posted in the first half of 2026. Both dates are inclusive and in UTC: 2026-06-30 covers that whole day. The walk stops at the first page whose non-pinned videos are all older than 2026-01-01, so the rest of the history is not walked:

{
"profiles": ["apifytech"],
"resultsPerPage": 0,
"oldestPostDateUnified": "2026-01-01",
"newestPostDate": "2026-06-30"
}

The 10 newest videos of each creator plus one free profile row per creator with its stats and how the walk ended:

{
"profiles": ["apifytech", "scout2015"],
"resultsPerPage": 10,
"includeProfileRow": true
}
FieldTypeDefaultWhat it does
profilesarray of stringsPublic TikTok handles (apifytech), @handles, or profile URLs (https://www.tiktok.com/@apifytech, also with m. or without www.; query strings and # fragments are ignored). Up to 50 unique profiles per run; extra ones are dropped with a warning in the log. Aliases of the same handle (case-insensitive) run once.
resultsPerPageinteger100Maximum videos delivered per profile, counting only videos that match your date window. 0 means all accessible videos: the walk runs to TikTok's natural end of the list, or until the run's cost limit.
oldestPostDateUnifiedstringblankOnly videos posted at or after this moment. YYYY-MM-DD (starts at 00:00 UTC) or an ISO timestamp with a zone (2026-01-01T12:00:00Z, 2026-01-01T12:00:00+02:00). Relative values such as "7 days" are not accepted.
newestPostDatestringblankOnly videos posted at or before this moment. Same formats; a date-only value includes that whole UTC day.
includeProfileRowbooleanfalseAdds one free itemType: "profile" row per creator. A creator with zero delivered videos always gets one, whatever this is set to.

An invalid date, a timestamp without a zone, or oldestPostDateUnified later than newestPostDate stops the run before any request to TikTok: you get one free success: false row with errorCode: VALIDATION_FAILED and statusCode: 400 naming the field, no charge, and the run still succeeds.

Output example

Every record has itemType: video (billed), profile (free), or error (free). The author, sound, and media details are nested objects; the Console's table views flatten them to columns such as authorMeta.fans and videoMeta.duration. One video:

{
"success": true,
"itemType": "video",
"id": "7702131657784216854",
"text": "nobody wants to copy Instagram reel stats by hand, so we built an Actor for that 👀 #aiagents #automation #aitools #ai #apify",
"textLanguage": null,
"createTime": 1793292277,
"createTimeISO": "2026-10-29T16:44:37.000Z",
"isAd": false,
"isPinned": false,
"locationCreated": null,
"privateItem": null,
"secret": null,
"forFriend": null,
"authorMeta": {
"id": "7353570794285417504",
"name": "apifytech",
"nickName": "Apify",
"profileUrl": "https://www.tiktok.com/@apifytech",
"avatar": "https://p16-sign-va.tiktokcdn.com/...",
"signature": "...",
"bioLink": "apify.it/m/tiktok",
"verified": false,
"privateAccount": false,
"region": null,
"commerceUserInfo": { "commerceUser": false },
"ttSeller": false,
"roomId": "",
"fans": 3591,
"following": 1,
"heart": 4145,
"video": 63,
"digg": 0,
"friends": 1
},
"musicMeta": {
"musicName": "original sound",
"musicAuthor": "Apify",
"musicOriginal": false,
"musicId": "7702131695763409686",
"musicAlbum": "",
"playUrl": "https://sf16-ies-music-va.tiktokcdn.com/...",
"coverMediumUrl": "https://p16-sign-va.tiktokcdn.com/..."
},
"webVideoUrl": "https://www.tiktok.com/@apifytech/video/7702131657784216854",
"videoMeta": {
"duration": 55.68,
"height": 3840,
"width": 2160,
"definition": "540p",
"format": null,
"coverUrl": "https://p16-sign-va.tiktokcdn.com/...",
"originalCoverUrl": "https://p16-sign-va.tiktokcdn.com/...",
"dynamicCoverUrl": "https://p16-sign-va.tiktokcdn.com/...",
"subtitleLinks": null
},
"hasSubtitleDescriptors": null,
"diggCount": 7,
"shareCount": 1,
"playCount": 780,
"commentCount": 0,
"collectCount": 2,
"repostCount": 0,
"hashtags": [
{ "id": "7175202785762213930", "name": "aiagents", "title": "aiagents", "cover": null },
{ "id": "8617580", "name": "automation", "title": null, "cover": null }
],
"mentions": [],
"input": "apifytech"
}

(Two of the five hashtags shown; signature and signed URLs shortened.)

A profile row, written for every creator when includeProfileRow is true, and always for a creator with zero delivered videos. It has no video fields and is not charged:

{
"success": true,
"itemType": "profile",
"input": "apifytech",
"profileUrl": "https://www.tiktok.com/@apifytech",
"authorMeta": {
"id": "7353570794285417504",
"name": "apifytech",
"nickName": "Apify",
"fans": 3591,
"following": 1,
"heart": 4145,
"video": 63,
"digg": 0,
"friends": 1
},
"videosDelivered": 57,
"stopReason": "source_exhausted",
"complete": true
}

(authorMeta shortened; it is the same object as on the video rows.)

A profile that cannot be read lands in the same dataset and is not charged:

{
"success": false,
"itemType": "error",
"url": "m9_no_such_creator_20261103",
"input": "m9_no_such_creator_20261103",
"errorCode": "TIKTOK_PROFILE_UNAVAILABLE",
"error": "TikTok did not make this profile available (it may not exist, be deleted or be banned)",
"requestId": "req_7f3a9c1e5b2d4086a1c4e9f2",
"statusCode": 422,
"videosDelivered": 0
}

Split videos from the rest on itemType (or on success for hits versus misses). Quote requestId in any support issue and we can trace the exact request.

What you get

Every video is one record with success: true and itemType: "video". Any value TikTok does not report is null:

FieldTypeWhat it is
successbooleantrue for video and profile rows, false for error rows.
itemTypestringvideo (billed), profile (free), or error (free).
idstringThe video ID.
textstringThe caption, possibly empty.
textLanguagestring or nullCaption language code TikTok reports.
createTime / createTimeISOinteger / stringWhen the video was posted: Unix seconds and ISO 8601 UTC.
playCount, diggCount, commentCount, shareCountintegerViews, likes, comments, shares. Always present.
collectCount, repostCountinteger or nullSaves and reposts.
isAdboolean or nullTikTok's ad flag for the video.
isPinnedboolean or nulltrue when TikTok marks the video as pinned on the profile, false when it marks it as not pinned, null when TikTok did not say.
webVideoUrlstringThe canonical video URL.
inputstringThe profile this record answers, as you gave it.
authorMetaobjectThe creator, from their profile page at the time of the run and identical on every row of that creator: id, name (handle), nickName, profileUrl, avatar, signature (bio), bioLink (as TikTok shows it, possibly without https://), verified, privateAccount, region, commerceUserInfo (commerceUser), ttSeller, roomId, fans (followers), following, heart (likes received), video (videos the profile reports, not the number delivered), digg (videos liked), friends.
musicMetaobjectmusicName, musicAuthor, musicOriginal, musicId (string), musicAlbum, playUrl, coverMediumUrl.
videoMetaobjectduration (seconds, with fractions such as 55.68), height, width, definition (such as 540p), coverUrl, originalCoverUrl, dynamicCoverUrl. format and subtitleLinks are always null here.
hashtagsarray or nullHashtags from TikTok's structured caption data, not parsed from the caption text: id, name, title, cover. A caption with # words can still have none here. null when TikTok sent no structured data.
mentionsarray or null@handles mentioned in the caption, [] when the caption structure has none, null when unknown.
locationCreated, privateItem, secret, forFriend, hasSubtitleDescriptorsnullAlways null here: the profile video list does not report them.

IDs are strings because TikTok's 19-digit IDs do not fit in a JavaScript number. Media, cover, and music URLs are TikTok's signed links: they expire and may only work from some networks.

Profile rows (itemType: "profile", free) carry input, profileUrl, authorMeta (as above), videosDelivered, stopReason, and complete. complete is true only when the walk reached TikTok's natural end of the creator's list. stopReason says why the walk of a profile ended:

stopReasonMeaning
source_exhaustedTikTok's list ended. The only reason with complete: true.
result_limitresultsPerPage videos were delivered.
date_boundaryThe walk passed oldestPostDateUnified; the rest of the list is older.
budgetThe run's cost limit was reached.
cursor_stalledTikTok's pagination stopped moving forward. A free TIKTOK_UNAVAILABLE error row says so; videos already delivered are kept.
idle_pagesThree pages in a row brought no new videos. A free TIKTOK_UNAVAILABLE error row says so; videos already delivered are kept.
expected_errorA page failed with one of the error codes below. Videos already delivered are kept.

Profiles that could not be read, or whose walk failed or stalled partway, carry success: false, itemType: "error", url and input (the profile you gave), a machine-readable errorCode, a human-readable error, the service requestId and HTTP statusCode when a request failed, and videosDelivered (videos already delivered for that profile before the failure). They are not charged:

CodeMeaning
TIKTOK_PROFILE_UNAVAILABLETikTok does not serve this profile. It may not exist, be deleted, or be banned; TikTok does not say which.
TIKTOK_PRIVATE_ACCOUNTThe account is private. Its videos are never requested.
TIKTOK_UNAVAILABLETikTok did not serve the profile or a page of videos and gave no reason, or its list stopped yielding before its end (stopReason cursor_stalled or idle_pages).
TIKTOK_BLOCKEDTikTok refused the request after an automatic retry.
TIKTOK_RATE_LIMITEDTikTok rate-limited the request after an automatic retry.
TIKTOK_TIMEOUTTikTok did not answer in time after an automatic retry.
DEADLINE_EXCEEDEDThe request hit its 30-second deadline.
VALIDATION_FAILEDThe input is not a supported profile (short links vm.tiktok.com/vt.tiktok.com, video URLs, other sites, URLs with credentials or a port), or a date filter is invalid (then it is the only row and no profile is requested).
NO_INPUTNo usable profiles were given. The run ends successfully with this single row.

The run also writes a summary to the OUTPUT record of its default key-value store, one entry per profile, including input, status, profileId, uniqueId, videosDelivered, pagesFetched, stopReason, and complete. It is free, and it is there even when includeProfileRow is false.

Pricing

Pay per event. One video charge per unique video delivered: $0.0009 each, which is $0.90 per 1,000 videos. The charge fires only after the video record lands in your dataset.

  • 1,000 delivered videos cost $0.90, whether they come from one creator or from fifty.
  • A full walk of apifytech (57 accessible videos) is 57 charges.
  • Profile rows, error rows, and the OUTPUT summary cost $0. So do private, unavailable, and invalid profiles.
  • Date filters cost nothing extra: videos outside the window are skipped and not charged.
  • There is no per-run start fee and no minimum, so a run that delivers nothing costs nothing.

Platform compute is included in the per-video price. Cap the maximum spend of a single run from Apify's Run Limits panel; the Actor checks the cap before each profile and each page of videos, and stops cleanly with stopReason: budget when it runs out.

What this Actor does not do

Honest scope, so you know before you run it:

  • Public accounts only. A private account returns a free success: false row with errorCode: TIKTOK_PRIVATE_ACCOUNT, and its videos are never requested. Nothing that needs a TikTok login is returned.
  • Accessible videos, not the profile's count. The number of videos TikTok serves can be lower than the count the profile shows. apifytech reports 63 videos, and a full walk returns 57. authorMeta.video is the count the profile reports; complete: true means the walk reached the end of TikTok's list, not that it matched that count.
  • Newest first only. Videos come in TikTok's order, newest first, with pinned videos possibly at the top. There is no oldest-first or most-popular sorting.
  • Pins as TikTok reports them. isPinned is filled when TikTok sends it, but pinned videos are not guaranteed to come first or to be present.
  • Videos only. No stories, reposts, liked videos, follower or following lists, comments, transcripts, or video downloads. For comments use the TikTok Comments Scraper, for captions as text the TikTok Transcript Scraper, and for the full detail of specific videos, including caption-track links, the TikTok Video Scraper.
  • Profiles only. It does not search TikTok or list videos by hashtag or sound. Short links (vm.tiktok.com/..., vt.tiktok.com/...) and video URLs come back as a free VALIDATION_FAILED row. Open a short link in a browser and paste the creator's https://www.tiktok.com/@handle URL instead.
  • ISO dates only. oldestPostDateUnified and newestPostDate take a date or a timestamp with a zone. Relative values such as "7 days" or "1 month" are rejected.
  • A snapshot, not a feed. Counts are what TikTok showed at the time of the run, and media, cover, and music URLs are TikTok's signed links that expire. No files are downloaded.
  • No ban diagnosis. For a profile TikTok does not serve, TikTok does not say whether it never existed, was deleted, or was banned, so neither does the Actor.

How to use this Actor

  1. Open the Actor in the Apify Console.
  2. Paste one or more TikTok handles or profile URLs into profiles. Up to 50 unique profiles run per job.
  3. Set resultsPerPage to cap videos per profile, or 0 for all accessible videos. Optionally set oldestPostDateUnified and newestPostDate for a date window, and includeProfileRow for a free stats row per creator.
  4. Click Start. Each video is one success: true record. Profiles that cannot be read produce one free success: false record and do not stop the run. Only videos are charged.

The Actor is also callable from the Apify API and every official integration (Make, Zapier, n8n, Slack, webhooks). The API tab in the Console has ready-to-paste JavaScript, Python, and curl snippets.

Reliability

A bad profile never fails the batch. A private account, a missing creator, or a bad URL becomes a success: false record with a specific errorCode. The run keeps going and finishes successfully.

You never pay for a failure. Billing fires on an explicit per-video charge event, not on dataset writes, so profile rows, error rows, and the summary are free. There is no per-run start fee either.

Partial walks say so. If a page fails partway through a creator, or TikTok's list stalls or keeps repeating itself, the videos already delivered stay in your dataset, a free error row records how many (videosDelivered), and the profile's stopReason says why (expected_error, cursor_stalled or idle_pages). A walk that stops for a cap, a date, the budget, or a stalled list is never reported as complete.

Pinned posts do not cut a date walk short. With oldestPostDateUnified set, the walk stops early only when every non-pinned video on a page is older than your date, so an old pinned video at the top of a profile never ends the walk.

Automatic retry, then a clear answer. A blocked, rate-limited, or slow request is retried once from a fresh network identity before it is reported as failed. Each request runs under a 30-second deadline, so a stalled page returns a free DEADLINE_EXCEEDED record instead of hanging your run.

No duplicate charges. Aliases of one handle run once, a creator reached through two different inputs is walked once, and a video is charged at most once per run.

Genuine faults are not hidden. An outage of our backend, a response TikTok changed in a way we cannot read, or a failure to write or bill results fails the run, so a broken run never looks like a clean one with missing data.

FAQ

How do I scrape all videos from a TikTok profile?

Set resultsPerPage to 0. The Actor walks the creator's videos newest first until TikTok's list ends, or until the run's cost limit. The profile row and the OUTPUT summary show stopReason: source_exhausted and complete: true when it got to the end.

Why do I get fewer videos than the profile shows?

TikTok often serves fewer videos than the count on the profile: apifytech shows 63 and serves 57 to a full walk. The Actor returns every video TikTok lists and does not invent the rest. Check complete: true means the walk reached the end of TikTok's list. If it is false, stopReason tells you what stopped it, such as your resultsPerPage cap or the run's cost limit.

Can I scrape private TikTok accounts?

No. A private account returns a free success: false row with errorCode: TIKTOK_PRIVATE_ACCOUNT, and its videos are never requested. The Actor does not log in.

How do I get only videos from a date range?

Set oldestPostDateUnified and/or newestPostDate to a date (2026-01-01) or a timestamp with a zone (2026-01-01T12:00:00Z). Both ends are inclusive, and a date-only newestPostDate covers that whole UTC day. Videos outside the window are skipped and not charged, and the walk stops once it passes the oldest date. Combine with resultsPerPage: 0 to get every video in the window.

Do I pay for failed profiles?

No. Private, unavailable, and invalid profiles come back as free success: false rows, and the other profiles keep running. If a walk fails partway, you pay only for the videos already delivered.

Is it compatible with Clockworks TikTok Profile Scraper output?

For the core contract, yes. The inputs profiles, resultsPerPage, oldestPostDateUnified, and newestPostDate have the same names, and video records use the familiar text, createTimeISO, playCount, diggCount, commentCount, shareCount, collectCount, webVideoUrl, isPinned, input, and nested authorMeta, musicMeta, and videoMeta. Differences: every record has an itemType, resultsPerPage: 0 means all accessible videos, dates are ISO only (no relative values), there are no sorting or repost/story options, and failure records use this Actor's own errorCode values (listed above).

How do I get profile stats without videos?

Rows are videos, and every video row carries the creator's stats in authorMeta. Set includeProfileRow: true to also get one free profile row per creator, and a small resultsPerPage (such as 1) so you pay for few videos. A creator with zero videos to deliver always gets a free profile row with its stats.

Are pinned videos included?

Yes, as TikTok lists them, usually at the top. isPinned is true or false when TikTok says, null when it does not. Pinned videos are not guaranteed to come first or to be present.

What profile formats are accepted?

Handles (apifytech), @handles, and profile URLs: https://www.tiktok.com/@apifytech, also with m.tiktok.com, without www., with a query string, or with a # fragment. Short links, video URLs, and URLs with credentials or a port return a free VALIDATION_FAILED row.

Do I need a TikTok account or API key?

No. The Actor reads public profiles directly. There is nothing to log into and no TikTok developer key to provision. (You run it with your normal Apify account, like any Actor.)

How much does it cost?

$0.90 per 1,000 delivered videos, one charge per unique video. Profile rows, failed profiles, and empty runs cost $0, date filters are included, and there is no start fee.

Why is a field null?

TikTok did not report it for that video. The Actor never fills a gap with a guessed 0 or false, so null always means "unknown", not "none".

Can I also get comments or transcripts of these videos?

Yes, with the sibling Actors TikTok Comments Scraper and TikTok Transcript Scraper. Pass them the webVideoUrl values from this Actor's output as postURLs. They use the same failure-free billing.

Can I use this Actor from my own code?

Yes. Use the Apify API or one of the official SDKs (Node.js: apify-client, Python: apify-client). The Console shows ready-to-paste code samples on the API tab.

Found a bug or want a feature?

Open an issue on this Actor's Issues tab and include the requestId from any error record you saw. We respond within one business day.