DEV Community

unifyport for UnifyPort

Posted on Originally published at unifyport.ai

How to Reply to TikTok Comments via API Without Creating Duplicate Replies

Replying to a TikTok comment through an API looks like a simple write operation:

post_id + comment_id + text → reply
Enter fullscreen mode Exit fullscreen mode

In production, however, the difficult part is not sending the request. It is proving that the selected post and comment are valid targets—and handling cases where the reply outcome is uncertain.

A safe workflow should:

  1. Select a post owned by the connected TikTok account.
  2. Retrieve its top-level comments.
  3. Preserve every provider ID as a string.
  4. Let the operator review the exact target and reply.
  5. Submit the reply once.
  6. Avoid automatically retrying an uncertain write.

This article demonstrates that workflow using the UnifyPort API.

Understand the operation boundary

A public TikTok comment is not a private conversation. You should not send comment replies through a generic messaging or DM endpoint.

The relevant operations are:

Task Method and endpoint
List posts owned by an account GET /v1/accounts/{account_id}/posts
Inspect a selected post POST /v1/accounts/{account_id}/posts/info
List top-level comments POST /v1/accounts/{account_id}/posts/comments/list
Reply to a top-level comment POST /v1/accounts/{account_id}/posts/comments/reply

This workflow applies to posts owned by the connected TikTok account. It is not an arbitrary-video commenting interface.

The connected account should also be authorized and running before these operations are enabled.

Step 1: Verify the post

Start by listing the posts belonging to the selected account. When the user chooses one, inspect it again before enabling comment actions.

Do not treat a pasted TikTok URL as proof that the account owns the post.

Keep these values associated with one another:

const selection = {
  accountId: "connected-account-id",
  postId: "7481234567890123456"
};
Enter fullscreen mode Exit fullscreen mode

TikTok identifiers can exceed JavaScript's safe integer range. Preserve them as strings throughout storage, API requests, UI state, and logs.

Avoid this:

const postId = Number(apiResult.post_id);
Enter fullscreen mode Exit fullscreen mode

Use this instead:

const postId = apiResult.post_id;
Enter fullscreen mode Exit fullscreen mode

A useful validation rule for a returned post ID is:

function isValidPostId(value) {
  return typeof value === "string" && /^[1-9][0-9]*$/.test(value);
}
Enter fullscreen mode Exit fullscreen mode

Step 2: List top-level comments safely

Send the verified post_id to the comment-list endpoint:

async function listTikTokComments({
  apiKey,
  accountId,
  postId,
  cursor
}) {
  const body = {
    post_id: postId,
    limit: 20
  };

  if (cursor) {
    body.cursor = cursor;
  }

  const response = await fetch(
    `https://api.unifyport.ai/v1/accounts/${encodeURIComponent(accountId)}/posts/comments/list`,
    {
      method: "POST",
      headers: {
        "X-Api-Key": apiKey,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(body)
    }
  );

  return response.json();
}
Enter fullscreen mode Exit fullscreen mode

Omit cursor from the first request. If the response reports has_more: true, pass next_cursor back unchanged.

Do not assume that receiving fewer than 20 comments means pagination is complete. Follow has_more.

Each pagination chain should remain attached to its original account and post:

const paginationState = {
  accountId,
  postId,
  nextCursor,
  hasMore
};
Enter fullscreen mode Exit fullscreen mode

This prevents a cursor obtained for one post from being reused after the operator switches to another post.

The comment-list operation covers top-level comments. It should not be treated as a complete history of every nested reply.

Step 3: Bind the approved reply to its target

Before dispatch, show the operator:

  • The connected account
  • The selected post
  • The selected comment
  • The reply text

Store the reviewed values together:

const approvedReply = {
  accountId,
  postId,
  commentId,
  text,
  approvedAt: new Date().toISOString()
};
Enter fullscreen mode Exit fullscreen mode

This matters because refreshing the comment list could otherwise change the current selection while leaving the draft text intact.

The write should always use the reviewed record—not whatever comment happens to be selected in the UI at dispatch time.

Step 4: Submit the reply once

Here is a minimal helper for submitting the reviewed reply:

async function replyToTikTokComment({
  apiKey,
  accountId,
  postId,
  commentId,
  text
}) {
  if (
    typeof postId !== "string" ||
    !/^[1-9][0-9]*$/.test(postId)
  ) {
    throw new Error("Use the returned post ID string");
  }

  if (
    typeof commentId !== "string" ||
    !commentId.trim()
  ) {
    throw new Error("Select a valid comment");
  }

  if (
    typeof text !== "string" ||
    !text.trim()
  ) {
    throw new Error("Reply text cannot be blank");
  }

  const response = await fetch(
    `https://api.unifyport.ai/v1/accounts/${encodeURIComponent(accountId)}/posts/comments/reply`,
    {
      method: "POST",
      headers: {
        "X-Api-Key": apiKey,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        post_id: postId,
        comment_id: commentId,
        text
      })
    }
  );

  const result = await response.json();

  return {
    httpStatus: response.status,
    result
  };
}
Enter fullscreen mode Exit fullscreen mode

