# TikTok Scraper – Videos, Profiles, Hashtags, Comments & API (`poidata/tiktok-scraper`) Actor

🔥 $0.50/1K results🔥 Scrape TikTok videos, profiles, hashtags, search results, comments, subtitles, sounds, live streams, and media. Filter and sort results, download videos, export structured data, or use the TikTok Scraper API. No login, cookies, or proxy setup required.

- **URL**: https://apify.com/poidata/tiktok-scraper.md
- **Developed by:** [PoiData](https://apify.com/poidata) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Scraper – Videos, Profiles, Hashtags, Comments, Subtitles & API

Scrape TikTok videos, profiles, hashtags, keyword search results, sounds, live streams, comments, subtitles, and downloadable media from one Actor.

Use four input lists for hashtags, profiles, searches, and TikTok URLs. Mix them in the same run, apply filters or sorting when needed, and export structured results as JSON, CSV, or Excel. No TikTok login, cookies, browser, or proxy configuration is required.

### Key features

- 🔎 **Nine query types in one Actor:** keyword search, hashtag feeds, creator videos, profile details, single videos, sounds, creator search, live streams, and mixed searches.
- 💬 **Comments:** collect top-level comments with author data, likes, reply counts, timestamps, and permalinks.
- 📝 **TikTok subtitles:** fetch available caption tracks as raw WebVTT and plain text, with one entry per language.
- ⬇️ **Media downloads:** optionally store video files, photo-post slides, thumbnails, creator avatars, and sound covers.
- 📊 **Structured TikTok data:** numeric engagement counts, ISO-8601 timestamps, direct URLs, `authorMeta`, `musicMeta`, and `videoMeta`.
- 🎯 **Filtering and sorting:** filter by date, views, likes, or follower counts and sort by relevance, latest, or popular.
- 👤 **Creator discovery:** search for profiles or keep one video per author with `uniqueAuthors`.
- 🔗 **TikTok URL detection:** route video, photo, profile, hashtag, music, live, and supported short links automatically.
- ⚙️ **Automation-ready:** run manually, schedule it on Apify, or call the TikTok Scraper API from your own application.
- 🌐 **No session setup:** there is no `proxyConfiguration`, cookie, login, or browser input to maintain.

### What this TikTok Scraper does

The Actor accepts four main types of input and determines how to process each entry.

| Capability      | What it returns                                                                                             |
| --------------- | ----------------------------------------------------------------------------------------------------------- |
| Keyword search  | Videos matching a search term in captions and metadata                                                      |
| Hashtag feed    | Videos published under a specific TikTok hashtag                                                            |
| Creator videos  | Videos posted by a TikTok profile                                                                           |
| Profile details | Profile metadata such as followers, following, likes, bio, verification, and account status                 |
| Single video    | Metadata for an individual TikTok video or photo post                                                       |
| Sound/music     | Metadata for a TikTok sound, including title, artist, playable URL, artwork, and usage count when available |
| Creator search  | Profiles matching a keyword                                                                                 |
| Live streams    | Live-room metadata, viewer counts, likes, owner details, cover image, and status                            |
| Mixed search    | Multiple supported entry points processed in one run                                                        |

Video results can include views, likes, shares, comments, saves, caption text, hashtags, mentions, timestamps, ad status, dimensions, duration, definition, format, creator information, music information, cover URLs, and direct video URLs.

Profile results contain profile **counts and metadata**. They do not return lists of follower or following usernames.

### Supported use cases

Use the Actor for:

- TikTok trend and hashtag research.
- Creator and influencer discovery.
- Brand, product, and competitor monitoring.
- Social listening using TikTok comments.
- Engagement and content-performance analysis.
- Building TikTok datasets for dashboards or research.
- Collecting caption tracks for text analysis.
- Saving public TikTok videos, photo slides, covers, avatars, or sound artwork.
- Scheduled TikTok monitoring and data-export workflows.
- Feeding structured TikTok data into applications through the TikTok Scraper API.

### TikTok Scraper input parameters

At least one of `hashtags`, `profiles`, `searchQueries`, or `postURLs` must contain a value. If all four are empty, the run stops with `invalid_input`.

| Field                    |     Default | Description                                                                                                                                  |
| ------------------------ | ----------: | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `hashtags`               |           — | Hashtags to scrape. The leading `#` is optional.                                                                                             |
| `profiles`               |           — | TikTok usernames. The leading `@` is optional.                                                                                               |
| `searchQueries`          |           — | Keyword searches.                                                                                                                            |
| `postURLs`               |           — | Full TikTok URLs. Supported routing includes videos, photo posts, profiles, hashtags, sounds/music, live links, and supported short links.   |
| `maxItems`               |       `100` | Hard total result cap for the run.                                                                                                           |
| `maxItemsPerQuery`       |         `0` | `0` derives a per-query cap from `maxItems`, never below `12` and never above `200`. Set an explicit number to use an exact per-query limit. |
| `uniqueAuthors`          |     `false` | Keep at most one video per author.                                                                                                           |
| `sortBy`                 | `relevance` | `relevance`, `latest`, or `popular`.                                                                                                         |
| `dateFrom`               |           — | Keep posts on or after this date. Format: `YYYY-MM-DD`.                                                                                      |
| `dateTo`                 |           — | Keep posts on or before this date. Format: `YYYY-MM-DD`.                                                                                     |
| `minPlayCount`           |         `0` | Minimum video play count.                                                                                                                    |
| `minLikes`               |         `0` | Minimum video like count.                                                                                                                    |
| `maxLikes`               |         `0` | Maximum video like count.                                                                                                                    |
| `minFollowers`           |         `0` | Minimum creator follower count.                                                                                                              |
| `maxFollowers`           |         `0` | Maximum creator follower count.                                                                                                              |
| `excludePinnedPosts`     |     `false` | Remove pinned posts from `profiles` inputs. Search and hashtag rows use `isPinned: null`, so this option does not remove them.               |
| `enrichProfiles`         |     `false` | Fetch follower, following, and video counts for profiles. Billable when enrichment succeeds.                                                 |
| `includeComments`        |     `false` | Collect comments for videos. Comments do not count toward `maxItems`. Billable per returned comment.                                         |
| `commentsPerPost`        |        `50` | Maximum number of top-level comments requested per video.                                                                                    |
| `includeSubtitles`       |     `false` | Fetch TikTok caption tracks as WebVTT and plain text. Billable when a track is fetched for a video.                                          |
| `downloadVideos`         |     `false` | Store video media or photo-post slides in the run's storage and expose stored URLs.                                                          |
| `downloadThumbnails`     |      `true` | Store the video's cover when media downloads are enabled.                                                                                    |
| `downloadProfileAvatars` |      `true` | Store the creator's avatar when media downloads are enabled.                                                                                 |
| `downloadSoundCovers`    |      `true` | Store the sound artwork when media downloads are enabled.                                                                                    |

### Quick-start examples

#### Scrape a hashtag and keyword together

```json
{
  "hashtags": ["#fitness"],
  "searchQueries": ["home workout"],
  "maxItems": 50
}
```

#### Scrape videos from a profile

```json
{
  "profiles": ["catcafe"],
  "maxItems": 50
}
```

#### Scrape one TikTok video with comments and subtitles

```json
{
  "postURLs": [
    "https://www.tiktok.com/@user/video/1234567890123456789"
  ],
  "includeComments": true,
  "commentsPerPost": 50,
  "includeSubtitles": true
}
```

#### Download TikTok media and companion files

```json
{
  "hashtags": ["#fitness"],
  "maxItems": 20,
  "downloadVideos": true,
  "downloadThumbnails": true,
  "downloadProfileAvatars": true,
  "downloadSoundCovers": true
}
```

### Important workflows and operating modes

#### How the four input lists are interpreted

The input list determines the default meaning of an entry.

| Input           | `cat` means                                   |
| --------------- | --------------------------------------------- |
| `hashtags`      | Videos tagged with `#cat`                     |
| `searchQueries` | Videos matching the keyword `cat`             |
| `profiles`      | Videos posted by `@cat`                       |
| `postURLs`      | The TikTok object represented by the full URL |

Keyword search and hashtag search are different. `searchQueries` searches captions and metadata, while `hashtags` requests the tagged feed.

#### TikTok URL routing

A full TikTok URL in `postURLs` is routed according to what it represents. Supported forms include:

- `https://www.tiktok.com/@user/video/<id>`
- `https://www.tiktok.com/@user/photo/<id>`
- `https://www.tiktok.com/@user`
- TikTok `/tag/...` links
- TikTok `/music/...` links
- TikTok `/live/...` links
- `vm.tiktok.com` and `vt.tiktok.com` short links, resolved on a best-effort basis

A URL must begin with `http://` or `https://`. A value such as `/tag/cat` or a short link with its scheme removed is interpreted as a keyword rather than a URL.

#### Profile and creator-search prefixes

These special query forms are also supported:

- `user:cat` returns the profile's own details instead of the profile's videos.
- `users:cat` performs creator search and returns profiles matching the keyword.
- A `music:` entry returns the sound's metadata rather than a list of videos using the sound.

#### Filtering, sorting, and deduplication

You can filter video results with:

- `dateFrom` and `dateTo`
- `minPlayCount`
- `minLikes`
- `maxLikes`
- `minFollowers`
- `maxFollowers`

Use `sortBy: "latest"` for newest results or `sortBy: "popular"` for popularity-based ordering. `relevance` is the default.

`uniqueAuthors: true` keeps one video per creator.

Results are deduplicated across queries. Filtering and deduplication happen after fetching, so the final number of rows can be lower than `maxItems`.

`excludePinnedPosts` applies to profile video lists because TikTok supplies the pinned flag there. Search and hashtag results use `isPinned: null`.

#### Comment options

Set:

```json
{
  "includeComments": true,
  "commentsPerPost": 50
}
```

Comments are collected with the author's handle, name, and avatar, like counts, reply counts, timestamps, and permalinks. Comments do not consume the video's `maxItems` allowance.

Each comment reports how many replies it has through `replyCommentTotal`, and carries `isReply`. There is no separate reply-depth or reply-limit input.

#### Subtitles and captions

Set:

```json
{
  "includeSubtitles": true
}
```

For videos where TikTok provides captions, `subtitles` contains one entry per available language with:

- `lang`
- `source`
- `format`
- `text` — raw WebVTT
- `plain` — flattened plain text

`source: "ASR"` means TikTok generated the caption track. Other source values represent uploader-supplied tracks.

Videos without available caption tracks do not receive empty caption rows.

#### Media downloads

Set `downloadVideos: true` to store media in the run's key-value store.

For a normal video, the Actor can store:

- The video file.
- The thumbnail/cover.
- The creator avatar.
- The sound cover.

The stored URLs are exposed through `mediaUrls` and dedicated fields such as `downloadedVideo`.

Direct URLs such as `videoUrl` and `coverUrl` are available on video rows even when storage downloads are disabled.

##### Photo posts

TikTok photo posts do not contain a video file. For a photo post:

- `isPhotoPost` is `true`.
- Slides are stored in TikTok's original order.
- Each file uses `<video id>_slide_NN.jpg`.
- `downloadedSlides` contains the stored URLs.
- `mediaUrls` starts with the slide URLs.
- Successfully storing the photo post generates one `video-downloaded` event, the same billing unit used for a stored video.

A normal video uses `isPhotoPost: false`.

##### Download recovery

Each failed download is retried twice. If all attempts fail:

- The video metadata row is still returned.
- The failure is added to the `ERRORS` key-value-store record.
- The row can still contain `videoUrl` and `coverUrl` for your own retrieval attempt.

If an extra file — a cover, avatar, or sound cover — is no longer available on TikTok, the run notes it in the log and continues. The video itself is unaffected.

#### Run restarts

If Apify restarts a run, work already completed is not repeated: media that was stored is reused, rows that were delivered are not pushed twice, and comments that were already delivered are not billed twice.

### Output structure and examples

Results use typed values. Counts are numbers rather than numeric strings, timestamps use ISO-8601 where documented, and TikTok URLs are provided as ready-to-use strings.

Results live in one dataset, presented as separate tables. Failures are not rows — they go to the `ERRORS` key-value-store record.

Rows use `type` to distinguish `video`, `user`, `music`, and `live`.

Where applicable, rows also contain `searchQuery` and `searchQueryType` so results remain traceable to their input.

#### Default dataset views

| View            | Contents                                                                                                 |
| --------------- | -------------------------------------------------------------------------------------------------------- |
| **Overview 📊** | Avatar, author, text, five engagement counts, duration, sound, timestamp, video URL, and downloaded file |
| **Posts 📄**    | Cover, text, counts, duration, ad flag, hashtags, author, URL, and timestamp                             |
| **Comments 💬** | One row per comment: text, author handle/name/avatar, likes, replies, reply flag, posted time, permalink, and the parent video's URL |
| **Authors 👤**  | Avatar, name, nickname, verification, bio, followers, video count, and private status                    |
| **Music 🎵**    | Artwork, music name, author, original flag, ID, and playable URL                                         |
| **Video 🎬**    | Cover, duration, definition, format, dimensions, and downloaded link                                     |

These views are projections designed primarily around video rows. Because the shared dataset also contains `user`, `music`, and `live` rows, a non-video row can appear with empty cells in a video-oriented view. Filter API results by `type` when you need one object type only.

#### Video result

```json
{
  "type": "video",
  "id": "7670954639625538823",
  "webVideoUrl": "https://www.tiktok.com/@thecatsdrops/video/7670954639625538823",
  "text": "The cat #viral #catsvideo",
  "textLanguage": "en",
  "createTime": 1786033312,
  "createTimeISO": "2026-08-06T16:21:52Z",
  "diggCount": 4660,
  "shareCount": 215,
  "playCount": 312200,
  "commentCount": 252,
  "collectCount": 1029,
  "isAd": false,
  "isPinned": null,
  "hashtags": [
    {
      "name": "viral"
    },
    {
      "name": "catsvideo"
    }
  ],
  "mentions": [],
  "videoMeta": {
    "coverUrl": "https://p16-common-sign.tiktokcdn.com/…",
    "duration": 70,
    "width": 576,
    "height": 1024,
    "definition": "540p",
    "format": "mp4"
  },
  "authorMeta": {
    "id": "7628616679741178896",
    "name": "thecatsdrops",
    "nickName": "The CatDrop",
    "profileUrl": "https://www.tiktok.com/@thecatsdrops",
    "fans": 13400,
    "following": 185,
    "video": 281,
    "heart": 62900,
    "verified": false,
    "privateAccount": false,
    "avatar": "https://p16-common-sign.tiktokcdn.com/…",
    "signature": "…"
  },
  "musicMeta": {
    "musicName": "som original",
    "musicAuthor": "The CatDrop",
    "musicOriginal": true,
    "musicId": "7670954682017139476",
    "playUrl": "https://sf16-ies-music.tiktokcdn.com/obj/…",
    "coverMediumUrl": "https://p16-va.tiktokcdn.com/…"
  },
  "videoUrl": "https://v16-webapp-prime.tiktok.com/video/tos/…",
  "searchQuery": "#cats",
  "searchQueryType": "hashtag",
  "scrapedAt": "2026-09-20T19:18:53Z"
}
```

When optional features are enabled, video rows can additionally contain:

| Field                  | Condition                              | Meaning                                                                                   |
| ---------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------- |
| `comments`             | `includeComments`                      | The video's comments as an array (empty when it has none). Each entry carries the prefixed `comment…` fields shown in the Comments table. |
| `subtitles`            | `includeSubtitles`                     | Caption tracks containing `lang`, `source`, `format`, raw WebVTT `text`, and `plain` text |
| `downloadedVideo`      | `downloadVideos`                       | Stored video URL                                                                          |
| `downloadedCover`      | `downloadVideos` + thumbnail enabled   | Stored cover URL                                                                          |
| `downloadedAvatar`     | `downloadVideos` + avatar enabled      | Stored creator-avatar URL                                                                 |
| `downloadedSoundCover` | `downloadVideos` + sound-cover enabled | Stored sound-artwork URL                                                                  |
| `isPhotoPost`          | Media workflow                         | `true` for photo posts and `false` for normal videos                                      |
| `downloadedSlides`     | Downloaded photo post                  | Ordered stored slide URLs                                                                 |
| `mediaUrls`            | `downloadVideos`                       | All stored media URLs; video first for normal videos and slides first for photo posts     |

#### Profile result

```json
{
  "type": "user",
  "id": "6714483547013583877",
  "uniqueId": "meow__cash",
  "nickName": "Cash",
  "profileUrl": "https://www.tiktok.com/@meow__cash",
  "avatarLarger": "https://p16-common-sign.tiktokcdn.com/…",
  "signature": "…",
  "verified": false,
  "privateAccount": false,
  "fans": 4600000,
  "following": 631,
  "videoCount": 435,
  "heart": 118100000,
  "searchQuery": "@meow__cash",
  "searchQueryType": "profile"
}
```

#### Comments

Comments are nested on their video row and expanded into the **Comments 💬** table. Each entry uses prefixed field names so it cannot collide with the video's own columns:

```json
{
  "comments": [
    {
      "commentId": "7395xxxxxxxxxxxxx",
      "commentText": "so cute 😭",
      "commentLanguage": "en",
      "commentAuthor": "fan_of_cats",
      "commentAuthorName": "Cat Fan",
      "commentAuthorAvatar": "https://p16-common-sign.tiktokcdn.com/…",
      "commentLikes": 1204,
      "commentReplies": 3,
      "commentIsReply": false,
      "commentCreatedAt": "2026-08-06T16:35:02Z",
      "commentUrl": "https://www.tiktok.com/@thecatsdrops/video/7670954639625538823?comment_id=7395…"
    }
  ]
}
```

Expanded by the Comments table, each row also shows the parent video's `webVideoUrl`, `text`, and `searchQuery`.

#### Sound result

A sound lookup returns the sound's metadata rather than automatically returning every video using that sound.

```json
{
  "type": "music",
  "id": "7670954682017139476",
  "title": "som original",
  "artist": "The CatDrop",
  "album": null,
  "durationSec": 30,
  "videoCount": 41200,
  "isOriginal": true,
  "playUrl": "https://sf16-ies-music.tiktokcdn.com/…",
  "coverUrl": "https://p16-va.tiktokcdn.com/…",
  "createTime": "2026-08-06T16:20:00Z",
  "searchQuery": "#cats",
  "searchQueryType": "hashtag"
}
```

#### Live-stream result

```json
{
  "type": "live",
  "id": "7395xxxxxxxxxxxxx",
  "title": "cat cafe live",
  "viewers": 1520,
  "likes": 8400,
  "status": 2,
  "startTime": "2026-09-20T18:00:00Z",
  "coverUrl": "https://p19-webcast.tiktokcdn.com/…",
  "owner": {
    "uniqueId": "catcafe",
    "nickname": "Cat Café",
    "id": "6791xxxxxxxxxxxxx",
    "profileUrl": "https://www.tiktok.com/@catcafe"
  },
  "searchQuery": "cats",
  "searchQueryType": "live"
}
```

#### Error result

Failures are not mixed into the normal dataset. They are written to an `ERRORS` record in the run's key-value store.

```json
{
  "type": "error",
  "errorCode": "upstream_blocked",
  "errorMessage": "blocked by TikTok",
  "inputSource": "#blocked",
  "inputType": "hashtag"
}
```

The `ERRORS` record exists only when something fails.

Open it under **Storage → Key-value store**, or access the record through the run's key-value-store API:

```text
…/key-value-stores/default/records/ERRORS
```

### TikTok Scraper API usage

Every Actor run can be started programmatically through the Apify API.

Use the Actor ID shown in the Actor's **API** tab and your Apify API token.

#### Run the Actor and return dataset items

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/<ACTOR_ID>/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "hashtags": ["#fitness"],
    "searchQueries": ["home workout"],
    "maxItems": 50
  }'
