Home > Blog > How to Scrape TikTok Comments via API
Post Data

How to Scrape TikTok Comments via API

By PrimeApi·August 15, 2026·8 min read·Updated August 15, 2026

Comments are the part of a TikTok post that actually tells you what an audience thinks. View and like counts measure reach, but the comment thread is the raw text you can search, classify and quote. This guide covers how to pull that thread programmatically with PrimeApi: paging with count and cursor, what each field in a comment record means, how reply_comment_total tells you when to open a sub-thread, and how to handle the case that breaks most first drafts - a deleted video that returns null instead of an array.

The endpoint and its parameters

The call you want is post-comments. It takes one required parameter, postId, which is the numeric ID of the video, plus two optional ones: count for the page size and cursor for the offset into the thread. The base URL is https://api.primeapi.co/ and authentication is a single header, X-PrimeAPI-Key, which you copy from your profile page after registering. There is no OAuth step and no TikTok developer account involved - PrimeApi is an independent service and is not affiliated with or endorsed by TikTok or ByteDance.

The post ID is the long number at the end of a TikTok video URL. If you are starting from a feed, a hashtag or a sound rather than a single link, the item objects those endpoints return carry the same value in their id field, so you can chain straight from a listing into comments without touching a browser.

curl

curl -s "https://api.primeapi.co/post-comments?postId=7330315169584778539&count=50&cursor=0" \
  -H "X-PrimeAPI-Key: YOUR_API_KEY"

Node.js (Axios)

const axios = require("axios");

async function getComments(postId, cursor = 0, count = 50) {
  const res = await axios.get("https://api.primeapi.co/post-comments", {
    params: { postId, count, cursor },
    headers: { "X-PrimeAPI-Key": "YOUR_API_KEY" }
  });
  console.log("Remaining credits:", res.headers["x-primeapi-balance"]);
  return res.data;
}

getComments("7330315169584778539")
  .then(data => {
    const rows = data.comments || [];
    console.log("total:", data.total, "returned:", rows.length);
    rows.forEach(c => console.log(c.cid, c.digg_count, c.text));
  })
  .catch(err => console.error(err.response ? err.response.data : err.message));

Python (Requests)

import requests

BASE = "https://api.primeapi.co"
HEADERS = {"X-PrimeAPI-Key": "YOUR_API_KEY"}

def get_comments(post_id, cursor=0, count=50):
    resp = requests.get(
        f"{BASE}/post-comments",
        params={"postId": post_id, "count": count, "cursor": cursor},
        headers=HEADERS,
        timeout=20,
    )
    resp.raise_for_status()
    print("Remaining credits:", resp.headers.get("X-PrimeAPI-Balance"))
    return resp.json()

data = get_comments("7330315169584778539")
for c in (data.get("comments") or []):
    print(c["cid"], c["digg_count"], c["text"])

Paging with count and cursor

The response wraps the thread in a small envelope. comments holds the array of records for this page, total is the count TikTok reports for the whole thread, cursor is the integer offset you send on the next call, and has_more is an integer flag rather than a boolean - 1 while there is more to read, 0 when you have reached the end. Two other envelope keys are worth knowing about: has_filtered_comments signals that TikTok is holding some comments back from the public view, and alias_comment_deleted relates to comments removed from the thread. There is also the usual status_code and status_msg pair and a log_pb block carrying the request ID, which is handy to keep in your own logs when something looks wrong.

The loop itself is simple: start at cursor=0, send the cursor you get back on each subsequent call, and stop when has_more is 0 or the array comes back empty. Always add a hard page cap as a second exit condition. A cursor that stops advancing is the classic way to turn a collection job into an infinite loop that quietly burns credits.

def all_comments(post_id, page_size=50, max_pages=40):
    cursor, pages, out = 0, 0, []
    while pages < max_pages:
        data = get_comments(post_id, cursor=cursor, count=page_size)
        rows = data.get("comments") or []
        if not rows:
            break
        out.extend(rows)
        pages += 1
        if not data.get("has_more"):
            break
        nxt = data.get("cursor", 0)
        if nxt == cursor:      # cursor did not advance - stop
            break
        cursor = nxt
    return out

