# TikTok Comments Scraper - Reply Threads, No Login (`lanternlab318/tiktok-comments`) Actor

Top-level comments and reply threads from public TikTok videos, read as a signed-out visitor. No login, no cookies. Incomplete threads are marked partial with the reason. $1.99 per 1,000 results + $0.005 per run (from 2026-10-26; $0.50 per 1,000 until then).

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

## Pricing

from $0.50 / 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 Comments & Replies Scraper

Collect the comments under public TikTok videos: top-level comments and their reply threads, each reply linked to its parent. Paste video links or IDs; no login, no cookies, no account of your own. The Actor only reads what TikTok shows a signed-out visitor. **$1.99 per 1,000 results, plus $0.005 per run, from 2026-10-26.** See [Pricing](#pricing).

Input:

```json
{
  "videos": ["https://www.tiktok.com/@scout2015/video/6718335390845095173"],
  "maxCommentsPerVideo": 200,
  "includeReplies": true,
  "maxRepliesPerComment": 100
}
```

Output - one row per comment or reply (`recordType: "comment"` / `"reply"`); `video_summary` rows are free. Sample from a real run; author names partially masked (that run set `maxRepliesPerComment: 3`, which is why the thread is `partial`):

```json
{
  "recordType": "comment",
  "platform": "tiktok",
  "videoId": "7451282344960412950",
  "videoUrl": "https://www.tiktok.com/@autohub561/video/7451282344960412950",
  "commentId": "7471361215131452182",
  "parentId": null,
  "replyToReplyId": null,
  "text": "This has to be AI ? 😭",
  "authorNickname": "fal***",
  "authorUsername": "fal***",
  "likeCount": 68,
  "replyCount": 8,
  "repliesCollected": 3,
  "likedByCreator": false,
  "isPinned": false,
  "publishedAt": "2025-02-14T19:37:50+00:00",
  "collectedAt": "2026-10-19T23:34:30+00:00",
  "source": "comment_list",
  "accessMode": "direct",
  "collectionStatus": "partial",
  "partialReason": "cap:maxRepliesPerComment=3"
}
```

Limits:

- **Public data only.** Only what TikTok shows a signed-out visitor; the Actor never logs in. TikTok's displayed comment count includes comments it does not serve - see [How much you get](#how-much-you-get).
- **Every row says how complete it is.** A thread or video the Actor could not read to the end is marked `partial` with the reason, and a private or removed video is reported as such - never as "0 comments".
- **Your limits stop collection and appear in the row**: `maxCommentsPerVideo`, `maxRepliesPerComment` and the maximum charge per run (see [Pricing](#pricing)).

### What it does

- Reads the top-level comments of each video, page by page, up to your limit.
- Reads each comment's reply thread, up to your limit per thread.
- Records the commenter's public nickname and @username exactly as the page shows them.
- Writes one `video_summary` row per input: video status, how many comments TikTok displays, how many were collected, and why anything is missing.
- Works as a signed-out visitor without a proxy. TikTok serves one visitor session a limited number of pages; when it stops answering, the Actor opens a new visitor session and continues where it stopped.

### What it does not do

- **No search, no profiles, no hashtags.** Bring video links or IDs.
- **No login-only content.** Only what a signed-out visitor can see.
- **No personal profiles.** No user IDs, avatars, bios or follower counts - only the nickname and @username shown beside the comment.
- **Not every comment counted on the video.** TikTok's displayed count includes comments it does not serve. See [How much you get](#how-much-you-get).
- No video downloads, no transcripts, no scheduling built in.

### Input

| Field | Default | Meaning |
| --- | --- | --- |
| `videos` | - | Video links (`https://www.tiktok.com/@user/video/...`), short links (`https://vm.tiktok.com/...`) or numeric video IDs. |
| `maxCommentsPerVideo` | 200 | Top-level comments per video. |
| `includeReplies` | true | Also collect reply threads. |
| `maxRepliesPerComment` | 100 | Replies per thread. |
| `requestDelaySecs` | 0.5 | Minimum pause between two requests. |

### Output

One dataset, three kinds of rows (`recordType`): `comment`, `reply` and `video_summary`. Two views are predefined: **Comments and replies** and **Video summaries**. A `RUN_SUMMARY` record in the key-value store has run totals.

Comment and reply fields:

| Field | Meaning |
| --- | --- |
| `recordType` | `comment` (top level) or `reply`. |
| `videoId`, `videoUrl` | The video. |
| `commentId` | The comment or reply ID. |
| `parentId` | For replies: the top-level comment's `commentId`. |
| `replyToReplyId` | For a reply to another reply in the same thread: that reply's ID. |
| `text` | Comment text. |
| `authorNickname`, `authorUsername` | Public display name and @username shown on the page. |
| `likeCount` | Likes. |
| `replyCount` | Top-level comments: replies TikTok reports for the thread. |
| `repliesCollected` | Top-level comments: replies of this thread in the dataset. |
| `likedByCreator`, `isPinned` | Whether the creator liked or pinned the comment. |
| `publishedAt` | Publication time (UTC). |
| `collectedAt` | Collection time (UTC). |
| `source` | `comment_list`, `reply_api` (the thread's own reply list) or `reply_preview` (a reply TikTok shows inline under the comment). |
| `accessMode` | `direct` (no proxy). |
| `collectionStatus` | `complete` or `partial` for the thread this row belongs to. |
| `partialReason` | Why the thread is partial. |

`video_summary` fields: `input`, `videoId`, `videoStatus`, `videoTitle`, `videoPublishedAt`, `displayedCommentCount`, `topLevelCollected`, `threadsWithReplies`, `expectedReplies`, `repliesCollected`, `threadsComplete`, `threadsPartial`, `records`, `accessMode`, `collectionStatus`, `partialReason`, `elapsedMs`.

#### Video status

| `videoStatus` | Meaning |
| --- | --- |
| `available` | Public video, comments read. |
| `private` | The author made the video private. |
| `unavailable` | The page refuses to show the video to visitors (removed, restricted or never existed); its status code is in `partialReason`. |
| `invalid_input` | The input is not a TikTok video link or ID. |
| `unknown` | The page did not say; comments were still attempted. |

For `private`, `unavailable` and `invalid_input` the counts are **null**, not 0, and `collectionStatus` is `not_collected`.

#### What `partial` means

`collectionStatus` on a `video_summary`:

- `complete`: TikTok's comment list was read to its end, every reply thread was read to its end, and the comments found account for at least 80% of the count TikTok displays.
- `partial`: something is missing. `partialReason` lists what, with a count per reason, for example:
  - `cap:maxCommentsPerVideo=200`, `cap:maxRepliesPerComment=100`: your limits.
  - `cap:maxTotalChargeUsd`: your maximum cost per run was reached (see [Pricing](#pricing)).
  - `comments:refused:...`: TikTok stopped answering for the comment list even after new visitor sessions; the comments read before that are kept.
  - `comments:below_displayed (60 of 112)`: TikTok said there were no more pages, but top-level comments plus the replies TikTok reports for them come to less than 80% of the displayed count.
  - `replies:fewer_than_reply_count x3`: three threads were read to their end but hold fewer replies than their `replyCount`.
  - `replies:refused:... x1`: TikTok stopped answering for a thread's reply list.
- `blocked`: TikTok refused the comment list before any comment was read.
- `not_collected`: see video status.

On a comment or reply row, `collectionStatus` describes that comment's thread.

`displayedCommentCount` is the number TikTok prints on the video. It counts replies too, and it also counts comments TikTok does not serve to anyone browsing the list - for example comments that were deleted, hidden by the creator or filtered. TikTok's `replyCount` on a comment counts the same way. That is why a thread that ends with fewer replies than `replyCount` is marked `partial`, even when TikTok served nothing more.

### How much you get

Measured on 5 public videos on 2026-10-09 (displayed comment counts 27, 112, 187, 2,107 and 5,844), no proxy, up to 1,000 top-level comments per video and 50 replies per comment. TikTok changes its web client often, so results after a change may differ.

- **Rows:** 2,987 (2,193 top-level comments and 794 replies). All 5 videos returned comments; 238 of 248 requests were answered, and the 10 refusals were recovered with a new visitor session.
- **Replies:** 794 of the 985 replies TikTok reported for the collected comments, **81%**. Every thread with a gap is marked `partial`.
- **Against the count shown on the video:** on the 3 videos whose comment list was read to the end, top-level comments plus replies came to **54%-93%** of the displayed count (25 of 27, 150 of 187, 60 of 112). The rest is not served by TikTok's comment list (see above). The 112-comment video would now be marked `partial` with `comments:below_displayed`.
- The 2 large videos stopped at the 1,000 top-level comment limit and are marked `partial`.

Every `video_summary` row shows the displayed count next to what was collected, so you can check each video yourself.

### Pricing

Pay per event: **$1.99 per 1,000 results, plus $0.005 per run.** Apify platform usage is
included; you pay only these two events.

| Event | Price | Charged for |
| --- | --- | --- |
| `result` | $0.00199 ($1.99 per 1,000) | Each comment or reply row in the dataset. `video_summary` rows are free. |
| `run-start` | $0.005 | Once per run, when it starts. |

Example: one video with 300 comments and replies costs $0.005 + 300 x $0.00199 = $0.602.

This price takes effect on **2026-10-26**. Until then the Actor still charges the previous
$0.0005 per result ($0.50 per 1,000); the `run-start` price is unchanged by this. The
Actor's Apify Store page always shows the price in force right now.

To cap a run, set **Maximum cost per run** in the run options. When the cap is reached, the Actor stores no further rows, stops before the next video, marks a video it cut short `partial` with `cap:maxTotalChargeUsd`, says so in the run's status message and log, and records `"fatal": "max total charge reached"` in `RUN_SUMMARY`. Videos after it get no rows.

### Resources

- Runs with **2 GB memory**, fixed. Each visitor session is a real browser page.
- A run opens a visitor session first (about 20-30 seconds). The 5-video run above took about 9 minutes for 2,987 rows and opened 11 visitor sessions.

### Notes

- Respect TikTok's terms and local law, and only collect what you need. Nicknames and usernames are personal data in many jurisdictions.
- TikTok changes its web client often. The `RUN_SUMMARY` record shows request outcomes per endpoint and the first refusals.

### Licenses

Request signing and response parsing are adapted from [Douyin_TikTok_Download_API](https://github.com/Evil0ctal/Douyin_TikTok_Download_API) by Evil0ctal and contributors, licensed under the Apache License 2.0. The license and notice are in `dtk/LICENSE` and `dtk/NOTICE`.

# Actor input Schema

## `videos` (type: `array`):

Public TikTok videos, one per line: a video link (https://www.tiktok.com/@user/video/...), a short link (https://vm.tiktok.com/...), or the numeric video ID. Search is not supported.

## `maxCommentsPerVideo` (type: `integer`):

Stop reading a video's top-level comments after this many. A video cut off here is marked partial.

## `includeReplies` (type: `boolean`):

Also collect the replies under each top-level comment.

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

Stop reading one reply thread after this many replies. A thread cut off here is marked partial.

## `requestDelaySecs` (type: `number`):

Minimum pause between two requests from the same visitor session.

## Actor input object example

```json
{
  "videos": [
    "https://www.tiktok.com/@scout2015/video/6718335390845095173"
  ],
  "maxCommentsPerVideo": 200,
  "includeReplies": true,
  "maxRepliesPerComment": 100,
  "requestDelaySecs": 0.5
}
```

# Actor output Schema

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

No description

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

No description

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

No description

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

// Run the Actor and wait for it to finish
const run = await client.actor("lanternlab318/tiktok-comments").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 = { "videos": ["https://www.tiktok.com/@scout2015/video/6718335390845095173"] }

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

```

## MCP server setup

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

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/ARuYDQH30Pg7JteQl/builds/ta37sS9K3vQGLcKqp/openapi.json