```

The run's Output tab exposes its dataset endpoint in this form:

```text
…/datasets/<id>/items
```

`GET Run` also exposes the output location under `output`, allowing API clients and agents to discover where the result dataset is stored.

The output can be exported as JSON, CSV, or Excel.

The Actor can also be scheduled on Apify or used in workflows that call Actor runs through REST endpoints, webhooks, Python, JavaScript, Zapier, Make, or n8n. Datasets can also be forwarded to services such as Google Sheets, S3, or your own endpoint.

### Performance and processing limits

| Behavior                           | Limit or behavior                                                                        |
| ---------------------------------- | ---------------------------------------------------------------------------------------- |
| Whole-run result cap               | `maxItems`, default `100`                                                                |
| Automatic per-query cap            | Derived from `maxItems`; minimum `12`, maximum `200`                                     |
| Explicit per-query cap             | Set with `maxItemsPerQuery`                                                              |
| Hashtag/search/creator-video depth | Typically `100+` videos per input, subject to TikTok availability                        |
| Creator search                     | About `10` creators per page                                                             |
| Live results                       | Typically tens of rooms; availability changes continuously                               |
| Comment count                      | Controlled by `commentsPerPost`                                                          |
| Maximum individual media file      | `100 MB`                                                                                 |
| Download retries                   | Two retries after a failed download attempt                                              |
| Pipeline behavior                  | Downloads begin as soon as results for an input are available; inputs do not need to finish in order |

#### Storage lifetime

Downloaded media is stored in the run's key-value store, not as a permanent archive.

The free-tier retention period is **7 days after the last access**. Move files you need to keep into your own long-term storage before the run storage expires.

### Pricing and billing

The Actor uses pay-per-event billing for six optional or conditional operations.

Ordinary video, profile, hashtag, sound, live-stream, and search results carry no per-result event charge. Apify platform usage still applies.

Current dollar rates should always be taken from the Actor's **Pricing** tab.

| Event              | Billing unit            | When it is charged                                                      |
| ------------------ | ----------------------- | ----------------------------------------------------------------------- |
| `comment-scraped`  | Per comment returned    | A comment was successfully returned                                     |
| `subtitle-scraped` | Per video               | A caption track was successfully fetched for the video                  |
| `video-downloaded` | Per video or photo post | Media was successfully stored                                           |
| `follower-fetched` | Per distinct profile    | The profile's follower data was successfully returned                   |
| `filtered-search`  | Per query               | A query ran with a date window or likes/views/follower floor or ceiling |
| `sorted-search`    | Per query               | A query was reordered using `latest` or `popular`                       |

`excludePinnedPosts` and `uniqueAuthors` do not create a `filtered-search` charge.

#### How to calculate event cost

If the Pricing tab shows an event price of **P dollars per 1,000 events**:

| Successful events | Event cost |
| ----------------: | ---------: |
|                 1 | `P / 1000` |
|               100 |   `P / 10` |
|             1,000 |        `P` |

Your effective run cost is:

```text
Apify platform usage
+ comment-scraped events
+ subtitle-scraped events
+ video-downloaded events
+ follower-fetched events
+ filtered-search events
+ sorted-search events
```

#### Billing behavior

Rejected work is not billed. Examples include a comment that fails to load, media that cannot be stored, or profile numbers that are not returned.

Duplicate creators are billed once for `follower-fetched` when the same handle appears on several videos and enrichment is actually needed.

Filters and sorting are billed **per qualifying query, not per returned row**.

A query does not create its filter/sort event when it:

- Is stopped by `maxItems` before that work occurs.
- Fails and produces only an error record.
- Produces rows that all fail to parse.

For media downloads, thumbnails, avatars, and sound covers do not create separate `video-downloaded` events. A successfully stored post is one event. A downloaded photo slideshow is also one `video-downloaded` event.

Billing occurs as successful extras arrive rather than as one final lump at the end.

The final run log reports the charged units. For example:

```text
charges: 6 units charged in total (comment-scraped 4, video-downloaded 2)
```

### Limitations and failure handling

#### TikTok availability controls result depth

`maxItems` is a ceiling, not a guaranteed result count. TikTok can return fewer available items.

Filtering and deduplication can reduce the result count further.

#### Short links are best-effort

`vm.tiktok.com` and `vt.tiktok.com` links are resolved on a best-effort basis. Full URLs require an `http://` or `https://` scheme.

