TikTok Video Scraper avatar

TikTok Video Scraper

Pricing

$1.75 / 1,000 videos

Go to Apify Store
TikTok Video Scraper

TikTok Video Scraper

Export TikTok video metadata to JSON by video URL: caption, views, likes, comments, shares, saves, author, music, duration, cover and caption-track links. Deleted or unavailable videos return success:false, cost $0, and never break the batch. One record per video. $1.75 per 1,000 videos.

Pricing

$1.75 / 1,000 videos

Rating

0.0

(0)

Developer

apihq dev

apihq dev

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

17 hours ago

Last modified

Share

Export the public metadata of TikTok videos as clean JSON from their video URLs: caption, views, likes, comments, shares, saves, author, sound, duration, cover, hashtags, and caption-track links. No TikTok account, no API key, no browser.

The contract is simple: a deleted or unavailable video never fails your run, and you only pay for videos delivered. Paste a removed video or a bad URL, and it comes back as a success: false row while the rest of the batch keeps going. Pay-per-result at $1.75 per 1,000 videos. Failed videos cost $0.

See one run

Input:

{
"postURLs": [
"https://www.tiktok.com/@scout2015/video/6718335390845095173",
"https://www.tiktok.com/@apifytech/video/7398101551744552225",
"https://www.tiktok.com/@looooooooch/video/7332342275151760642",
"https://www.tiktok.com/@scout2015/video/7000000000000000001"
]
}

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

  • 3 success: true records, one per video, all billed. The apifytech video, for example: 595 views, 21 likes, 0 comments, 1 share, 1 save, 23 seconds, 576x1024, created in region CZ, five hashtags, and one English auto-generated caption track. A video with zero comments is still a full, billed record
  • 1 success: false row, errorCode: TIKTOK_VIDEO_UNAVAILABLE, for the missing video, not billed
  • Run status: SUCCEEDED
  • Charge: 3 delivered videos x $0.00175 = $0.00525

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

Why this Actor

  • A bad video never breaks the batch. A deleted, private, or unreachable video returns a success: false record with a machine-readable errorCode (TIKTOK_VIDEO_UNAVAILABLE, VALIDATION_FAILED, TIKTOK_TIMEOUT, ...). Branch on errorCode without parsing English.
  • You pay only for videos. Billing fires on an explicit per-video event, not on dataset writes, so failure rows sit in your dataset for free. There is no per-run start fee, so a run that delivers nothing costs nothing.
  • Drop-in field names. Records use the field names and nesting most TikTok scrapers already use (text, playCount, diggCount, commentCount, shareCount, collectCount, createTimeISO, webVideoUrl, authorMeta, musicMeta, videoMeta), 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 the numbers behind specific TikTok videos: campaign and influencer reporting, tracking views and engagement of known videos over time, enriching a list of video links with author and sound data, and building video datasets for analytics or LLM pipelines. The failure-free billing and machine-readable error rows matter most when the extraction runs unattended inside a larger pipeline, where one deleted video should never take down the batch. It works fine for one-off manual pulls too.

Input example

A single video:

{
"postURLs": ["https://www.tiktok.com/@scout2015/video/6718335390845095173"]
}

A batch of videos, copied from the browser with tracking parameters (they are ignored, and copies of the same video run once):

{
"postURLs": [
"https://www.tiktok.com/@scout2015/video/6718335390845095173?is_from_webapp=1&sender_device=pc",
"https://m.tiktok.com/@scout2015/video/6718335390845095173",
"https://www.tiktok.com/@apifytech/video/7398101551744552225/"
]
}

A daily stats refresh of the videos you track (schedule it in the Console and diff playCount between runs):

{
"postURLs": [
"https://www.tiktok.com/@looooooooch/video/7332342275151760642",
"https://www.tiktok.com/@apifytech/video/7398101551744552225"
]
}
FieldTypeDefaultWhat it does
postURLsarray of stringsTikTok video URLs, up to 50 unique videos per run. Copies of the same video (with www., m., a query string, or a trailing slash) are run and billed once.

Output example

One record per video. The author, sound, and media details are nested objects; the Console's table views flatten them to columns such as authorMeta.name and videoMeta.duration:

