# TikTok Video Scraper - Bulk Data by URL (`praise-most-high/tiktok-bulk-video-data`) Actor

TikTok Video Scraper that extracts video records from a list of TikTok video URLs or IDs. Returns description, post date, duration, plays, likes, comments, shares, saves, hashtags, sound details, creator profile and cover image URLs. No login, no API key. Deleted videos are reported, never charged.

- **URL**: https://apify.com/praise-most-high/tiktok-bulk-video-data.md
- **Developed by:** [angel nguyen](https://apify.com/praise-most-high) (community)
- **Categories:** Social media, Videos, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 video extracteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

TikTok Bulk Video Data Extractor collects full TikTok video records from a list of video links or IDs you already have. You paste the videos, you get one typed row each — description, post date, duration, plays, likes, comments, shares, saves, hashtags, the sound, the creator, and cover image URLs that actually load. There is no login, no API key and no developer account anywhere in it.

It is built for the job that starts with a list: the videos in a campaign report, the posts a brand-monitoring tool flagged, the creator submissions in a spreadsheet. If you already know *which* videos you care about, this turns them into data.

![TikTok Bulk Video Data Extractor](https://apify-image-uploads-prod.s3.us-east-1.amazonaws.com/0ezaunoiVKF5LFUup-actor-JeMOra7CXtEVpgaIJ-Dqzaqg9tJC-tiktok-bulk-video-data.png)

### Key features

- **Paste links or bare IDs.** Full `tiktok.com/@user/video/<id>` and `/photo/<id>` links, or just the numeric video ID — mixed together. You do not need to go and find the @handle for an ID.
- **Duplicates are removed before anything is fetched**, so the same video pasted twice is fetched once and charged once.
- **48 fields per video**, read from the page's own data payload rather than scraped out of rendered HTML — which is what keeps the record stable when TikTok re-skins the page.
- **Deleted and private videos are reported, not silently dropped.** They come back as a row with `available: false` and a reason, and are never charged.
- **Creators are billed once each, not once per video.** 100 videos from 3 creators costs 3 creator charges.
- **The 200-that-carries-nothing is handled.** TikTok answers an unavailable video with HTTP 200 and an internal status code, so a status-code check alone reports a healthy empty result. This Actor reads the internal code.

### How it works

1. You give it a list of video links or IDs in **TikTok video URLs or IDs**.
2. Every entry is parsed to a numeric video ID. Anything that is not a video reference is reported in the run log with a reason and skipped — it is never guessed at.
3. The list is de-duplicated, then capped at **Maximum videos per run** so a large paste cannot cost more than you intended.
4. Each video page is fetched and its embedded data payload is read, paced by **Delay between requests**.
5. Each result is sorted into available or unavailable and written to the dataset.

Step 5 is the part worth reading twice. TikTok returns **HTTP 200 for a video that no longer exists**, carrying an internal status code inside the page data instead. A scraper that trusts the HTTP status reports that video as a healthy empty record. This Actor treats only internal status `0` **with** a video object as success; everything else becomes an explicit unavailable row.

### Input

Every field below is optional except the list itself.

| Field | Type | Default | What it does |
|---|---|---|---|
| `videoUrls` | array | *required* | The videos to extract, one per line. Accepts full tiktok.com/@user/video/<id> and /photo/<id> links, and bare numeric video IDs — you can mix both. Duplicates are removed before anything is fetched, so a list pasted twice is never charged twice. Short vm.tiktok.com links must be expanded first. |
| `maxVideos` | integer | `1000` | A hard ceiling on how many videos this run will fetch, applied after duplicates are removed. Use it to bound the cost of a large paste. Set it to the number of videos you are willing to pay for. |
| `includeStats` | boolean | `true` | Add plays, likes, comments, shares, saves and reposts to each record, read from TikTok's own counters at the moment of the run. |
| `includeAuthor` | boolean | `true` | Add the creator block: username, display name, profile URL, avatar image URL, bio, verified and private-account flags, follower count and video count. |
| `includeMusic` | boolean | `true` | Add the sound attached to the video: its ID, title, the account that owns it, duration, album, and whether it is an original sound rather than a licensed track. |
| `includeUnavailable` | boolean | `true` | Emit a row for every requested video that could not be read, carrying available=false and the reason — deleted, private, region-blocked or removed. These rows are never charged. Turn this off to receive only successful extractions. |
| `requestDelayMs` | integer | `400` | Milliseconds to wait between videos. The default of 400 ms paces a run politely against the source. Raise it for very large lists; 0 disables the pause. |

Example input — this is the Actor's own prefill, so it is exactly what you see in the form:

```json
{
  "videoUrls": [
    "https://www.tiktok.com/@juliettheband/video/7676891956911295758",
    "https://www.tiktok.com/@humminglion/video/7662540717725732126",
    "7638649825382190350"
  ],
  "maxVideos": 100,
  "includeStats": true,
  "includeAuthor": true,
  "includeMusic": true,
  "includeUnavailable": true,
  "requestDelayMs": 400
}
```

### Output

One row per requested video. This is a **real row from a real run** of this Actor, not an illustration:

```json
{
  "videoId": "7638649825382190350",
  "url": "https://www.tiktok.com/@atmoio/video/7638649825382190350",
  "inputRef": "7638649825382190350",
  "available": true,
  "unavailableReason": null,
  "description": "Turtleneck optional but highly recommended. #ai #chatgpt #tech #bigtech #job",
  "createdAt": "2026-05-11T15:03:25.000Z",
  "durationSeconds": 371,
  "width": 720,
  "height": 1280,
  "ratio": "720p",
  "definition": "720p",
  "format": "mp4",
  "videoSizeBytes": 30024579,
  "coverImageUrl": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/osq7yAAfUAHipoWBEyFEA8DiEAAr2KAIEAfNke~tplv-tiktokx-origin.image?dr=9636&x-expires=1788285600&x-signature=nfpH0mtsMbld1QfS%2Fdd6T2gC8Iw%3D&t=4d5b0474&ps=13740610&shp=81f88b70&shcp=43f4a2f9&idc=useast8",
  "dynamicCoverUrl": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/osq7yAAfUAHipoWBEyFEA8DiEAAr2KAIEAfNke~tplv-tiktokx-origin.image?dr=9636&x-expires=1788285600&x-signature=IKpHze%2FcYe28vHIl09SgRINg2JU%3D&t=4d5b0474&ps=13740610&shp=81f88b70&shcp=43f4a2f9&idc=useast8",
  "hashtags": [
    "ai",
    "chatgpt",
    "Tech",
    "bigtech",
    "job",
    "tech"
  ],
  "mentions": [],
  "isAd": false,
  "isAiGenerated": false,
  "aiGeneratedNote": null,
  "locationCreated": "US",
  "textLanguage": "en",
  "duetEnabled": false,
  "stitchEnabled": false,
  "commentsEnabled": true,
  "playCount": 148100,
  "likeCount": 11800,
  "commentCount": 494,
  "shareCount": 2759,
  "saveCount": 2781,
  "repostCount": 0,
  "authorId": "7598585030021268535",
  "authorUsername": "atmoio",
  "authorNickname": "Mo Bitar",
  "authorUrl": "https://www.tiktok.com/@atmoio",
  "authorAvatarUrl": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/bef21a6f8a63b8b73c44250aabe570c1~tplv-tiktokx-cropcenter:1080:1080.jpeg?dr=9640&refresh_token=a68da889&x-expires=1788285600&x-signature=cKWrNGJhObit5ssskoP2vB%2Fv3es%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8",
  "authorBio": "Exploring what AI actually is. @atmoio on YouTube.",
  "authorVerified": false,
  "authorPrivateAccount": false,
  "authorFollowerCount": 32300,
  "authorVideoCount": 50,
  "musicId": "7638650059680254733",
  "musicTitle": "original sound - Mo Bitar",
  "musicAuthorName": "Mo Bitar",
  "musicIsOriginalSound": true,
  "musicDurationSeconds": 371,
  "musicAlbum": null
}
```

And this is a video that is no longer available. It carries the reason, invents no fields, and was not charged:

```json
{
  "videoId": "7226295362399767854",
  "url": "https://www.tiktok.com/@nasa/video/7226295362399767854",
  "inputRef": "https://www.tiktok.com/@nasa/video/7226295362399767854",
  "available": false,
  "unavailableReason": "UNAVAILABLE",
  "unavailableDetail": "item doesn't exist",
  "description": null,
  "createdAt": null,
  "coverImageUrl": null,
  "hashtags": [],
  "mentions": []
}
```

#### Availability states

| `unavailableReason` | What it means | Charged |
|---|---|---|
| *(null, `available: true`)* | The video was read and returned in full | Yes |
| `UNAVAILABLE` | TikTok reports the video as deleted, private, region-blocked, or the account removed | **No** |
| `NOT_FOUND` | TikTok returned HTTP 404 for the video page | **No** |
| `NO_PAYLOAD` | The page arrived without its data payload — usually a temporary interstitial, worth retrying | **No** |

Set **Report unavailable videos** to `false` if you want only the successful rows.

### Pricing

This Actor bills per delivered unit. A video it could not read costs nothing, and a run that reads nothing costs nothing.

| Event | Price | When you are charged |
|---|---|---|
| `video-extracted` | **$0.005** | Charged once per video record delivered to the dataset. A video that is deleted, private, region-blocked or removed is reported with available=false and is never charged, and duplicate URLs in your input are removed before anything is fetched. |
| `creator-profile-resolved` | **$0.0015** | Charged once per DISTINCT creator in the run, not once per video. Extracting 100 videos from 3 creators costs 3 of these, not 100. It covers the creator block: username, display name, avatar, bio, verified and private flags, follower count and video count. Turning off Include creator profile removes this charge entirely. |

What that works out to:

| Run | Videos | Creators | Total |
|---|---|---|---|
| 100 videos from one creator | $0.50 | $0.00 | **$0.50** |
| 100 videos from 25 creators | $0.50 | $0.04 | **$0.54** |
| 1,000 videos from 50 creators | $5.00 | $0.07 | **$5.08** |
| 1,000 videos with creator profiles switched off | $5.00 | $0.00 | **$5.00** |

The run whose output is shown above requested 4 videos, delivered 3 from 3 creators, for **$0.02** — with the 1 unavailable video costing nothing.

Note the second row against the first: the creator charge is driven by how many **distinct** creators are in your list, not by how many videos. A list of 1,000 videos from one creator pays that charge once.

### What this Actor is not

It is a resolver, not a discovery tool. It does not search TikTok, does not walk a creator's feed, does not read comments, and does not read anything behind a login.

**It also does not download video files, and that is deliberate.** TikTok's video CDN refuses requests that do not come from a browser session — measured HTTP 403 on both the play and download URLs on 2026-08-30 — so returning those URLs would hand you a link that does not work. The cover and dynamic-cover image URLs **are** returned, because those were verified to fetch outside a browser.

### Limits and troubleshooting

- **Short links.** `vm.tiktok.com` and `vt.tiktok.com` links are refused with an explicit reason rather than half-resolved. Open one in a browser and paste the full URL it lands on.
- **Counters are a snapshot.** Plays, likes and comments are read at the moment of the run. Two runs a day apart will legitimately disagree.
- **Everything came back unavailable.** Check the IDs first: a TikTok video ID is the long number at the end of the URL. If known-good IDs also fail, the source may be serving an interstitial — those show as `NO_PAYLOAD`, which is worth retrying.
- **A run stopped early.** If your event budget is reached mid-run, the Actor stops, says so in the log, and sets `stoppedByEventBudget` in the run summary — it never returns a short list that reads as a complete one.

### Integrations and API

The dataset is available in JSON, CSV, Excel, XML and RSS from the run's storage, and this Actor can be called from anywhere the [Apify API](https://docs.apify.com/api/v2) reaches — including the [Apify JavaScript and Python clients](https://docs.apify.com/api/client/js/), scheduled runs, webhooks, and Make or Zapier via Apify's own connectors.

```bash
curl -X POST "https://api.apify.com/v2/acts/praise-most-high~tiktok-bulk-video-data/runs?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"videoUrls":["https://www.tiktok.com/@humminglion/video/7662540717725732126"]}'
```

Because it runs on pay-per-event pricing with limited permissions, it can also be called by an AI agent through Apify's agentic payment flow.

### FAQ

**Do I need a TikTok account, an API key or a developer application?**
No. The Actor reads the public video page — the same page anyone gets by opening the link — and takes the structured data the page already carries.

**Can it find videos by hashtag, keyword or creator?**
No, and it does not pretend to. It resolves videos you name. If you need discovery, this is the wrong Actor and you should not buy it for that.

**Can it download the video file?**
No. See *What this Actor is not* above — TikTok's video CDN returns 403 outside a browser session, so a download URL would be a broken promise. Cover images are returned and do work.

**Does it work on private or deleted videos?**
It tells you they are unavailable. It cannot read them and does not charge you for finding out.

**Why is my run smaller than my input list?**
Three reasons, all visible in the run log and the run summary: entries that were not video references, duplicates removed, and the **Maximum videos per run** cap. The summary in the key-value store gives exact counts for each.

**Are the engagement numbers exact?**
They are what TikTok's own page reports at the moment of the run, including its rounding. TikTok returns some counters as strings; they are normalised to numbers so you can sort and sum them without cleaning first.

**Is this affiliated with TikTok?**
No. See the legal note below.

### Other actors

This Actor is one of a small set on this account that read first-party public endpoints without an API key, alongside extractors and monitors for X (Twitter) posts, YouTube channel uploads and Telegram channel messages. Each reads one source and does one job.

### Legal

This Actor is **unofficial** and is **not affiliated with, endorsed by, or sponsored by TikTok**, ByteDance Ltd., or any of their subsidiaries. "TikTok" is a trademark of its owner and is used here only to describe what this Actor reads.

It accesses only publicly available video data — the same data the public video page serves to any visitor — and it accesses no private, protected or logged-in content. You are responsible for how you use the data you collect, including compliance with the [TikTok Terms of Service](https://www.tiktok.com/legal/page/row/terms-of-service/en) and with data-protection law in your jurisdiction where the videos you collect contain personal data.

### Your feedback

Found a video this Actor handles badly, or a field you need that the page serves and this does not return? Open an issue from the Actor's **Issues** tab on its Store page. Issues are read and answered, and a bug report with the video ID that triggered it is the fastest possible fix.

# Actor input Schema

## `videoUrls` (type: `array`):

The videos to extract, one per line. Accepts full tiktok.com/@user/video/<id> and /photo/<id> links, and bare numeric video IDs — you can mix both. Duplicates are removed before anything is fetched, so a list pasted twice is never charged twice. Short vm.tiktok.com links must be expanded first.

## `maxVideos` (type: `integer`):

A hard ceiling on how many videos this run will fetch, applied after duplicates are removed. Use it to bound the cost of a large paste. Set it to the number of videos you are willing to pay for.

## `includeStats` (type: `boolean`):

Add plays, likes, comments, shares, saves and reposts to each record, read from TikTok's own counters at the moment of the run.

## `includeAuthor` (type: `boolean`):

Add the creator block: username, display name, profile URL, avatar image URL, bio, verified and private-account flags, follower count and video count.

## `includeMusic` (type: `boolean`):

Add the sound attached to the video: its ID, title, the account that owns it, duration, album, and whether it is an original sound rather than a licensed track.

## `includeUnavailable` (type: `boolean`):

Emit a row for every requested video that could not be read, carrying available=false and the reason — deleted, private, region-blocked or removed. These rows are never charged. Turn this off to receive only successful extractions.

## `requestDelayMs` (type: `integer`):

Milliseconds to wait between videos. The default of 400 ms paces a run politely against the source. Raise it for very large lists; 0 disables the pause.

## Actor input object example

```json
{
  "videoUrls": [
    "https://www.tiktok.com/@juliettheband/video/7676891956911295758",
    "https://www.tiktok.com/@humminglion/video/7662540717725732126",
    "7638649825382190350"
  ],
  "maxVideos": 100,
  "includeStats": true,
  "includeAuthor": true,
  "includeMusic": true,
  "includeUnavailable": true,
  "requestDelayMs": 400
}
```

# Actor output Schema

## `videos` (type: `string`):

One row per requested video. Rows with available=false carry the reason and were never charged.

## `runSummary` (type: `string`):

Counts of what was extracted, what was unavailable and what was charged.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "videoUrls": [
        "https://www.tiktok.com/@juliettheband/video/7676891956911295758",
        "https://www.tiktok.com/@humminglion/video/7662540717725732126",
        "7638649825382190350"
    ],
    "maxVideos": 100,
    "includeStats": true,
    "includeAuthor": true,
    "includeMusic": true,
    "includeUnavailable": true,
    "requestDelayMs": 400
};

// Run the Actor and wait for it to finish
const run = await client.actor("praise-most-high/tiktok-bulk-video-data").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "videoUrls": [
        "https://www.tiktok.com/@juliettheband/video/7676891956911295758",
        "https://www.tiktok.com/@humminglion/video/7662540717725732126",
        "7638649825382190350",
    ],
    "maxVideos": 100,
    "includeStats": True,
    "includeAuthor": True,
    "includeMusic": True,
    "includeUnavailable": True,
    "requestDelayMs": 400,
}

# Run the Actor and wait for it to finish
run = client.actor("praise-most-high/tiktok-bulk-video-data").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "videoUrls": [
    "https://www.tiktok.com/@juliettheband/video/7676891956911295758",
    "https://www.tiktok.com/@humminglion/video/7662540717725732126",
    "7638649825382190350"
  ],
  "maxVideos": 100,
  "includeStats": true,
  "includeAuthor": true,
  "includeMusic": true,
  "includeUnavailable": true,
  "requestDelayMs": 400
}' |
apify call praise-most-high/tiktok-bulk-video-data --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,praise-most-high/tiktok-bulk-video-data"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/JeMOra7CXtEVpgaIJ/builds/NgJwVhdIlNEzEc6zv/openapi.json
