Post captions and comment sections often disagree. Pass a public post URL to a platform comments endpoint, paginate with cursor, and feed comment.text into keyword rules or a classifier. Same { data, meta } envelope on every platform.
You'll need an API key. Test in the Playground. Set a per-post page cap before you write the loop.
Why comments
Comments carry objections, churn language, and campaign pushback that captions miss. Manual copy-paste breaks on pagination. Pair comment pulls with search-based listening when you do not have the post URL — search finds candidates; comments endpoints score the ones above your velocity threshold.
Which platforms expose comments
Pattern: GET /v1/{platform}/posts/comments or .../videos/comments, required url, optional cursor.
| Platform | Route | Notable query params | Reply depth |
|---|---|---|---|
| TikTok | /v1/tiktok/videos/comments | trim for lighter payloads | Top-level per page; replyCount on each row |
| YouTube | /v1/youtube/videos/comments | order=top or newest | /v1/youtube/videos/comments/replies per thread |
/v1/instagram/posts/comments | works on posts and reels | Top-level; replyCount when reported | |
/v1/reddit/posts/comments | trim | Nested replies on each comment | |
/v1/facebook/posts/comments | — | Top-level pages | |
| Rumble | /v1/rumble/videos/comments | — | Top-level pages |
Every response includes data.lookupStatus (found or not_found), data.comments, data.page.nextCursor, data.page.hasMore, and meta.requestId. Field names differ by platform — TikTok uses likes; YouTube uses likeCount; Reddit uses score — but text and id are always present. Each page is one billed lookup; meta.creditsCharged is on every response.
TikTok video comments
curl -sS \
-H "x-api-key: $SOCIALFETCH_API_KEY" \
-G "https://api.socialfetch.dev/v1/tiktok/videos/comments" \
--data-urlencode "url=https://www.tiktok.com/@mrbeast/video/7596844935442189598"TikTok-specific: replyCount flags debate threads; language routes locale-specific models; trim=true drops ancillary fields. totalComments helps decide whether full pagination is worth it on viral posts.
Reference: TikTok video comments.
YouTube video comments
YouTube exposes sort order at query time — top and newest can disagree on the same video.
const params = new URLSearchParams({"url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","order":"top"});
const response = await fetch(
`https://api.socialfetch.dev/v1/youtube/videos/comments?${params.toString()}`,
{
headers: {
"x-api-key": process.env.SOCIALFETCH_API_KEY,
},
}
);
const body = await response.json();
console.log(response.status, body);order value | When to use |
|---|---|
top | Brand monitoring — the visible thread |
newest | Launch week, controversy spikes, support triage |
YouTube-specific: author.creator marks creator replies (tag separately in aggregates); repliesCursor means a sub-thread exists. Fetch replies when replyCount exceeds your threshold:
Each reply page bills separately. Top-level plus high-reply threads usually captures the argument without walking every leaf.
Reference: YouTube video comments · Comment replies.
Instagram post comments
Pass the full post or reel URL — canonical /p/ or /reel/ is safest.
Instagram-specific: no separate replies route — replyCount on top-level comments indicates sub-threads; author.verified helps filter brand-account replies.
Reference: Instagram post comments.
Reddit post comments
Long objections and nested arguments. Pull comments only on threads that passed your score filter (see Reddit product research).
Reddit-specific: depth, nested replies, and score. Flatten before scoring if your classifier expects a string list:
Use trim=true for text, score, and ids only. Keep isSubmitter — OP clarifications change how you read negative replies.
Reference: Reddit post comments. Facebook: /v1/facebook/posts/comments for Page posts or public group discussions.
Paginate the full thread
One page plus data.page.nextCursor per call. Loop until hasMore is false or you hit your cap:
import { SocialFetchClient } from "@socialfetch/sdk";
const client = new SocialFetchClient({
apiKey: process.env.SOCIALFETCH_API_KEY!,
});
const videoUrl = "https://www.tiktok.com/@mrbeast/video/7596844935442189598";
const comments = [];
let cursor: string | undefined;
do {
const result = await client.tiktok.getVideoComments({ url: videoUrl, cursor });
if (!result.ok) break;
if (result.value.data.lookupStatus !== "found") break;
comments.push(
...result.value.data.comments.map((c) => ({
text: c.text,
likes: c.metrics?.likes ?? 0,
})),
);
cursor = result.value.data.page.nextCursor ?? undefined;
} while (cursor);
// Feed comments.text into your sentiment model or keyword rules.
console.log(comments.length, "comments ready for analysis");- Stop when
lookupStatus !== "found". - Prefer
hasMoreover guessing from array length. - Pass
cursorverbatim — do not construct tokens. - Log
meta.requestIdper page.
Re-running the same cursor chain twice bills twice.
Sampling strategies
Full-thread pulls are the exception. Cap pages before you loop.
| Strategy | How | Good for |
|---|---|---|
| Page cap | Stop after N cursor iterations | Hourly monitors, credit budgets |
| Engagement floor | Skip comments where likes/score < threshold | Noise reduction on viral posts |
| Reply threshold | Fetch YouTube reply pages when replyCount > 10 | Debate-heavy threads |
| Time window | Re-fetch newest page only on repeat runs | Trend detection vs archive |
| Stratified sample | Top page + newest page (YouTube order) | When sort order disagrees |
| Search handoff | Comments only on search hits above velocity | Monitoring workflows |
Weight aggregates by engagement: weighted_score = sentiment_score * log1p(likes).
Feed LLM classifiers
Keyword rules — run first. Flag refund, scam, love this, competitor names before spending tokens.
Classical ML — export text + likes/score to sklearn or Hugging Face. Train on a few hundred hand-labeled comments per vertical.
LLM batch classification — structured input, one batch per 50–100 comments:
Ask for per-id labels (positive, negative, neutral, mixed) and optional themes. Require JSON output; store the model version on each row. Strip URLs and bare @mentions if noisy; keep emoji for Gen-Z brands. Pair with transcripts when spoken claims and comments diverge.
Deduplication and warehouse rows
| Problem | Fix |
|---|---|
| Same comment on re-fetch | Upsert on platform + comment.id |
| Same text, different ids (rare) | Secondary hash on normalize(text) for analytics only |
| Same post scored twice in one run | Dedupe URLs before the outer loop |
| Stale sentiment in dashboards | Store capturedAt; re-pull on schedule |
| Audit trail | Persist meta.requestId and meta.creditsCharged per page |
Join back to listening snapshots on postUrl or platform-native video id — not title text.
Troubleshooting
not_found but the post opens in a browser
- Comments may be disabled — lookup can return
foundwith zero rows. - Private, age-gated, or region-blocked media returns
not_found. - Pass canonical URLs (full YouTube watch link, full Reddit
/comments/path).
Empty comments array with lookupStatus: found
- Legitimate for new posts or comments-off accounts. Still billed.
Pagination stops early or repeats
- Only pass
cursorfrom the immediately previous response. - If
hasMoreis true butnextCursoris null, stop and logrequestId. - Cap pages per post.
YouTube top vs newest disagree
- Two passes with different
ordervalues if sort skew matters — two credit lines per page.
Reddit nested replies missing from classifier input
- Walk
replies.itemsrecursively or flatten as shown above.
TikTok id looks like a placeholder
- Still stable within a pull — use for dedupe keys in that run.
lookup_failed or HTTP 503
- Not charged. Retry with backoff; include
meta.requestIdif you contact support.
Classifier drift
- Re-score a frozen snapshot when you change models — do not compare scores across model versions without calibration.
Billing notes
| Action | Credits (typ.) |
|---|---|
| 1 TikTok comment page | 1 |
| 1 YouTube comment page | 1 |
| 1 YouTube reply page | 1 |
| 1 Instagram comment page | 1 |
| 1 Reddit comment page | 1 |
| 10 posts × 3 pages each | 30 |
Completed not_found lookups still bill. lookup_failed and 503 do not. See Credits. Planning: (posts) × (pages per post) + (YouTube reply pages) — 20 posts × 3 pages ≈ 60 credits per run.
What you can build
- Launch monitoring — hourly sentiment after a drop; alert on negative fraction threshold.
- Creator vetting — flag toxic reply patterns before signing.
- Support triage — route negative threads to Slack on keyword match.
- Competitive dashboards — same classifier on your posts and competitor posts from search.
- Research exports — flat CSV of Reddit thread comments with themes for PM decks.
Next steps: Playground · API reference · Pricing