{
"success": true,
"id": "7398101551744552225",
"text": "📣 Yes, you can scrape job listings #data in a minute!! #webscraping #techtiktok #job #automation ",
"textLanguage": "en",
"createTime": 1722504752,
"createTimeISO": "2024-08-01T09:32:32.000Z",
"isAd": false,
"isPinned": null,
"locationCreated": "CZ",
"privateItem": false,
"secret": false,
"forFriend": false,
"authorMeta": {
"id": "7353570794285417504",
"name": "apifytech",
"nickName": "Apify",
"profileUrl": "https://www.tiktok.com/@apifytech",
"avatar": "https://p16-sign-va.tiktokcdn.com/...",
"signature": "...",
"verified": false,
"privateAccount": false,
"region": null,
"fans": 3589,
"following": 1,
"heart": 4145,
"video": 63,
"digg": 66,
"friends": 0
},
"musicMeta": {
"musicName": "original sound",
"musicAuthor": "Apify",
"musicOriginal": true,
"musicId": "7398101561353718560",
"musicAlbum": null,
"playUrl": "https://v16m.tiktokcdn.com/...",
"coverMediumUrl": "https://p16-sign-va.tiktokcdn.com/..."
},
"webVideoUrl": "https://www.tiktok.com/@apifytech/video/7398101551744552225",
"videoMeta": {
"duration": 23,
"height": 1024,
"width": 576,
"definition": "540p",
"format": "mp4",
"coverUrl": "https://p16-sign-va.tiktokcdn.com/...",
"originalCoverUrl": "https://p16-sign-va.tiktokcdn.com/...",
"dynamicCoverUrl": "https://p16-sign-va.tiktokcdn.com/...",
"subtitleLinks": [
{
"language": "eng-US",
"tiktokLink": "https://v16-webapp.tiktok.com/...",
"downloadLink": null,
"source": "ASR",
"version": "1:big_caption",
"format": "webvtt",
"size": 872,
"expiresAt": "2026-11-04T13:16:40.000Z"
}
]
},
"hasSubtitleDescriptors": true,
"diggCount": 21,
"shareCount": 1,
"playCount": 595,
"commentCount": 0,
"collectCount": 1,
"repostCount": 0,
"hashtags": [
{ "id": "400574", "name": "data", "title": "data", "cover": null },
{ "id": "1622560817599509", "name": "webscraping", "title": "webscraping", "cover": null }
],
"mentions": [],
"submittedVideoUrl": "https://www.tiktok.com/@apifytech/video/7398101551744552225"
}

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

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

{
"success": false,
"url": "https://www.tiktok.com/@scout2015/video/7000000000000000001",
"input": "https://www.tiktok.com/@scout2015/video/7000000000000000001",
"errorCode": "TIKTOK_VIDEO_UNAVAILABLE",
"error": "TikTok reports this video does not exist or is not available",
"requestId": "req_ea6f91d1d7da4706a7c41189",
"statusCode": 422
}

Split hits from misses on the success field. Quote requestId in any support issue and we can trace the exact request.

What you get

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

FieldTypeWhat it is
successbooleantrue for a video (billed), false for a video that could not be read (not billed).
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.
webVideoUrlstringThe canonical video URL, with the author's current handle.
submittedVideoUrlstringThe input URL this record answers, as you gave it.
authorMetaobjectid, name (handle), nickName, profileUrl, avatar, signature (bio), verified, privateAccount, region, fans, following, heart (likes received), video (videos posted), digg (videos liked), friends.
musicMetaobjectmusicName, musicAuthor, musicOriginal, musicId, musicAlbum, playUrl, coverMediumUrl. All null when TikTok reports no sound.
videoMetaobjectduration (seconds), width, height, definition, format, coverUrl, originalCoverUrl, dynamicCoverUrl, subtitleLinks.
videoMeta.subtitleLinksarray or nullCaption tracks TikTok advertises: language, tiktokLink, downloadLink (always null), source (ASR = auto-generated), version, format, size (bytes), expiresAt. [] when TikTok lists none.
hasSubtitleDescriptorsboolean or nulltrue when at least one caption track is listed, false when none, null when TikTok did not say.
hashtagsarray or nullHashtags in the caption: id, name, title, cover.
mentionsarray or null@handles mentioned in the caption.
isAd, privateItem, secret, forFriendboolean or nullTikTok's own flags for the video.
isPinnednullAlways null: the video page does not say whether a video is pinned on its profile.
locationCreatedstring or nullRegion code TikTok reports for where the video was created. Not the author's location.

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

