Build in Public · LF-03

Retrieving Douyin Comments: From Video URL to JSON Preserving Reply Structure

Start with live parameters for douyin.comments, read comments using minimal requests, preserve video source, comment IDs, and reply relationships, and handle limits, empty results, timestamp parsing, and repeated observations.

After exporting Douyin comments to JSON, a common issue is easily overlooked: a line in the file may be an independent comment or a reply to another comment. Treating them all as independent user feedback loses context, and mixing like counts, reply counts, and comment totals makes discussion engagement metrics ambiguous.

Therefore, fetching comments from a video URL cannot stop at an API returning an array. A complete integration workflow requires confirming the video object, reading current parameters, preserving comment identity and reply relationships, and stating what this observation covers and does not cover. This article uses the douyin.comments action in EveryInfra to explain step by step, making it suitable for developers preparing to integrate comments into research spreadsheets, content analysis, or manual review workflows.

The parameters and fields in this section are verified against the public catalog and source code in 2026-09-04. The request template did not execute paid operations in this round; the synthesized JSON below is only used to demonstrate normalization and is not any real video, user, or comment sample.

Determine Which Interface Type You Are Integrating

If you are developing Douyin authorized login, posting, or other official open capabilities, you should enter the corresponding workflow from the Douyin Open Platform interface list and verify your application type and permissions. Official access tokens, parameters, and error codes belong to official interface contracts and cannot be directly applied to the EveryInfra data requests in this article.

This article only discusses reading comments after the target video has been determined. Video details, comments, search, and speech transcription in EveryInfra are separate actions; if you want to analyze what the comment section is discussing, you should not use video titles or voice transcriptions to impersonate comment text. For private, deleted, or restricted content, you cannot assume you still have read rights just because you have a video URL.

Step 1: Confirm Current Parameters for Comments

Read the full declaration of the Douyin comments capability
curl -fsS --max-time 30 \
  'https://api.everyinfra.com/api/v1/social/catalog?platform=douyin' \
  | jq -e '.capabilities[] | select(.action == "comments") | {
      action, required_params, optional_params, param_meanings,
      mode, available, returns_list,
      default_limit, max_limit, response_fields
    }'

The current catalog shows that douyin.comments requires a url, uses synchronous returns, outputs a list, has a default count of 20, and a maximum limit of 100. List capabilities can use a general limit; these values constrain request scale and are not guarantees of returning one hundred items or that a video has at most one hundred comments.

The current declaration of this action does not provide page, cursor, or has_more parameters for the caller, nor does it expose sort filter parameters. Do not add sort: newest on your own to implement fetching the newest one hundred items; parameters supported by other actions do not mean comments support them. When the required sorting or paging cannot be confirmed from the contract, you should explicitly state that the requirement is not yet met rather than letting the program silently degrade into arbitrary samples.

The response_fields description in the catalog describes comment records, not a pagination protocol. Even if a field named cursor is added to records in the future, you must first confirm it belongs to a pagination mechanism before using it to advance requests; do not write loops based solely on field names.

Step 2: Determine Video Identity Before Requesting

Inputs should be valid video links accepted by this capability. In actual work, operations colleagues may pass a share snippet, a short link, or multiple addresses of the same video with different query parameters. Sorting out the target before calling makes troubleshooting easier than passing any string to url.

It is recommended to store three items at the business layer: the user's initial submission reference, the confirmed request URL, and the video ID you have reliably identified. If you cannot reliably obtain the video ID, temporarily use the confirmed source reference for controlled association, without guessing IDs based on digit lengths or treating short share codes as video identities.

The same video appearing in multiple research lists can reuse the same authorized observation, but you must retain which research tasks it belongs to. Conversely, two videos with the same title or appearing characters cannot be automatically merged. Deduplication is based on content identity, not copywriting similarity.

Link normalization only handles representation differences you have confirmed are irrelevant. Do not delete query parameters across the board; raw inputs and actually submitted values should be retained separately. Unconfirmed redirects, inaccessible content, or links pointing to other objects should go to manual verification rather than continuously retrying with different shapes.

Step 3: Make a Minimal Request Using a Single Video

Assume you have configured EVERYINFRA_API_KEY and an authorized video URL in EVERYINFRA_DOUYIN_VIDEO_URL on your server. Send a single request below, check fields using a small number of results, do not enable automatic retry, and do not claim that full quotas will be returned.

Douyin comments minimal request template
: "${EVERYINFRA_API_KEY:?set the API Key first}"
: "${EVERYINFRA_DOUYIN_VIDEO_URL:?set the authorized full video URL first}"
jq -cn --arg url "${EVERYINFRA_DOUYIN_VIDEO_URL}" '{
  platform: "douyin",
  action: "comments",
  params: {url: $url, limit: 5}
}' | curl --silent --show-error --include --max-time 180 \
  'https://api.everyinfra.com/api/v1/social' \
  -H "Authorization: Bearer ${EVERYINFRA_API_KEY}" \
  -H 'Content-Type: application/json' \
  --data-binary @-

