# Tiktok Video Scraper (`fertech/tiktok-video-scraper`) Actor

- **URL**: https://apify.com/fertech/tiktok-video-scraper.md
- **Developed by:** [Fertech](https://apify.com/fertech) (community)
- **Features:** Limited permissions
- **Stats:** 17 total users, 8 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.79 / 1,000 results

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## TikTok Video Scraper

Extract complete data from any TikTok video URL — play counts, likes, shares, comments, author stats, music and video metadata, **plus subtitles, TikTok's own content categories, and the search keywords TikTok associates with each video**.

**One flat rate, whatever Apify plan you are on. No plan ladder — see the
pricing section of this listing for the current rate.**

### Why this one

**One flat rate, whatever plan you are on.** Most TikTok scrapers advertise
a "from $X" headline that applies only on the top subscription tier, and
charge Free and Starter customers considerably more. This one charges
everyone the same.

**A familiar shape.** Field names match the widely-used Clockworks TikTok
scrapers — `authorMeta.name`, `diggCount`, `playCount`,
`videoMeta.duration`, `createTimeISO` — so pipelines and spreadsheets built
around that output already know what to do with this one.

**Fields most scrapers skip.** TikTok's own content categories, and the
search keywords it associates with each video.

### What you get

Feed it a list of TikTok video URLs, get one structured record per video.

- **Engagement**: plays, likes, shares, comments, saves, reposts
- **Author**: username, nickname, verification, bio, followers, following, total likes, video count
- **Video**: duration, dimensions, cover image
- **Music**: track name, artist, original-sound flag, playable URL
- **Content**: caption text, hashtags, mentions, language, country of creation
- **Subtitles**: per-language WebVTT download links
- **Discovery data**: TikTok's own content categories and the search keywords it associates with the video — fields most scrapers do not return
- **Flags**: ad, pinned, slideshow, AI-generated

### Input

```json
{
  "postURLs": [
    "https://www.tiktok.com/@apifytech/video/7398101551744552225"
  ],
  "maxAttemptsPerUrl": 4
}
```

| Field | Required | Description |
|---|---|---|
| `postURLs` | Yes | TikTok video URLs. Short `vm.tiktok.com` / `vt.tiktok.com` / `tiktok.com/t/...` links work too. |
| `maxAttemptsPerUrl` | No | Total attempts per URL before giving up. Default 4. |

Unrecognised URLs are reported as error records rather than failing the run.

### Output

```json
{
  "id": "7242449293112577323",
  "text": "The struggle man lol",
  "createTimeISO": "2023-06-08T22:42:18.000Z",
  "authorMeta": {
    "name": "selenagomez",
    "nickName": "Selena Gomez",
    "verified": true,
    "fans": 58600000,
    "heart": 715400000,
    "video": 270
  },
  "musicMeta": { "musicName": "original sound", "musicAuthor": "Selena Gomez", "musicOriginal": true },
  "videoMeta": { "width": 576, "height": 1024, "duration": 13 },
  "diggCount": 15400000,
  "playCount": 100200000,
  "shareCount": 320400,
  "commentCount": 32400,
  "collectCount": 465929,
  "repostCount": 0,
  "hashtags": [],
  "mentions": [],
  "locationCreated": "US",
  "textLanguage": "en",
  "subtitleLinks": [
    { "language": "eng-US", "format": "webvtt", "source": "ASR", "downloadLink": "https://..." }
  ],
  "diversificationLabels": ["Comedy", "Performance"],
  "suggestedWords": ["Selena Gomez", "selena gomez dress merah"],
  "isSlideshow": false,
  "isPinned": false,
  "isAd": false,
  "isAigc": false,
  "webVideoUrl": "https://www.tiktok.com/@selenagomez/video/7242449293112577323",
  "submittedVideoUrl": "https://www.tiktok.com/@selenagomez/video/7242449293112577323"
}
```

### Errors

A URL that cannot be scraped still produces a record, carrying `error` and
`errorDescription`:

```json
{
  "submittedVideoUrl": "https://www.tiktok.com/@user/video/123",
  "error": "not-found",
  "errorDescription": "Video is deleted, private or does not exist"
}
```

| `error` | Meaning | Retried |
|---|---|---|
| `invalid-url` | Not a recognised TikTok video URL | no |
| `not-found` | Deleted, private, or never existed | no |
| `blocked` | TikTok's anti-bot challenge was not passed on any attempt | yes, up to `maxAttemptsPerUrl`; the session rotates once it has failed enough times |
| `unsupported-page` | A page loaded but was not a recognised video layout | no — re-fetching returns the same page |
| `network` | Connection, TLS, DNS or timeout failure, not a TikTok block | yes |

TikTok serves transient anti-bot challenges. The Actor detects them and
retries, so they rarely reach your dataset. If you do see `blocked` records,
raising `maxAttemptsPerUrl` gives each URL more attempts.

### Pricing: every submitted URL is charged once

See the pricing section of this listing for the current rate.

**One URL in, one charge out.**

Videos get deleted, accounts go private, and TikTok serves anti-bot
challenges. When a URL can't be scraped you get an error record instead of a
video record, so you always know what happened to it — and it costs the same
as a delivered one.

Retries and duplicates are not charged on top. You pay once per URL you
submit, however many attempts it takes.

### Requirements

**Residential proxies are required.** TikTok blocks datacenter IPs at close
to 100%, so a run without them cannot work.

That is why there is no proxy picker in the input form — the only thing it
could offer you is a configuration that fails. The Actor always uses Apify
residential proxies; an API caller can still override that.

Residential proxies are **not included in the Apify Free plan.** On the Free
plan this Actor stops with a clear message rather than spending your credit
on requests that cannot succeed.

### Limitations

Stated plainly so you can decide before you run it:

- **Video URLs only.** Profiles, hashtags, keyword search and comments are not supported in this version.
- **No watermark-free video downloads.**
- **No direct video file URLs.** TikTok's own links are IP-bound and expire within hours, so returning them would only hand you broken links.
- Subtitle download links carry an expiry — fetch them promptly.

### Reading the run summary

Each run writes a summary to the log and to the key-value store, under
`RUN_SUMMARY`:

```
requested 200 | delivered 187 (93.5%) | unpaid 0 | waf-retries 148 | notfound 8 | failed 5 | callbackErrors 0
```

| Field | What it counts |
|---|---|
| `requested` | URLs you submitted |
| `delivered` | URLs that produced a video record |
| `notfound` | Deleted, private or non-existent videos |
| `failed` | URLs that ended in any other error record |
| `waf-retries` | *Attempts* that hit an anti-bot challenge — not URLs |
| `unpaid` | URLs processed after the charge budget ran out |
| `callbackErrors` | Rows that could not be written. Should always be 0 |

**These do not sum to `requested`**, and that is deliberate. Every URL ends
as exactly one of `delivered`, `notfound` or `failed`. `waf-retries` sits
alongside them: one video challenged twice and then fetched adds 1 to
`delivered` and 2 to `waf-retries`.

### Using the API

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run = client.actor("YOUR_USERNAME/tiktok-video-scraper").call(run_input={
    "postURLs": ["https://www.tiktok.com/@apifytech/video/7398101551744552225"],
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["playCount"], item["authorMeta"]["name"])
```

### Is scraping TikTok legal?

This Actor collects only publicly available data — information anyone can see without logging in. It does not access private accounts or login-walled content. You remain responsible for how you use the data, particularly regarding personal information and the GDPR. If you plan to process personal data, seek your own legal advice first.

### Issues and requests

Found a bug, or want profiles, hashtags, search or comments supported? Open an issue on the Actor's Issues tab — feature demand genuinely drives what gets built next.

# Changelog

This Actor's version history is a separate document: https://apify.com/fertech/tiktok-video-scraper/changelog.md

# Actor input Schema

## `postURLs` (type: `array`):

One or more TikTok video URLs, e.g. https://www.tiktok.com/@user/video/1234567890. Short vm.tiktok.com links are supported.

## `maxAttemptsPerUrl` (type: `integer`):

TikTok serves transient anti-bot challenges. Each URL is retried on a new proxy session up to this many times. More attempts improve the success rate at the cost of proxy bandwidth.

## Actor input object example

```json
{
  "postURLs": [
    "https://www.tiktok.com/@apifytech/video/7398101551744552225"
  ],
  "maxAttemptsPerUrl": 4
}
```

# Actor output Schema

## `results` (type: `string`):

All scraped video records and any error records, newest run first.

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

Delivery counts and block rate for the run: requested, delivered, unpaid, waf-retries, notfound, failed.

# 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 = {
    "postURLs": [
        "https://www.tiktok.com/@apifytech/video/7398101551744552225"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fertech/tiktok-video-scraper").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 = { "postURLs": ["https://www.tiktok.com/@apifytech/video/7398101551744552225"] }

# Run the Actor and wait for it to finish
run = client.actor("fertech/tiktok-video-scraper").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 '{
  "postURLs": [
    "https://www.tiktok.com/@apifytech/video/7398101551744552225"
  ]
}' |
apify call fertech/tiktok-video-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fertech/tiktok-video-scraper"
        }
    }
}
```

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/6EKZ3zRIu5cPsFgeF/builds/KshEmhpRaUbm7aBQn/openapi.json