Videos that could not be read carry success: false, url and input (the URL you gave), a machine-readable errorCode, a human-readable error, the service requestId, and the HTTP statusCode. They are not charged:

CodeMeaning
TIKTOK_VIDEO_UNAVAILABLEThe video is deleted, private, or otherwise unavailable.
TIKTOK_UNAVAILABLETikTok did not serve this video and gave no reason.
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 video request hit its 30-second deadline.
VALIDATION_FAILEDThe URL is not a supported TikTok video URL (including short links and photo posts), or TikTok rejected the video ID.
NO_INPUTNo usable postURLs were given. The run ends successfully with this single row.

Pricing

Pay-per-result. One charge per video delivered: $0.00175 each, which is $1.75 per 1,000 videos. The charge fires only after a video record lands in your dataset.

  • 1,000 delivered videos cost $1.75.
  • 10,000 delivered videos cost $17.50.
  • A run that hits only deleted or unavailable videos costs $0.
  • There is no per-run start fee, so an empty run is genuinely free.

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 video and stops cleanly when the budget runs out.

What this Actor does not do

Honest scope, so you know before you run it:

  • Video URLs only. It reads the videos you give it. It does not find videos from a profile, hashtag, sound, or search query, and it does not fetch related videos.
  • Full video URLs only. Short share links (vm.tiktok.com/..., vt.tiktok.com/...) and photo/slideshow posts (/photo/...) come back as a free success: false row with errorCode: VALIDATION_FAILED. Open a short link in a browser and paste the full https://www.tiktok.com/@user/video/<id> URL instead.
  • No downloads. It does not download videos, covers, audio, or caption files. Media and caption URLs are returned as TikTok serves them.
  • No transcripts. subtitleLinks lists the caption tracks TikTok advertises; the Actor does not fetch or parse their text.
  • Only what the video page says. Pinned status is not on the page (isPinned is always null), and author region is usually null. Ad, sponsorship, and shop flags beyond isAd are not reported.
  • No login-only content. Private videos and anything that needs a TikTok account return success: false.

How to use this Actor

  1. Open the Actor in the Apify Console.
  2. Paste one or more TikTok video URLs into postURLs. Up to 50 unique videos run per job.
  3. Click Start. Each video is one success: true record. Videos that cannot be read produce one success: false record and do not stop the run. Only delivered 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 video never fails the batch. A deleted video, a bad URL, or a video TikTok will not serve 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 success: false records are free. There is no per-run start fee either.

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

No duplicate charges. The same video pasted several times in one input (with m., a query string, or a trailing slash) is fetched and charged once per run.

Genuine faults are not hidden. An outage of our backend, a page 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

Do I need a TikTok account or API key?

No. The Actor reads public video pages 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.)

What URL formats are accepted?

Full TikTok video URLs: https://www.tiktok.com/@user/video/<id>, also with m.tiktok.com, without www., with a query string, or with a trailing slash. Short links (vm.tiktok.com, vt.tiktok.com) and photo posts are not supported and return a free success: false row with errorCode: VALIDATION_FAILED.

Is the output compatible with other TikTok video scrapers?

Yes for the core fields: text, createTimeISO, playCount, diggCount, commentCount, shareCount, collectCount, webVideoUrl, and the nested authorMeta, musicMeta, and videoMeta objects use the names most TikTok scrapers already use. Failure records name the error in errorCode, but the code values are this Actor's own (listed above).

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 get the transcript of a video?

Not with this Actor. It tells you whether caption tracks exist (hasSubtitleDescriptors) and lists their links in videoMeta.subtitleLinks, but it does not fetch the caption text.

Can I download the video file?

No. The Actor returns metadata and TikTok's own media links only.

How much does it cost?

$1.75 per 1,000 delivered videos. 10,000 videos cost $17.50. Failed videos and empty runs cost $0, and there is no start fee.

What if a video was deleted or is private?

It comes back as a success: false record with errorCode: TIKTOK_VIDEO_UNAVAILABLE. You are not charged, and the other videos in the batch keep running.

Can I also get the comments of these videos?

Yes, with the sibling Actor TikTok Comments Scraper, which takes the same postURLs and uses 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.