---
title: Handle comments and messages
description: 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

```ts
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

```ts
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`.

```ts
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.

```ts
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.

```ts
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`.

```ts
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.

```ts
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.

```ts
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`.

```ts
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`.

```ts
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.

```ts
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.

```ts
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.
