# TikTok Comments Scraper (`apihq/tiktok-comments-scraper`) Actor

Export TikTok comments and replies to JSON by video URL. Deleted or unavailable videos return success:false, cost $0, and never break the batch. One record per comment with author, likes, replies, and timestamp. $0.25 per 1,000 comments.

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

## Pricing

$0.25 / 1,000 comments

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 Comments Scraper

Export the public comments and replies of TikTok videos as clean JSON from their video URLs. No TikTok account, no API key, no browser.

The contract is simple: **a deleted or unavailable video never fails your run, and you only pay for comments delivered.** Paste a removed video or a bad URL, and it comes back as a `success: false` row while the rest of the batch keeps going. Pay-per-result at $0.25 per 1,000 comments. Failed videos cost $0.

### See one run

Input:

```json
{
  "postURLs": [
    "https://www.tiktok.com/@scout2015/video/6718335390845095173",
    "https://www.tiktok.com/@scout2015/video/7000000000000000001"
  ],
  "commentsPerPost": 30,
  "maxRepliesPerComment": 2
}
```

What came back:

- 30 top-level comments from the good video, plus up to 2 replies under each (49 replies), all `success: true` and billed. Reply rows carry `repliesToId`, the `cid` of the comment they answer
- 1 `success: false` row, `code: TIKTOK_VIDEO_UNAVAILABLE`, for the missing video, not billed
- Run status: **SUCCEEDED**
- Charged: 79 comments = $0.01975

`postURLs` alone works too. `commentsPerPost` defaults to 100 and replies are off by default. Good input returns billed rows, bad input returns free error rows in the same dataset, and the run still succeeds, so one bad video never costs you a batch.

### Why this Actor

- **A bad video never breaks the batch.** A deleted, private, or unreachable video returns a `success: false` record with a machine-readable `code` (`TIKTOK_VIDEO_UNAVAILABLE`, `TIKTOK_NO_COMMENTS`, `VALIDATION_FAILED`, `TIKTOK_TIMEOUT`, ...). Branch on `code` without parsing English.
- **You pay only for comments.** Billing fires on an explicit per-comment event, not on dataset writes, so failure rows sit in your dataset for free. There is no per-run start fee, so a run that returns zero comments costs zero.
- **Drop-in field names.** Rows use the field names most TikTok scrapers already use (`cid`, `text`, `diggCount`, `replyCommentTotal`, `createTimeISO`, `uniqueId`, `uid`, `avatarThumbnail`, `videoWebUrl`), so an existing pipeline can switch without a remap.

### Best for

Data and AI workflows that turn TikTok discussion into text: sentiment and brand monitoring, influencer and campaign research, audience and product-feedback analysis, moderation review, and building comment datasets for LLM pipelines. The failure-free billing and machine-readable error rows matter most when the extraction runs unattended inside a larger pipeline, where one deleted video should never take down the batch. It works fine for one-off manual pulls too.

### Input example

A single video, top-level comments only:

```json
{
  "postURLs": ["https://www.tiktok.com/@scout2015/video/6718335390845095173"],
  "commentsPerPost": 200
}
```

A batch of videos, with replies:

```json
{
  "postURLs": [
    "https://www.tiktok.com/@scout2015/video/6718335390845095173",
    "https://www.tiktok.com/@khaby.lame/video/7675410938970901791",
    "https://www.tiktok.com/@looooooooch/video/7332342275151760642"
  ],
  "commentsPerPost": 500,
  "maxRepliesPerComment": 10
}
```

A quick sample across many videos (the first 20 comments of each, cheap to run):

```json
{
  "postURLs": [
    "https://www.tiktok.com/@scout2015/video/6718335390845095173",
    "https://www.tiktok.com/@khaby.lame/video/7675410938970901791"
  ],
  "commentsPerPost": 20
}
```

| Field | Type | Default | What it does |
|---|---|---|---|
| `postURLs` | array of strings | | TikTok video URLs, up to 50 unique videos per run. Copies of the same video (with `www.`, `m.`, a query string, or a trailing slash) are run once. |
| `commentsPerPost` | integer, 1-10,000 | 100 | Maximum top-level comments per video. Replies do not count toward it. |
| `maxRepliesPerComment` | integer, 0-1,000 | 0 | Maximum replies fetched under each top-level comment. `0` skips replies. |

Out-of-range numbers are clamped to the nearest allowed value, with a note in the run log.

### Output example

One record per comment or reply. The video's canonical URL is copied onto every row so each row stands alone:

```json
{
  "success": true,
  "cid": "7642984713451356950",
  "text": "this never gets old",
  "diggCount": 3,
  "replyCommentTotal": 2,
  "createTimeISO": "2026-05-20T10:11:12.000Z",
  "uniqueId": "somehandle",
  "uid": "7608207343391097878",
  "avatarThumbnail": "https://p16-common-sign.tiktokcdn.com/...",
  "videoWebUrl": "https://www.tiktok.com/@scout2015/video/6718335390845095173",
  "repliesToId": null
}
```

