Skip to content
Social SDK megaphone markSocial SDK
Esc
navigateopen⌘Jpreview
On this page

Handle comments and messages

List, reply to, and moderate comments, and read and send direct messages, with per-platform examples for X, Bluesky, Threads, YouTube, and more.

The normalized facades use platform references and opaque cursors. The examples assume one social client built with the platform’s adapter, an account from connectedAccountRef, and a post from platformPostRef.

Comments

const comments = await social.comments.list(post, { limit: 20 });
const commentId = String(comments.items[0]?.["id"]);
await social.comments.reply({ ...post, kind: "comment", commentId }, { text: "Thanks." });

comments.list(post, { cursor, limit }) returns { items, nextCursor? }. Pass nextCursor back unchanged. comments.iterate follows pages. comments.reply takes a comment reference: the post reference fields plus kind: "comment" and the provider commentId.

Messages

const conversations = await social.messages.listConversations(account, { limit: 20 });

messages.listConversations(account, { cursor, limit }) returns { items, nextCursor? }. listMessages takes a conversation reference, and messages.send(ref, { text }) writes to it. messages.iterateConversations and messages.iterateMessages follow pages. Only X and Bluesky implement normalized messages.

Platform support

X

Comments list and reply. Messages: conversations, messages, and send. Access: user token. Listing replies needs tweet.read and users.read. DM reads need dm.read, users.read, and tweet.read; writes also need dm.write.

comments.list runs X recent search for conversation_id:<postId>, so it only returns replies from the last 7 days, includes nested replies, and accepts a limit from 10 through 100. Pass the conversation’s root post.

Configure x({ auth: { userId, accessToken } }). Normalized DMs need a user token; an app bearer token is rejected with missing_permission. listConversations derives one latest event per conversation from /2/dm_events, never repeats a conversation across pages, and stops after 1,200 conversations. X keeps only 30 days of DM events, so it is not a complete historical inbox.

Native sendDirectMessage, sendConversationMessage, and createGroupConversation are available from the acknowledged X native adapter.

Native deleteComment deletes a reply post with DELETE /2/tweets/{id}. X only lets the authenticated user delete their own posts, so you cannot remove someone else’s reply. If X does not confirm deleted: true, the SDK raises ambiguous_outcome.

const conversations = await social.messages.listConversations(account, { limit: 20 });
await native.sendDirectMessage({ account, participantId, text: "Hello.", context });
await native.deleteComment({ account, commentId: replyPostId, context });

Native hideReply hides or unhides a reply in a conversation the authenticated user started. It needs a user token with tweet.moderate.write, tweet.read, and users.read, and returns the hidden state X reports.

await native.hideReply({ account, replyId, hidden: true, context });

Bluesky

Comments list and reply. Messages: chat conversations, messages, and send. Access: an authenticated session with chat.read and chat.write.

Configure bluesky({ auth: { service, did, accessJwt } }). Comment items keep their AT Protocol identifiers. Chat is normalized when the authenticated session has the declared chat capabilities.

const conversation = { ...account, kind: "conversation", conversationId } as const;
const messages = await social.messages.listMessages(conversation, { limit: 50 });
await social.messages.send(conversation, { text: "Thanks for reaching out." });

Native deleteComment takes the reply’s at:// URI and calls com.atproto.repo.deleteRecord. It only deletes replies stored in the authenticated account’s repository; a URI from another DID raises unauthorized. To hide another account’s reply in your own thread, use hideReply.

await native.deleteComment({ account, commentId: replyUri, context });

Native hideReply hides or unhides a reply by adding its AT-URI to, or removing it from, the hiddenReplies list on the root post’s threadgate. Only the root post’s author can do this, so the call raises invalid_input for replies in other accounts’ threads. The adapter creates a threadgate without reply rules when none exists, and updates an existing one with swapRecord so a concurrent change fails instead of being overwritten. Hidden replies stay in the thread; Bluesky clients collapse them.

await native.hideReply({ account, replyUri, hidden: true, context });

Threads

Comments list and reply. Messages: not supported. Scopes: threads_basic, threads_read_replies, and threads_manage_replies.

Configure threads({ auth: { userId, accessToken } }). Messaging is unsupported-by-platform, so message calls raise unsupported_capability.

Moderation is native. hideReply hides or unhides a reply, and listConversation, listPendingReplies, and managePendingReply cover the rest:

Native deleteComment deletes a reply the authenticated user published, using DELETE /{threads-media-id}. It needs threads_delete and counts toward Meta’s limit of 100 deletions per 24 hours. Use hideReply for other users’ replies.

await native.hideReply({ account, replyId, hide: true, context });
await native.deleteComment({ account, commentId: replyMediaId, context });

YouTube

Comments list and reply. Messages: not supported. Access: an OAuth token for the channel; moderation needs youtube.force-ssl.

Configure youtube({ auth: { accessToken, channelId } }) for the authorized channel. Native heldComments lists comments held for review. Native commentsModeration supports setModerationStatus, update, and delete. setModerationStatus requires a moderationStatus such as published, heldForReview, or rejected; omitting it raises invalid_input.

Native deleteComment calls comments.delete with a comment ID. Pass the top-level comment ID, not the thread ID. Google does not document which comments a channel may delete; a 403 raises missing_permission. To remove another user’s comment from your video, set its moderation status to rejected.

const held = await native.heldComments({ maxResults: 20, context });
await native.commentsModeration({
  action: "setModerationStatus",
  commentId,
  moderationStatus: "published",
  context,
});

Instagram

Comments list and reply. Messages: not supported. Access: a professional account with comment-management permission.

Configure instagram({ auth: { accessToken, accountId, flavor } }). flavor defaults to instagram-login; use facebook-login where the adapter requires Facebook Login. Instagram DMs raise unsupported_capability.

await native.moderateComment({ account, commentId, hidden: true, context });
await native.deleteComment({ account, commentId, context });

LinkedIn

Comments list and reply. Messages: not supported. Scopes: r_member_social and w_member_social, with an author URN.

Configure linkedin({ auth: { accessToken, author }, apiVersion }). author is a urn:li:person:<id> or urn:li:organization:<id>; organization actions need administrator access.

LinkedIn comments use Social Actions and may return composite URNs. If a write is accepted without a returned URN, the SDK can raise ambiguous_outcome. LinkedIn messages raise unsupported_capability. Hiding comments is unsupported-by-platform: the Comments API has no hide or moderation-status operation.

const comments = await social.comments.list(post, { limit: 20 });
await social.comments.reply({ ...post, kind: "comment", commentId }, { text: "Thanks." });

Native deleteComment takes the share or UGC post URN and the full comment URN, and calls DELETE /rest/socialActions/{postUrn}/comments/{commentId}. The SDK adds the actor query when the author is an organization. LinkedIn does not document which comments an actor may delete.

await native.deleteComment({ account, postId: "urn:li:share:123", commentId, context });

Errors and native access

An unavailable operation raises unsupported_capability, not an empty page. Provider permission or token failures use missing_permission; malformed references and missing YouTube moderation status use invalid_input. ambiguous_outcome means a write may have succeeded and must be reconciled.

social.native(backend, { acknowledgeUnsafe: true }) returns the native handle used above. It bypasses tenant authorization and concurrency middleware, so wrap native calls in your own policy.

Last updated on September 24, 2026