#### Profile results contain counts, not follower lists

Profile data can include follower, following, video, and total-like counts. The Actor does not return follower/following username lists.

#### Comments are top-level comments

`commentsPerPost` limits top-level comments per video. Rows contain `replyCommentTotal` and `isReply`. There is no separate reply-depth setting.

#### Subtitles depend on TikTok

Only caption tracks available from TikTok can be returned. A video without captions simply has no subtitle tracks.

#### Some fields may be absent

Fields TikTok does not supply are omitted rather than filled with empty placeholders. Examples include:

- `isMuted`
- `musicMeta.musicAlbum`
- `authorMeta.bioLink`

#### Media files have a size limit

A single file cannot exceed **100 MB**.

#### Most failures do not stop other inputs

Per-input failures are isolated. Successful inputs continue, while errors are collected in the `ERRORS` key-value-store record.

Error codes you may see in the `ERRORS` record include:

- `upstream_blocked`
- `upstream_error`
- `no_results`
- `timeout`
- `rate_limited`
- `all_proxies_failed`
- `no_proxies`
- `invalid_input`
- `service_error`
- `service_unreachable`
- `malformed_response`
- `auth_failed`
- `download_failed`
- `downloads_unavailable`
- `internal_error`
- `normalize_error`

Some failures carry a more specific code instead, such as:

