# TikTok Video Scraper — Views, Likes, Hashtags & Music (`andronixmd/tiktok-scraper`) Actor

Scrape TikTok video data from video URLs: view, like, comment, share and save counts, description, hashtags, author, publish time, duration, thumbnail and music track. Export JSON, CSV or Excel. No login and no video download. $0.002 per video and $0.002 per run.

- **URL**: https://apify.com/andronixmd/tiktok-scraper.md
- **Developed by:** [Sergey Andronik](https://apify.com/andronixmd) (community)
- **Categories:** Social media, Videos, Developer tools
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 video scrapeds

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

Scrape **TikTok video data** from video URLs into clean JSON. For each public TikTok
video, TikTok Video Scraper returns the view, like, comment, share and save counts, the
description and hashtags, the author, the publish time, the duration, the thumbnail and
the music track. It reads only the video's public page data: the video itself is never
downloaded and no AI is run, so results are fast and cost $0.002 per video. Use the
output for spreadsheets, dashboards, trend and campaign tracking, influencer reports or
LLM prompts. No TikTok login, cookies or API key are needed.

### What TikTok data you get

| Field | Content |
|---|---|
| `url`, `video_id` | The URL you submitted and TikTok's video ID. |
| `description`, `hashtags` | The video description and the hashtags parsed from it. |
| `author_username`, `author_name`, `author_id`, `author_url` | The creator. |
| `author_sec_uid` | TikTok's stable secondary ID for the creator; it survives a handle change. |
| `published_at`, `upload_date` | Publish time (UTC, ISO 8601) and date. |
| `duration_s`, `width`, `height` | Video length in seconds, and the size in pixels of the best video stream. |
| `view_count`, `like_count`, `comment_count`, `share_count`, `save_count` | Engagement counts as TikTok shows them. |
| `thumbnail_url`, `cover_url`, `dynamic_cover_url` | Cover image links: the origin cover, the cover, and the animated cover. TikTok signs these links and they expire. |
| `music_title`, `music_artist`, `music_album` | The sound used in the video. The album only when TikTok lists one. |
| `available_caption_languages` | The caption tracks TikTok lists for the video, `[{ "language", "name" }]`, with TikTok's language codes such as `eng-US`. Only when the video has captions; most videos have none. |
| `billing` | Whether this row was charged. |

The Dataset has two views: **Videos** (description, author, date, duration and counts)
and **Hashtags and music**.

### How to scrape TikTok videos

1. **Paste your video URLs.** Full `tiktok.com/@user/video/…` links and the short
   `vm.tiktok.com/…`, `vt.tiktok.com/…` and `tiktok.com/t/…` share links work, up to
   1,000 per run.
2. **Set a run budget** in the run options if you want a hard cap on the spend.
3. **Start the run and open the Dataset.** One row per URL. Export it as JSON, CSV or
   Excel, or read it through the API.

### Input

```json
{
  "urls": ["https://www.tiktok.com/@khaby.lame/video/7420109434153717024"]
}
```

| Field | What it does |
|---|---|
| `urls` | Up to 1,000 public TikTok video URLs. Required unless you set `datasetId`. |
| `datasetId` | Read URLs from another run's dataset. See [Chain from another Actor](#chain-from-another-actor). |
| `datasetUrlField`, `maxItems` | The dataset field that holds the URL (detected when empty), and how many rows to read (default 100, at most 1,000). |

### Start from a profile

Put public profile URLs in **TikTok profiles** (`profileUrls`) to scrape their newest videos:

```json
{
  "profileUrls": ["https://www.tiktok.com/@bbcnews"],
  "maxItemsPerSource": 50
}
```

- Profile URLs look like `https://www.tiktok.com/@bbcnews`. The newest videos are listed and sorted by date, so pinned videos do not crowd out new ones.
- Each row adds `source_url`, `source_type` (`tiktok_profile`) and `source_title`.
- `publishedAfter` (YYYY-MM-DD, UTC) uses the exact upload time from the listing.
- TikTok's profile listing fails intermittently (in tests from one network on 2026-10-31, 4 of 10 attempts succeeded), so each profile is tried up to 3 times with a pause between tries.
- Every listed video gets the full per-video data, as for a URL you submit.
- `maxItemsPerSource` (default 20, at most 200) limits how many of the newest items each source contributes. A run holds at most 1,000 items in total, counting `urls` and dataset rows.
- Each listed item is processed and charged exactly like a URL you submit. Listing itself is free.
- The example link in the URL list is processed and charged too: remove it when you want only listed items.
- Listed items run after `urls` and dataset rows, source by source in input order; an item already in the run is skipped.
- A source that cannot be listed becomes one error row with `source_url` and an `error` naming it (for example `profileUrls[1]: …`), and it is not charged. The other sources continue. If no source lists anything, the run fails before any charge.
- The listing is saved when the run starts, so a restarted run keeps the same items even if the source changed meanwhile.

### Scheduled runs: only new items

Turn on **Skip items processed by earlier runs** (`skipPreviouslyProcessed`) and run the
Actor on a [schedule](https://docs.apify.com/platform/schedules) to get only what is new
since the last run.

- The Actor keeps a key-value store named `polyglot-processed-tiktok-scraper` in your Apify
  account, or `polyglot-processed-tiktok-scraper--<memoryKey>` when you set **Memory name**
  (`memoryKey`), so separate schedules can keep separate memories. Other Actors keep
  their own stores, even with the same `memoryKey`. It records each item after the item
  is delivered and charged; failed items are tried again next time.
- It applies to every URL in the run: listed ones, dataset rows and the items listed
  from sources.
- When nothing is new, the run ends without any charge. Only error rows appear then, for
  sources or dataset rows that could not be used.
- If the store cannot be opened or read, the run stops before any charge, and the next
  scheduled run tries again. If an item's record cannot be written, the item is still
  delivered and the log says so; a later run may process and charge it again.
- Delete the store in Apify Console (Storage → Key-value stores) to start over. Do not run
  two runs with the same memory at the same time: both would process the same new items.

### Chain from another Actor

Run a TikTok scraper or search run such as clockworks/tiktok-scraper first, then paste the dataset ID of that run into **Dataset ID**
(`datasetId`) to refresh the data of every one of its TikTok videos:

```json
{ "datasetId": "<dataset ID from the other run>", "maxItems": 200 }
```

- The Actor reads up to `maxItems` rows (default 100) with your own run's Apify token and
  takes each row's URL from `webVideoUrl`, `url`, `postUrl`, `videoUrl`, `inputUrl` or `link`, or from the field you name in `datasetUrlField`.
- Dataset URLs run after any `urls` you list, in dataset order; a dataset URL that is
  already in the run is skipped. A run takes at most 1,000 URLs in total.
- A row without a usable URL becomes an error item with its `dataset_row` index, and it is
  not charged. Items from the dataset also carry `dataset_row`, so you can join the results
  back to the source rows.
- The example link in the URL list is processed and charged too: remove it when you want only the dataset's items.

### Output example

A real result from an Apify run on 2026-10-30; only the thumbnail link is shortened.

```json
{
  "url": "https://www.tiktok.com/@khaby.lame/video/7420109434153717024",
  "video_id": "7420109434153717024",
  "description": "I promise it works #learnfromkhaby #comedy",
  "hashtags": ["learnfromkhaby", "comedy"],
  "author_username": "khaby.lame",
  "author_name": "Khabane lame",
  "author_id": "127905465618821121",
  "author_url": "https://www.tiktok.com/@khaby.lame",
  "published_at": "2024-09-29T16:54:24Z",
  "upload_date": "2024-09-29",
  "duration_s": 50,
  "view_count": 62300000,
  "like_count": 3700000,
  "comment_count": 32400,
  "share_count": 32700,
  "save_count": 119910,
  "thumbnail_url": "https://p16-common-sign.tiktokcdn-us.com/…",
  "music_title": "original sound",
  "music_artist": "Khabane lame",
  "billing": {
    "status": "paid",
    "events": [{ "event": "video-scraped", "requested_count": 1, "charged_count": 1, "state": "confirmed" }]
  }
}
```

- A field is left out when TikTok does not show it for that video.
- Large counts can be rounded by TikTok, as `view_count` and `like_count` are here.
- `music_title` is a generic label such as "original sound" when the creator used their
  own audio.
- `thumbnail_url` is a signed link that expires, so download the image soon if you need
  it.
- A URL that fails returns a row with `url` and `error`.

### How much does it cost to scrape TikTok videos?

Pay per event, with no subscription. The
[Pricing tab](https://apify.com/andronixmd/tiktok-scraper/pricing) shows the current
prices.

| Event | Price | When |
|---|---:|---|
| Run setup | $0.002 | Once per run. |
| Video scraped | $0.002 | Per video whose data was returned. |

| Videos in one run | Cost |
|---|---:|
| 1 | $0.002 + $0.002 = **$0.004** |
| 100 | $0.002 + 100 × $0.002 = **$0.202** |
| 1,000 | $0.002 + 1,000 × $0.002 = **$2.002** |

Video length does not change the price. URLs that fail or are rejected are not charged
the per-video fee. Apify's own small run-start event is separate and is shown on the
Pricing tab.

### Use it from the API

Start a run with the [Apify API client for JavaScript](https://docs.apify.com/api/client/js/)
and read the results:

```js
import { ApifyClient } from 'apify-client'

const client = new ApifyClient({ token: process.env.APIFY_TOKEN })
const run = await client.actor('andronixmd/tiktok-scraper').call(
  { urls: ['https://www.tiktok.com/@khaby.lame/video/7420109434153717024'] },
  { maxTotalChargeUsd: 0.1 },
)
const { items } = await client.dataset(run.defaultDatasetId).listItems()
for (const item of items) console.log(item.url, item.error ?? item.view_count)
```

Or start one run over HTTP:

```sh
curl -X POST \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"urls":["https://www.tiktok.com/@khaby.lame/video/7420109434153717024"]}' \
  'https://api.apify.com/v2/actors/andronixmd~tiktok-scraper/runs?maxTotalChargeUsd=0.1'
```

The Actor also works from n8n and other automation tools through Apify's integrations. Schedule a run with the same URLs to track how the counts change over time.

#### Use it from an AI agent (MCP)

Connect your agent to `https://mcp.apify.com`. It can find this Actor with `search-actors`, or
call it by name, `andronixmd/tiktok-scraper`, with `call-actor`. Use `fetch-actor-details` first for the
input schema and pricing. Every call starts a new paid run, so always set
`callOptions.maxTotalChargeUsd` and keep the `urls` list short:

```json
{ "actor": "andronixmd/tiktok-scraper", "input": { "urls": ["https://www.tiktok.com/@khaby.lame/video/7420109434153717024"] }, "callOptions": { "maxTotalChargeUsd": 0.1 } }
```

`call-actor` returns the run status and storage IDs, not the results. Read the rows with
`get-dataset-items` and the run's dataset ID. The fields to use are `description`, `view_count`, `like_count` and `error`.

With an Apify account, use your API token or MCP OAuth. Without one, an agent can buy a prepaid token from [Apify AGI](https://agi.apify.com) over
x402 or MPP and use it as the Bearer token. It starts at $1, the balance is a hard spending cap,
and the token expires after 14 days. This is experimental; see
[agentic payments](https://docs.apify.com/agents/payments).

### Limitations

- **Public videos only.** Private, friends-only, age-restricted, region-blocked and
  removed videos, and anything that needs a login, are not supported. The Actor uses no
  accounts or cookies and does not try to get around TikTok's access checks.
- **Videos and profiles.** Hashtag, search, sound, photo-post and live URLs are not
  accepted; profiles go in `profileUrls`. Profile listing from Apify's servers has not
  been tested at scale. One URL in a wrong format
  rejects the whole input before the run starts. A short link that has expired or leads
  to something other than a video fails for that item only.
- **Not every public video returns data.** TikTok limits access from some networks and
  regions and changes its site often, so some URLs return an error row. Check each
  row's `error` field and retry failed URLs later.
- **Video data only.** No comment text, follower lists or profile statistics, and no
  video or audio files. No transcript or caption text either: for the spoken words, use
  [TikTok Video Transcript](https://apify.com/andronixmd/tiktok-video-transcript).

### FAQ

**Can I get the transcript of the video?**
Not here. This Actor returns data about the video, not what is said in it. Use
[TikTok Video Transcript](https://apify.com/andronixmd/tiktok-video-transcript), which
transcribes the audio with AI speech-to-text. Submit the same URLs to both and join the
results on `url`.

**Are the numbers live?**
They are what TikTok shows at the moment of the run. Run it again later for new
numbers.

**Does it scrape profiles, hashtags or search results?**
Profiles, yes: put them in `profileUrls`, and each listed video is scraped in full.
Hashtags and search results, no.

**Does it download the video?**
No. It reads the video's data only, which is why it is cheap and fast. It returns the
thumbnail link, not the video file.

**Where is my data kept?**
In your Apify run storage, under your account's access and retention settings. Use the
data in line with TikTok's terms and the law that applies to you.

### Related Actors

- [TikTok Video Transcript](https://apify.com/andronixmd/tiktok-video-transcript): the
  spoken words of the same videos as text, SRT and VTT.
- [Instagram Reel Scraper](https://apify.com/andronixmd/instagram-reel-scraper): the
  same kind of data for Instagram Reels.
- [YouTube Video Scraper](https://apify.com/andronixmd/youtube-scraper): YouTube video data
  and captions.
- [Audio & Video Transcriber](https://apify.com/andronixmd/speech-to-text-transcriber):
  transcripts of podcasts, audio and video files and other sites.

# Actor input Schema

## `urls` (type: `array`):

Public TikTok video links, one result per URL. Accepted forms: tiktok.com/@user/video/<id>, vm.tiktok.com/<code>, vt.tiktok.com/<code> and tiktok.com/t/<code>. Profile URLs go in TikTok profiles below; photo, tag, sound and live URLs are rejected. Submit up to 1,000 URLs, or leave this list empty and fill in TikTok profiles or Dataset ID below.

## `profileUrls` (type: `array`):

Public profile URLs such as https://www.tiktok.com/@bbcnews. The newest videos are listed (pinned videos are sorted by date), and each is processed exactly like a video URL above; each row names the profile it came from. TikTok profile listings fail intermittently, so each is tried up to 3 times; a profile that still fails becomes an error row that is not charged. Up to 100 profiles. The example URL above is processed and charged too: remove it if you want only listed videos.

## `maxItemsPerSource` (type: `integer`):

List at most this many of the newest videos from each source (up to 200). Each listed item is processed and billed like a URL you submit; listing itself is free. A run holds at most 1,000 items in total, counting URLs and dataset rows.

## `publishedAfter` (type: `string`):

Optional date as YYYY-MM-DD (UTC). Items published before this day are left out. TikTok listings give exact dates.

## `datasetId` (type: `string`):

The example URL above is processed and charged too: remove it if you want only the dataset's items. Pick the dataset, or paste its ID, from another run that lists TikTok videos (for example clockworks/tiktok-scraper) to get fresh data for every video in it. The Actor reads the rows with your run's own token and processes each row's URL like a URL above, after the listed URLs. Rows without a usable URL become uncharged error items. A named dataset works as username~dataset-name.

## `datasetUrlField` (type: `string`):

Optional. The row field that holds the URL, for example webVideoUrl, or a dotted path such as video.url. Leave it empty to use the first accepted URL from webVideoUrl, url, postUrl, videoUrl, inputUrl or link.

## `maxItems` (type: `integer`):

Read at most this many dataset rows, in dataset order. A run processes at most 1,000 URLs in total, counting the list above.

## `skipPreviouslyProcessed` (type: `boolean`):

Remember every item that was delivered and charged, in a key-value store in your Apify account, and skip those items in later runs. Use it with a schedule to get only new videos or episodes. Items that failed are tried again next time. When every item was already processed, the run ends without any charge. If the store cannot be read, the run stops before any charge and the next run tries again.

## `memoryKey` (type: `string`):

Optional. Keeps a separate memory for this schedule, for example my-weekly-podcasts. The store is named polyglot-processed-tiktok-scraper--<name>, and other Actors never share it; without a name every run of this Actor shares polyglot-processed-tiktok-scraper. Delete the store in Apify Console (Storage, Key-value stores) to start over. Used only with the option above.

## Actor input object example

```json
{
  "urls": [
    "https://www.tiktok.com/@khaby.lame/video/7420109434153717024"
  ],
  "maxItemsPerSource": 20,
  "maxItems": 100,
  "skipPreviouslyProcessed": false
}
```

# Actor output Schema

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

One Dataset item per submitted URL, with video metadata, confirmed billing and errors. Results are in the default dataset; item-NNN key-value records are internal resume state.

# 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 = {
    "urls": [
        "https://www.tiktok.com/@khaby.lame/video/7420109434153717024"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("andronixmd/tiktok-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 = { "urls": ["https://www.tiktok.com/@khaby.lame/video/7420109434153717024"] }

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,andronixmd/tiktok-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/Ad1np1RM3nUkpMb23/builds/Ga0YmdnoPm4B3MXwS/openapi.json
