TikTok Video Scraper
Pricing
$1.75 / 1,000 videos
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
17 hours ago
Last modified
Categories
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: truerecords, one per video, all billed. Theapifytechvideo, for example: 595 views, 21 likes, 0 comments, 1 share, 1 save, 23 seconds, 576x1024, created in regionCZ, five hashtags, and one English auto-generated caption track. A video with zero comments is still a full, billed record - 1
success: falserow,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: falserecord with a machine-readableerrorCode(TIKTOK_VIDEO_UNAVAILABLE,VALIDATION_FAILED,TIKTOK_TIMEOUT, ...). Branch onerrorCodewithout 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 guessed0orfalse.
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"]}
| Field | Type | Default | What it does |
|---|---|---|---|
postURLs | array of strings | TikTok 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:
| Field | Type | What it is |
|---|---|---|
success | boolean | true for a video (billed), false for a video that could not be read (not billed). |
id | string | The video ID. |
text | string | The caption, possibly empty. |
textLanguage | string or null | Caption language code TikTok reports. |
createTime / createTimeISO | integer / string | When the video was posted: Unix seconds and ISO 8601 UTC. |
playCount, diggCount, commentCount, shareCount | integer | Views, likes, comments, shares. Always present. |
collectCount, repostCount | integer or null | Saves and reposts. |
webVideoUrl | string | The canonical video URL, with the author's current handle. |
submittedVideoUrl | string | The input URL this record answers, as you gave it. |
authorMeta | object | id, name (handle), nickName, profileUrl, avatar, signature (bio), verified, privateAccount, region, fans, following, heart (likes received), video (videos posted), digg (videos liked), friends. |
musicMeta | object | musicName, musicAuthor, musicOriginal, musicId, musicAlbum, playUrl, coverMediumUrl. All null when TikTok reports no sound. |
videoMeta | object | duration (seconds), width, height, definition, format, coverUrl, originalCoverUrl, dynamicCoverUrl, subtitleLinks. |
videoMeta.subtitleLinks | array or null | Caption tracks TikTok advertises: language, tiktokLink, downloadLink (always null), source (ASR = auto-generated), version, format, size (bytes), expiresAt. [] when TikTok lists none. |
hasSubtitleDescriptors | boolean or null | true when at least one caption track is listed, false when none, null when TikTok did not say. |
hashtags | array or null | Hashtags in the caption: id, name, title, cover. |
mentions | array or null | @handles mentioned in the caption. |
isAd, privateItem, secret, forFriend | boolean or null | TikTok's own flags for the video. |
isPinned | null | Always null: the video page does not say whether a video is pinned on its profile. |
locationCreated | string or null | Region 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:
| Code | Meaning |
|---|---|
TIKTOK_VIDEO_UNAVAILABLE | The video is deleted, private, or otherwise unavailable. |
TIKTOK_UNAVAILABLE | TikTok did not serve this video and gave no reason. |
TIKTOK_BLOCKED | TikTok refused the request after an automatic retry. |
TIKTOK_RATE_LIMITED | TikTok rate-limited the request after an automatic retry. |
TIKTOK_TIMEOUT | TikTok did not answer in time after an automatic retry. |
DEADLINE_EXCEEDED | The video request hit its 30-second deadline. |
VALIDATION_FAILED | The URL is not a supported TikTok video URL (including short links and photo posts), or TikTok rejected the video ID. |
NO_INPUT | No 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 freesuccess: falserow witherrorCode: VALIDATION_FAILED. Open a short link in a browser and paste the fullhttps://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.
subtitleLinkslists 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 (
isPinnedis alwaysnull), and authorregionis usuallynull. Ad, sponsorship, and shop flags beyondisAdare not reported. - No login-only content. Private videos and anything that needs a TikTok account return
success: false.
How to use this Actor
- Open the Actor in the Apify Console.
- Paste one or more TikTok video URLs into
postURLs. Up to 50 unique videos run per job. - Click Start. Each video is one
success: truerecord. Videos that cannot be read produce onesuccess: falserecord 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.