- `not_found`
- `blocked`
- `queue_full`
- `cancelled`
- `worker_crashed`
- `invalid_query`

Two conditions can stop the entire run because no scraping can proceed:

- `missing_config` — the Actor is not configured for the account.
- `invalid_input` — no query was supplied.

The reason is recorded in `ERRORS` and in the run status message.

### Frequently asked questions

#### What is this TikTok Scraper?

TikTok Scraper is an Apify Actor that extracts structured data from TikTok videos, profiles, hashtags, keyword searches, sounds, live streams, comments, and caption tracks. It can also save supported media files and expose the results through datasets or the TikTok Scraper API.

#### Do I need a TikTok account, cookies, or proxy configuration?

No. The Actor does not expose login, cookie, browser, or `proxyConfiguration` inputs.

#### Can I use it as a TikTok Scraper API?

Yes. Start the Actor through the Apify REST API, pass the same JSON input used in the Console, and retrieve the resulting datasets programmatically.

#### Can I scrape several TikTok data types in one run?

Yes. `hashtags`, `profiles`, `searchQueries`, and `postURLs` can be combined in one run. Output rows carry type and query-origin information so mixed results remain identifiable.

#### Can I scrape TikTok comments and replies?

Yes. The Actor collects top-level comments and returns reply counts through `replyCommentTotal`. Each comment also carries `isReply`. There is no separate reply-depth or reply-limit parameter.