Keep the API key on your backend. It should never be embedded in browser code or exposed to end users.

Also suppress repeated clicks and serialize writes targeting the same comment. These are useful application safeguards, but they are not substitutes for API-level idempotency.

Interpret accepted carefully

A successful response may contain:

{
  "data": {
    "status": "accepted"
  }
}
Enter fullscreen mode Exit fullscreen mode

accepted means the request was accepted. It does not prove that the reply is already publicly visible.

If a reliable created-comment receipt is included, preserve it. Do not require that optional receipt before recognizing acceptance.

Your application state could distinguish:

const replyStatus = {
  state: "accepted",
  publiclyVisible: "unknown"
};
Enter fullscreen mode Exit fullscreen mode

That is more accurate than immediately displaying “Reply published.”

Never blindly retry an uncertain reply

Read operations and write operations need different retry policies.

If listing comments fails before returning data, retrying the read is usually reasonable.

A reply request is different. The provider may have created the reply even when your application did not receive a complete response.

For example:

  1. Your application sends the reply.
  2. The provider accepts it.
  3. The connection times out before the response reaches your application.
  4. A generic retry creates a duplicate reply.

For a timeout, connection interruption, or unconfirmed write receipt, store an unknown outcome:

const replyAttempt = {
  state: "unknown",
  accountId,
  postId,
  commentId,
  text,
  requestId,
  automaticRetryAllowed: false
};
Enter fullscreen mode Exit fullscreen mode

Send this attempt to manual review instead of automatically repeating it.

A later top-level comment read that does not show the reply is not definitive proof that the first write failed.

Handle errors by meaning

Different results require different recovery actions:

Result Recommended action
200 with status: accepted Save acceptance without claiming public visibility
403 post_inaccessible Stop and investigate account access
404 post_not_found Refresh the post selection
404 comment_not_found Refresh and reselect the comment
501 unsupported_by_provider Stop; do not silently switch to a DM
502 reply_post_comment_failed with an unconfirmed outcome Mark for review and do not automatically resend

Do not translate post_inaccessible into “the post was deleted” unless you have separate evidence confirming deletion.

Similarly, comment_not_found does not prove that every nested reply was searched. It only tells you that the requested target could not be verified through this operation.

Treat webhook events as notifications, not complete history

A comment-created webhook can help your application notice new activity, but it should remain a separate intake layer.

Webhook events may be:

  • Delayed
  • Duplicated
  • Reordered
  • Missing

Verify webhook signatures and deduplicate events by their event IDs.

Do not treat webhook delivery as proof that your reply was displayed, and do not assume a missing webhook means the reply failed.

If comment polling requires an account setting such as provider_data.enable_comment: true, confirm the environment behavior and reconnect requirements before relying on it.

Production checklist

Before enabling TikTok comment replies, verify that your application:

  • Confirms the connected account is authorized and running.
  • Allows selection only from posts owned by that account.
  • Preserves post and comment IDs as strings.
  • Follows has_more and next_cursor for pagination.
  • Restricts this workflow to verified top-level comments.
  • Shows the final account, post, comment, and text before dispatch.
  • Prevents double-click submissions.
  • Records the request ID and HTTP result.
  • Does not automatically retry uncertain writes.
  • Does not log API keys or unnecessary customer comment text.
  • Does not interpret accepted as guaranteed public visibility.

The most important design principle is simple:

Reads can often be retried. An uncertain public write should be reviewed before it is repeated.

That distinction prevents a temporary network problem from becoming a visible duplicate reply.


This article was adapted from an original UnifyPort technical guide with AI-assisted editing.

Top comments (0)