Page size is a cost decision, not a speed decision. Each request costs 1 credit whether it returns 5 comments or 50, so larger pages mean fewer credits for the same thread. Keep an eye on the X-PrimeAPI-Balance header while you tune it, and stay inside the default limit of 100 requests per minute.

The comment record, field by field

Each entry in comments is a flat object. These are the fields you will actually build on:

FieldWhat it holds
cidThe comment ID as a string. This is the value you pass as commentId when you fetch replies, and the natural primary key in your own database.
aweme_idThe post ID the comment belongs to. Useful when you flatten comments from many videos into one table.
textThe comment body, emoji included.
create_timeUnix timestamp in seconds for when the comment was posted.
digg_countHow many likes the comment itself has received.
reply_comment_totalThe number of replies hanging off this comment. Zero means there is nothing to fetch.
reply_commentA short inline preview of replies, often a single record, or null when there are none. Handy for showing a teaser without a second call.
reply_id"0" on a top-level comment. On the replies endpoint it carries the parent comment ID instead.
reply_to_reply_id"0" unless the message replies to another reply rather than to the top-level comment.
comment_languageThe detected language code, for example "en". Convenient for filtering before you run any text analysis.
is_author_diggedTrue when the video's creator liked the comment.
author_pinTrue when the creator pinned the comment to the top of the thread.
stick_positionThe pinning slot, which pairs with author_pin for ordering.
status, fold_status, no_showVisibility and moderation flags. Folded or hidden comments still appear in the payload, so filter on these if you only want what a normal viewer sees.
image_listAttached images, null on a plain text comment.
text_extraMarkup for mentions and hashtags inside the comment text.
label_listAny labels TikTok has attached to the comment, or null.
share_infoA block with url, title, desc and acl - the deep link back to the comment on tiktok.com.
sort_tags, sort_extra_scoreRanking hints TikTok uses to order the thread, including a reply score.
is_comment_translatableWhether TikTok offers a translation for the comment.

Store cid with a unique constraint and upsert on it. Threads are re-ranked constantly, so the same comment will reappear at a different position between runs and you want that to be an update, not a duplicate row. Because the data is real-time and uncached, digg_count is a live value - snapshotting it with each collection run is what lets you see which comments are gaining traction rather than only which are already on top.

Following reply_comment_total into replies

The comments endpoint gives you the top level of the discussion. reply_comment_total is the pointer that tells you where the rest of it lives. When that number is greater than zero, call post-comment-replies with both the original postId and the parent's cid as commentId. It pages with the same count and cursor pattern and returns the same envelope of comments, cursor, has_more and total.

curl -s "https://api.primeapi.co/post-comment-replies?postId=7191880324966599942&commentId=7192268986179076865&count=10&cursor=0" \
  -H "X-PrimeAPI-Key: YOUR_API_KEY"

Reply records look almost identical to top-level ones, with three differences worth coding around. reply_id now holds the parent comment ID, which is how you rebuild the tree. reply_comment is null, because a reply does not nest further previews. And two thread fields appear, thread_id and thread_has_more, alongside the translation-related trans_btn_style, translated_text_extra and text_to_image_item_ids. Since a reply carries the same aweme_id and cid shape as a parent, one table with a nullable parent column handles both levels cleanly. The dedicated walkthrough on getting replies to a TikTok comment goes further into thread reconstruction if you need the whole tree.

Be deliberate about which threads you expand. Every reply page is another credit, so a video with 300 top-level comments where 80 have replies is 80 extra calls minimum. A sensible rule is to expand only comments where reply_comment_total clears a threshold, or where digg_count puts the comment near the top of the thread. Those are the sub-threads that carry the conversation anyway, and skipping the long tail of one-reply comments cuts most of the cost. If you are feeding the text into classification afterwards, the notes on TikTok comment sentiment analysis cover what to do with the collected text.

Deleted videos and null comments

This is the failure mode that catches people. When the post ID points at a video that has been deleted or taken down, the call still succeeds at the HTTP level and still costs a credit, but comments is null rather than an empty array. Comments being disabled on the post produces the same result. Code written as a direct for loop over data["comments"] throws a type error on the first such video, which in a batch job means the whole run dies partway through.