#### Can I extract TikTok subtitles?

Yes. Set `includeSubtitles: true`. Available TikTok caption tracks are returned as raw WebVTT and plain text, one entry per language.

#### Can I download TikTok videos?

Yes. Set `downloadVideos: true`. Successfully stored media is exposed through `mediaUrls` and dedicated fields such as `downloadedVideo`.

Photo posts are stored as ordered slide images instead of a video file.

#### How many TikTok results can I scrape?

`maxItems` controls the whole run and defaults to `100`. `maxItemsPerQuery` controls individual inputs.

Typical depth by source:

| Source           | Depth                                  |
| ---------------- | -------------------------------------- |
| Hashtags         | 100+ videos per input                  |
| Keyword searches | 100+ videos per input                  |
| Creator videos   | 100+ videos per input                  |
| Creator search   | About 10 creators per page             |
| Comments         | Up to `commentsPerPost` for each video |
| Live streams     | Tens of currently available rooms      |

Actual availability is controlled by TikTok and can be lower.

#### Why did I receive fewer results than `maxItems`?

Filters, deduplication, `uniqueAuthors`, date restrictions, engagement limits, follower limits, and duplicate videos across multiple inputs can all reduce the final result count.

#### How are comments stored?

Comments are nested on their video's row in the main dataset and expanded into the **Comments 💬** table in the Output tab. Comments do not consume the `maxItems` allowance.

