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,
});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 });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.