A video that cannot be read lands in the same dataset and is not charged:

```json
{
  "success": false,
  "postURL": "https://www.tiktok.com/@scout2015/video/7000000000000000001",
  "code": "TIKTOK_VIDEO_UNAVAILABLE",
  "error": "TikTok reports this video does not exist or is not available",
  "requestId": "req_c5d69ba0387c4b2ab600df56",
  "statusCode": 422,
  "commentsDelivered": 0
}
```

Split hits from misses on the `success` field. Quote `requestId` in any support issue and we can trace the exact request.

### What you get

Every comment or reply is one record with `success: true`:

| Field | Type | What it is |
|---|---|---|
| `success` | boolean | `true` for a comment (billed), `false` for a video that could not be read (not billed). |
| `cid` | string | The comment's unique ID. |
| `text` | string | The comment body. |
| `diggCount` | integer | Likes on the comment. |
| `replyCommentTotal` | integer | Number of replies TikTok reports for the comment. |
| `createTimeISO` | string | When the comment was posted, ISO 8601 UTC. |
| `uniqueId` | string | The author's TikTok handle. |
| `uid` | string | The author's numeric user ID. |
| `avatarThumbnail` | string or null | The author's avatar URL, `null` when TikTok does not return one. |
| `videoWebUrl` | string | The canonical URL of the video the comment belongs to. |
| `repliesToId` | string or null | On a reply: the `cid` of the comment it answers. `null` on a top-level comment. |

IDs are strings because TikTok's 19-digit IDs do not fit in a JavaScript number.

Videos that could not be read carry `success: false`, `postURL`, a machine-readable `code`, a human-readable `error`, the service `requestId`, the HTTP `statusCode`, and `commentsDelivered` (rows already delivered for that video before it stopped). They are not charged:

| Code | Meaning |
|---|---|
| `TIKTOK_VIDEO_UNAVAILABLE` | The video is deleted, private, or otherwise unavailable. |
| `TIKTOK_NO_COMMENTS` | TikTok reports the video has no comments. |
| `TIKTOK_UNAVAILABLE` | TikTok did not serve the comments for this video. |
| `TIKTOK_BLOCKED` | TikTok refused the request after an automatic retry. |
| `TIKTOK_RATE_LIMITED` | TikTok rate-limited the request after an automatic retry. |
| `TIKTOK_TIMEOUT` | TikTok did not answer in time after an automatic retry. |
| `DEADLINE_EXCEEDED` | The page request hit its 30-second deadline. |
| `VALIDATION_FAILED` | The URL is not a supported TikTok video URL (including short links). |
| `NO_INPUT` | No usable `postURLs` were given. The run ends successfully with this single row. |

### Pricing

Pay-per-result. One charge per comment delivered: `$0.00025` each, which is `$0.25 per 1,000 comments`. Replies bill at the same per-comment rate. The charge fires only after a comment row lands in your dataset.

- 1,000 delivered comments cost $0.25.
- 10,000 delivered comments cost $2.50.
- A run that hits only deleted or unavailable videos costs $0.
- There is no per-run start fee, so an empty run is genuinely free.

Platform compute is included in the per-comment price. Cap the maximum spend of a single run from Apify's Run Limits panel; the Actor honors the cap and stops cleanly when the budget runs out.

### What this Actor does not do

Honest scope, so you know before you run it:

- **Video URLs only.** It reads the comments of the videos you give it. It does not find videos from a profile, hashtag, sound, or search query.
- **Full video URLs only.** Short share links (`vm.tiktok.com/...`, `vt.tiktok.com/...`) come back as a free `success: false` row with `code: VALIDATION_FAILED`. Open the short link in a browser and paste the full `https://www.tiktok.com/@user/video/<id>` URL instead.
- **Replies are opt-in.** By default it returns top-level comments only. Set `maxRepliesPerComment` above `0` to also fetch replies; they add extra requests.
- **Public comments as TikTok serves them.** TikTok may hide, filter, or reorder comments, so the number of rows can be lower than the comment or reply counts shown in the app. `replyCommentTotal` is TikTok's number, not a promise of how many reply rows you get.
- **No login-only content.** Private videos and anything that needs a TikTok account return `success: false`. It does not download videos, images, or audio.

### How to use this Actor

1. Open the Actor in the Apify Console.
2. Paste one or more TikTok video URLs into `postURLs`. Up to 50 unique videos run per job.
3. Set `commentsPerPost` to cap top-level comments per video, and `maxRepliesPerComment` if you want replies.
4. Click Start. Each comment is one `success: true` record. Videos that cannot be read produce one `success: false` record and do not stop the run. Only comments are charged.