#### Where are failures stored?

Failures are written to the `ERRORS` record in the run's key-value store. They are kept out of the dataset.

#### How much does the Actor cost?

Base TikTok result types carry no per-result event charge. Apify platform usage still applies.

Six operations can create pay-per-event charges: comments, subtitles, media downloads, profile enrichment, filtered queries, and sorted queries. Check the Actor's live Pricing tab for the current dollar rate of each event.

#### Are failed extras billed?

No. An extra is charged when the corresponding result succeeds: for example, a returned comment, stored media item, or successfully enriched profile.

#### Can I schedule TikTok scraping?

Yes. The Actor can be scheduled through Apify and its API can be used in recurring automation workflows.

#### Is scraping TikTok legal?

Publicly visible information can be subject to TikTok's terms and to privacy, copyright, and data-protection laws that vary by jurisdiction and use case. Make sure your collection and use of the data are lawful for your specific situation.

#### How do I report a problem?

Use **Report an issue** on the Actor page and include the run ID and the input that produced the problem. Check the run's `ERRORS` record first because it often contains the error code.

### Start scraping TikTok

Add a hashtag, profile, keyword, or TikTok URL, choose the optional comments, subtitles, filters, or media features you need, and start the run.

Use **TikTok Scraper** from the Apify Console for one-off jobs, schedule recurring runs, or connect the **TikTok Scraper API** to your own application and data pipeline.