The outer layer of normal synchronous results describes the request, and comment records are located in results; you also need to pay attention to id, count, billing, and other information. Record the outer id as the API request identifier and do not overwrite the comment ids inside results. 180 seconds is the client wait setting for this example, not an interface performance commitment; after a timeout, you can only determine the result is unknown.

Do not rush to keep only text on initial integration. Check whether comments come from the expected video, whether IDs are stable, and whether reply flags and relationship fields appear before connecting them to the database. If the response is not parseable JSON or the business object is not the expected list, retain failure evidence and stop parsing that instance, rather than returning an empty array to fake success.

Step 4: Distinguish Comment Content, Engagement, and Reply Relationships

View the field dictionary; do not interpret by name alone
curl -fsS --max-time 30 \
  'https://api.everyinfra.com/api/v1/social/fields?platform=douyin&action=comments' \
  | jq '{fields, undocumented}'

Current declarations include id, text, like_count, reply_count, author_name, author_id, ip_location, liked_by_author, is_reply, reply_to_id, posted_at, and platform. They can be divided into four groups by usage instead of forcefully filling all columns:

  • Identity and Source: id, platform, and the video source associated by the application from this request. Author nicknames do not serve as comment identities.
  • Content and Time: text and posted_at; additionally, observed_at recorded by you to represent the actual observation timestamp.
  • Engagement Records: like_count, reply_count, liked_by_author. Missing values are not zero, and author likes do not equate to conversion effects or commercial value.
  • Reply Relationships: is_reply and reply_to_id. Leave missing relationships for verification, without automatically supplementing parent comments based on text similarity or array position.

Code will organize retrieved comments and replies into lists and retain association IDs when evidence exists. Therefore, the array itself being flat does not mean all rows are semantically at the same level. Reading should first build a comment ID index before processing reply relationships, rather than assuming the next row is a reply to the previous row.

reply_count describes reply quantity information, not a length guarantee of delivered reply arrays. is_reply being true but lacking reply_to_id, or reply_to_id pointing to a comment not existing in current results, can become relationship gaps that need to be retained. Do not use fabricated parent comments to complete trees; reports should not claim thread completeness just because a tree can be drawn.

Step 5: Design Business JSON That Preserves Identity

Video sources should be associated with records from the confirmed request context, without relying on comment payloads to return a copy of the video URL. Databases can use the combination of platform, video identity, and comment ID as a key; append observations or update mutable fields when the same comment appears again, rather than repeatedly counting them as new increments.

IDs should ideally be preserved as raw strings. If long decimal IDs are first converted to JavaScript Numbers and then back to strings, original values may no longer be recoverable. The JSON specification explains precision boundaries for numeric interoperability; keeping identifiers as strings for values not needing arithmetic avoids meaningless numeric conversions.

The JavaScript below is a deliberately narrowed offline converter: it only accepts comment IDs confirmed to be strings, rejecting unrecognized types from automatic database insertion. It only retains fields and relationship states required by this article, is not a complete SDK, and initiates no network requests. Synthesized text is explicitly written as a synthetic example and cannot be used as real user feedback.

Offline demo: a relation gap leaves state; no fabricated parent comment
function mapComment(row, source) {
  if (!row || typeof row.id !== "string" || !row.id.trim()) {
    throw new Error("string comment id required");
  }
  if (row.reply_to_id != null && typeof row.reply_to_id !== "string") {
    throw new Error("string parent id required");
  }
  const parentId = row.reply_to_id || null;
  const isReply = typeof row.is_reply === "boolean" ? row.is_reply : null;
  const relation = isReply === true
    ? parentId ? "reply_with_reference" : "reply_parent_unknown"
    : isReply === false && !parentId ? "top_level"
    : "needs_review";
  return {
    platform: "douyin",
    video_ref: source.video_ref,
    comment_id: row.id,
    text: typeof row.text === "string" ? row.text : null,
    posted_at_raw: row.posted_at ?? null,
    observed_at: source.observed_at,
    like_count: typeof row.like_count === "number"
      && Number.isFinite(row.like_count) && row.like_count >= 0
      ? row.like_count : null,
    reply_to_id: parentId,
    relation
  };
}

console.log(mapComment(
  {
    id: "synthetic-reply",
    text: "synthetic example, not a real review",
    is_reply: true,
    like_count: null
  },
  {
    video_ref: "synthetic-video",
    observed_at: "2026-09-04T00:00:00Z"
  }
));