The Actor is also callable from the Apify API and every official integration (Make, Zapier, n8n, Slack, webhooks). The API tab in the Console has ready-to-paste JavaScript, Python, and curl snippets.

### Reliability

**A bad video never fails the batch.** A deleted video, a bad URL, or a video TikTok will not serve becomes a `success: false` record with a specific `code`. The run keeps going and finishes successfully.

**You never pay for a failure.** Billing fires on an explicit per-comment charge event, not on dataset writes, so `success: false` records are free. There is no per-run start fee either.

**Automatic retry, then a clear answer.** A blocked, rate-limited, or slow request is retried automatically once before the video is reported as failed. Each page request runs under a 30-second deadline, so a stalled page returns a `success: false` record with `code: DEADLINE_EXCEEDED` instead of hanging your run.

**No duplicate charges.** A comment TikTok repeats across pages is delivered and charged once per run, even when the same video appears several times in your input.

**Genuine faults are not hidden.** An outage of our backend, an unexpected response, or a failure to write or bill results fails the run, so a broken run never looks like a clean one with missing data.

### FAQ

**Do I need a TikTok account or API key?**

No. The Actor reads public comments directly. There is nothing to log into and no TikTok developer key to provision. (You run it with your normal Apify account, like any Actor.)

**What URL formats are accepted?**

Full TikTok video URLs: `https://www.tiktok.com/@user/video/<id>`, also with `m.tiktok.com`, without `www.`, with a query string, or with a trailing slash. Short links (`vm.tiktok.com`, `vt.tiktok.com`) are not supported yet and return a free `success: false` row with `code: VALIDATION_FAILED`.

**How do I get replies?**

Set `maxRepliesPerComment` to the number of replies you want under each top-level comment. Reply rows carry `repliesToId`, the `cid` of the parent comment, so you can rebuild threads.

**Why do I get fewer comments than the app shows?**

The count in the app includes comments TikTok later hides, filters, or only shows to some viewers. The Actor returns the public comments TikTok serves at run time, and the comment count is TikTok's own number.

**Is the output compatible with other TikTok comment scrapers?**

Yes for the common fields: `cid`, `text`, `diggCount`, `replyCommentTotal`, `createTimeISO`, `uniqueId`, `uid`, `avatarThumbnail`, `videoWebUrl`, and `repliesToId` use the field names most TikTok scrapers already use.

**Can I get all comments from a profile or a hashtag?**

Not with this Actor. It reads the comments of the video URLs you give it. Collect the video URLs first, then pass them in `postURLs`.

**How much does it cost?**

$0.25 per 1,000 comments, replies included. 10,000 comments cost $2.50. Failed videos and empty runs cost $0, and there is no start fee.

**What if a video was deleted or is private?**

It comes back as a `success: false` record with `code: TIKTOK_VIDEO_UNAVAILABLE`. You are not charged, and the other videos in the batch keep running.

**Can I also get YouTube comments?**

Yes, with the sibling Actor [YouTube Comments Scraper](https://apify.com/apihq/youtube-comments-scraper), which uses the same failure-free billing.

**Can I use this Actor from my own code?**

Yes. Use the Apify API or one of the official SDKs (Node.js: `apify-client`, Python: `apify-client`). The Console shows ready-to-paste code samples on the API tab.

### Found a bug or want a feature?

Open an issue on this Actor's Issues tab and include the `requestId` from any error record you saw. We respond within one business day.

# Actor input Schema

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

TikTok video URLs in the form https://www.tiktok.com/@user/video/<id>. Aliases of the same video are de-duplicated; up to 50 unique posts are processed per run. Short vm.tiktok.com / vt.tiktok.com links are not supported yet and come back as a success:false record with code VALIDATION_FAILED. A post that is unavailable or cannot be read comes back as a success:false record, so one bad post never blocks the batch. A post whose comment list is empty simply yields no rows.

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

Maximum number of top-level comments to return per post. Replies do not count toward this limit.

## `maxRepliesPerComment` (type: `integer`):

Maximum number of replies to return under each top-level comment. 0 (the default) skips replies. Reply rows carry repliesToId, the parent comment's cid.

## Actor input object example

```json
{
  "postURLs": [
    "https://www.tiktok.com/@scout2015/video/6718335390845095173"
  ],
  "commentsPerPost": 100,
  "maxRepliesPerComment": 0
}
```

# Actor output Schema

## `comments` (type: `string`):

One record per comment (and per reply when maxRepliesPerComment is above 0). success:true carries the comment fields; success:false carries the error code and message for a post that 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/@scout2015/video/6718335390845095173"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("apihq/tiktok-comments-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/@scout2015/video/6718335390845095173"] }

# Run the Actor and wait for it to finish
run = client.actor("apihq/tiktok-comments-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/@scout2015/video/6718335390845095173"
  ]
}' |
apify call apihq/tiktok-comments-scraper --silent --output-dataset

```

## MCP server setup

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