# Actor input Schema

## `hashtags` (type: `array`):

One hashtag per line. The leading `#` is optional — `fyp` and `#fyp` behave the same.

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

The total number of videos for the whole run, across every input list. Error rows are not counted. The run stops as soon as this number is reached.

## `maxItemsPerQuery` (type: `integer`):

Caps each query separately instead of the run as a whole. Leave 0 to derive each query's cap from **🎬 Videos to collect** — at least 12 and at most 200 per query, so one large query cannot crowd out the rest.

## `uniqueAuthors` (type: `boolean`):

Keeps only the first video seen from each creator and discards the rest. Useful for discovering new accounts inside a hashtag.

## `profiles` (type: `array`):

One username per line, with or without the `@`. By default this returns that creator's videos. Put `user:` in front — `user:mrbeast` — to collect the profile's own details instead.

## `enrichProfiles` (type: `boolean`):

Spends one extra request per profile to fill in follower, following and video counts, charged per profile filled. Slower and billable, but without it those numbers come back empty on search results.

## `excludePinnedPosts` (type: `boolean`):

Leave out a profile's pinned videos. TikTok only reports the pinned flag on profile feeds, so this affects profile video results only.

## `maxFollowers` (type: `integer`):

Drops accounts above this size, which surfaces smaller creators. 0 turns it off.