The fix is one line: coerce the value before you iterate, exactly as the samples above do with data.get("comments") or [] in Python and data.comments || [] in JavaScript. Then treat "zero comments returned" as a normal outcome rather than an exception, and log the post ID so you can tell a genuinely quiet video apart from a dead one.

If you need to know which of the two it is, check the video first with post-detail. A live video resolves to an itemInfo.itemStruct object with a top-level statusCode and statusMsg beside it; a removed one does not come back with the usual structure. When it does resolve, stats.commentCount gives you the number of comments TikTok says the video has, which is the figure to compare your collected rows against, and itemCommentStatus plus takeDown on the same record describe whether commenting is open and whether the item has been actioned. Running post-detail on every video costs an extra credit each, so most pipelines only reach for it when a comments call comes back empty. The guide to TikTok video data covers the rest of that record.

Credits, limits and errors

Every request costs 1 credit and the remaining balance comes back in the X-PrimeAPI-Balance response header. The default rate limit is 100 requests per minute, which is generous for a single thread but easy to hit when you fan out across many videos and their replies - a token bucket or a simple sleep between pages is enough. Four failures are reported as a JSON message rather than by status code alone: a missing key gives "Please sign up to primeapi.co", a wrong or inactive key gives "PrimeAPI-Key is not available", crossing the limit returns the per-minute message, and an empty balance returns "Your balance has been exhausted...". Branch on that body. The API documentation keeps the same list in reference form, and you can try any endpoint against your own key in the playground before writing a line of code.

Budgeting is straightforward once you know your page size. Collecting the top 100 comments on 1,000 videos at 50 per page is 2,000 credits, plus whatever reply expansion you allow. New accounts get 50 free credits to prototype with, and the paid tiers on the pricing page run from 2,500 credits at $9.90 up to 500,000 at $279.00. If you have not set up a key yet, registration takes a minute and the free credits are enough to page through several full threads.

Putting it together

A dependable comment collector looks like this: resolve your post IDs, page post-comments with a fixed count and the returned cursor until has_more is 0, upsert every record on cid, then expand only the comments whose reply_comment_total justifies a second call. Guard every iteration against a null comments array, cap your pages, and compare what you collected against total so you notice when a thread stops short. Do that and comment scraping becomes a predictable, one-credit-per-page operation that you can point at a hashtag, a sound or a creator's back catalogue without rewriting the collector each time.

Frequently asked questions

How many comments can I get in one request?

One call returns whatever you set in count. Fifty per page is a comfortable working size and is what the example request uses. To read more than one page you repeat the call with the cursor value from the previous response until has_more comes back as 0.

How do I get the replies under a comment?

Take the cid of the comment you want and call post-comment-replies with both the original postId and that value as commentId. Only bother when reply_comment_total on the parent comment is greater than zero, since a comment with no replies costs a credit and returns an empty thread.

Why does the comments array come back null?

The most common reason is that the video no longer exists or has been taken down, but comments being switched off on the post produces the same effect. In that case there is no array to iterate, so treat a null or missing comments key as an empty list rather than looping over it directly.

Does each page of comments cost a credit?

Yes. Every request costs exactly 1 credit, so a video with 500 comments read 50 at a time costs 10 credits. Requests rejected before they reach the data layer - missing key, invalid key, rate limit exceeded or an empty balance - are not charged. New accounts start with 50 free credits and your remaining balance comes back in the X-PrimeAPI-Balance header.

Can I get every comment on a video with a lot of comments?

You can page as deep as TikTok itself exposes, and has_more tells you when that runs out. On very heavily commented videos the reachable set is usually a large sample rather than a literal complete archive, so build reporting that tolerates a sample and compare what you collected against total.

Are the comments real-time or cached?

They are fetched in real time with no caching, so a comment posted a moment ago can appear in your next call. Average response time is around one second, and the default rate limit is 100 requests per minute.

Start building with the PrimeApi TikTok API
50 free credits when you sign up. No card required - you only pay when you need more.

← Back to the PrimeApi Blog