This example retains posted_at_raw without forcefully converting timestamps. During actual database insertion, you can set separate parsed timestamps, applied time zones, and parsing statuses; do not automatically guess units as seconds, milliseconds, or local time when units or formats cannot be confirmed. Missing text similarly remains null, preventing models from filling in original phrasing.

reply_with_reference only indicates parent references exist, and parent existence must still be checked within retrieved records of the same video; if absent, external or unretrieved references are retained. top_level is also only a classification based on current fields, not proof that original platform discussion structures are fully restored.

After confirming source time units and meanings, you can record parsed times according to RFC 3339 and retain Z or explicit UTC offsets. Specifications solve representation formats and will not determine whether a series of source digits represents seconds or milliseconds, nor turn unknown source time zones into current machine time zones.

Step 6: Separate Scheduled Observations from Full Paging

Current public requests lack available pagination advancement contracts, so repeating the exact same request yields multiple observations rather than page two or page three. Even if several results happen to differ, you cannot assert that the comment section has been traversed.

When establishing scheduled tasks, you can merge adjacent observations by comment identity and retain the first and most recent timestamps they were seen. Late comments, engagement changes, and reply relationship additions can be updated, but not being seen this round does not equal comments being deleted. Unless clear deletion signals or separate verification bases exist, only record that they were not observed this time.

If your business needs to compare two time periods, first align video sets, invocation parameters, observation frequencies, and successful coverage. When the previous week checks all targets and the following week succeeds on only a few, raw comment counts cannot directly serve as evidence of engagement decline. Writing coverage differences into reports is often more important than adding another sentiment tag.

Also clarify statistical units: whether counting comment rows, discussion threads, or deduplicating authors. Multiple people replying in a single thread versus one person posting consecutively represent different discussion structures. Independent users in reports should not be replaced solely by row counts; claims of counting independent individuals should not be made when reliable author identifiers are missing.

Step 7: Verify Errors, Empty Results, and Billing Separately

When handling failures, at least distinguish between illegal inputs, authentication or permission failures, insufficient quotas, temporary service failures, empty list returns, and clients failing to get complete responses. Their corresponding actions differ; uniformly writing them as no comments misleads both users and monitoring.

For 422, fix requests according to error.code and prompts without retrying without limit counts. For temporary issues, verify whether original requests might have executed before deciding whether to retry; timed-out POST requests cannot be assumed unsubmitted. For content explicitly confirmed inaccessible, stop automatic attempts and recheck scopes without seeking to bypass restrictions.

Billing should also be associated with the same request. Record actual billing and API request IDs, without calculating payable amounts directly from how many comments were captured. Data parsing failures are local processing failures and cannot be used alone to infer that the server completed fulfillment or reversals; similarly, when billing fields are missing from responses, mark them pending verification rather than writing them as free.

Retain Context and Manual Review Entrances Before Handing Comments to Models

Comment classification is best inputting threads or clearly bounded fragments. If a reply simply reads not really, seeing those three words alone makes it impossible to know what is being opposed; when parent comments are unretrieved, retain states of insufficient context rather than forcing positive or negative labels.

Save derived results separately, such as topics, sentiments, judgment rule versions, model versions, and manual corrections. Do not write these fields back into original comment fact columns, nor interpret model-given confidence levels as measurements of user true attitudes. Reports retain sufficient source associations for authorized personnel to review; public materials present only necessary content authorized for display.

Checklist Prior to Scaling Up

  • Target: Video identities correspond to request URLs, scopes and usages are confirmed, and no private content bypasses steps.
  • Structure: results is the expected list, comment IDs preserve precision, and records can be linked back to specific videos.
  • Relationships: Reply fields are retained, parent absences and unknown states are visible, and no threads are fabricated.
  • Observation: Missing values are not filled with zeros, posting times and observation times are separated, and full coverage without evidence is not claimed.
  • Operation: Timeouts do not trigger automatic resends, errors and billing retain request identifiers, and repeated observations are not counted as new increments.
  • Storage: Retain only necessary fields, with access controls, retention periods, and deletion schedules for raw text and author information.

When writing schemas for adapted business JSON, note that fields being described, fields being required, and values allowing null are different conditions. JSON Schema properties do not make fields mandatory by default; express requirements separately using required and type constraints, rather than letting a single empty string pass for all unknown values.

Deliver an Explainable Dataset, Not Just JSON Files

A reusable comment dataset should let taking-over parties understand its target scope, sources, observation times, reply relationships, and missing states. Achieving this allows JSON to reliably enter analysis, review, and reporting workflows; otherwise, exporting many rows merely saves original doubts in another file format.

Start from an authorized video, verify structures and relationships first, and then gradually add targets. The documentation and live catalogs below can help confirm current capabilities; do not use parameters from old articles to replace verification during integration.