## `minFollowers` (type: `integer`):

Drops accounts below this size. Profile results only. 0 turns it off.

## `searchQueries` (type: `array`):

One keyword or phrase per line. Each one is searched on TikTok and returns matching videos.

## `sortBy` (type: `string`):

**Relevance** leaves TikTok's own ranking untouched. Ordering happens after fetching, so it applies to hashtag and profile results too. Newest-first and Most-played-first re-order each query's results and are billed as `sorted-search` — one per query.

## `maxLikes` (type: `integer`):

Drops videos with more likes than this, which is useful for finding niche or low-competition content. 0 turns it off.

## `minLikes` (type: `integer`):

Drops videos with fewer likes than this. 0 turns it off. Using any filter in this section widens the fetch to find enough survivors and is billed as `filtered-search` — one per query.

## `minPlayCount` (type: `integer`):

Drops videos with fewer plays than this. 0 turns it off.

## `dateFrom` (type: `string`):

Keeps videos published from this date onwards. Leave empty to turn it off. Date filters bill `filtered-search` — one per query.

## `dateTo` (type: `string`):

Keeps videos published up to this date. Leave empty to turn it off.

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

One URL per line. Video, profile, hashtag, sound and short (`vm.` / `vt.`) links all work — short links are followed to their full URL for you.

## `includeSubtitles` (type: `boolean`):

For each video, pulls the caption tracks TikTok holds and adds them as `subtitles` — the raw WebVTT plus a `plain` text version. A video often has captions in more than one language; each track arrives separately with its own `lang`, so you can pick the one you want. `source` says whether TikTok generated the track (`ASR`) or the creator uploaded it. Videos with no captions simply return none.

## `includeComments` (type: `boolean`):

For each video, pulls its comments and writes them out as separate `type: "comment"` rows in the dataset, tagged `type: comment`. Comments do **not** count towards **🎬 Videos to collect**. Costs about one extra API call per video.

## `commentsPerPost` (type: `integer`):

How many top-level comments to pull per video when **Fetch comments** is on. Replies TikTok returns inline are added on top. 0 or unset means 50.

## `downloadVideos` (type: `boolean`):

Downloads each video and links the stored file on every video row as `mediaUrls` (plus `downloadedCover`, `downloadedAvatar` and `downloadedSoundCover` for the extras below). Stored files stay available for 48 hours by default — long enough to collect them during or shortly after the run, but not a permanent archive. TikTok's own CDN links expire within hours. Photo posts (slideshows) are stored as their rendered video plus cover — TikTok's web interface exposes no per-slide images.

## `downloadThumbnails` (type: `boolean`):

Stores each video's cover frame alongside the video and links it on the row as `mediaUrls` and `coverUrl`.

## `downloadProfileAvatars` (type: `boolean`):

Stores the creator's profile picture alongside their video and links it on the row.

## `downloadSoundCovers` (type: `boolean`):

Stores the square artwork of the sound used in each video and links it on the row.

## Actor input object example

```json
{
  "hashtags": [
    "fyp",
    "cat"
  ],
  "maxItems": 100,
  "maxItemsPerQuery": 0,
  "uniqueAuthors": false,
  "profiles": [],
  "enrichProfiles": false,
  "excludePinnedPosts": false,
  "maxFollowers": 0,
  "minFollowers": 0,
  "searchQueries": [],
  "sortBy": "relevance",
  "maxLikes": 0,
  "minLikes": 0,
  "minPlayCount": 0,
  "dateFrom": "",
  "dateTo": "",
  "postURLs": [],
  "includeSubtitles": false,
  "includeComments": false,
  "commentsPerPost": 50,
  "downloadVideos": false,
  "downloadThumbnails": true,
  "downloadProfileAvatars": true,
  "downloadSoundCovers": true
}
```

# Actor output Schema

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

One row per result: video rows (counts, caption, hashtags, duration and format, the sound used, the creator and the direct CDN links), plus creator rows, sound rows and live-room rows when those queries are used. With comments switched on, each video row also carries its comments, which the Comments table expands. Failures are not here — they go to the ERRORS record.

# 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 = {
    "hashtags": [
        "fyp",
        "cat"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("poidata/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 = { "hashtags": [
        "fyp",
        "cat",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("poidata/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 '{
  "hashtags": [
    "fyp",
    "cat"
  ]
}' |
apify call poidata/tiktok-scraper --silent --output-dataset

```

## MCP server setup

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