Skip to content

Comments

A comment is one level under an activity, and a reply is one level under that comment. Nothing sits under the reply: a reply to a reply is 400 VALIDATION_FAILED. Comments are not activities, so they do not fan out into followers’ timelines.

Text is trimmed and must be 1–2000 characters. A user token is the author; a user_id on that request is ignored. A server token has no user, so user_id is required — missing it is 400 VALIDATION_FAILED.

const comment = await client.activity(activity.id).comments.add({
text: 'Nice run.',
})

A reply sets parent_id to a top-level comment on the same activity. The SDK field is parentId. The parent must not itself be a reply.

const reply = await client.activity(activity.id).comments.add({
text: 'Same here.',
parentId: comment.id, // parent_id
})

The client and server SDKs page oldest first, with the same limit and next cursor as every other list. A reply id passed to the replies list is 404 — a reply has no children.

const page = await client.activity(activity.id).comments.list({ limit: 20 })
const more = page.next === null
? page
: await client.activity(activity.id).comments.list({ limit: 20, next: page.next })
const replies = await client.comment(comment.id).replies.list()

useComments and useReplies load that first page on mount. The page size is the server default, 20. hasNext is true when another page exists, and loadNext appends it. Call loadNext while hasNext is true to read past the first 20.

const { comments, hasNext, loadNext } = useComments(activity.id)
if (hasNext) await loadNext()

Only the author can change the text. A server token cannot. The new text is trimmed and scanned the same way as a create. A bounce leaves the stored text in place.

const updated = await client.activity(activity.id).comments.edit(comment.id, {
text: 'Updated.',
})

Delete is hard. Deleting a comment deletes its replies. comment_count on the activity, and reply_count on a parent, move only by the rows actually removed. A second delete is 404 and does not move the counts.

await client.activity(activity.id).comments.delete(comment.id)

A server token calls the same method. Who may delete:

  • The comment’s author.
  • The activity’s author, for any comment or reply on it.
  • The parent comment’s author, for a reply under it.
  • A group admin is the tenant’s server token after the tenant has checked the role. Dropin does not store the role, and that token may delete any comment.

Anyone else gets 403 FORBIDDEN. Only the comment’s author can edit the text, and only the text. A server token cannot rewrite someone else’s words.

CONTENT_REJECTED means the text was not stored. The message will not say why — it is This text isn't allowed., with no category and no matched word. A rejected edit leaves the previous text in place.

A server token skips the scan unless force_moderation is true. On a user token that field is ignored, and a user-token create or edit is always scanned.

await server.activity(activity.id).comments.add({
text: 'Nice run.',
userId: 'maya',
forceModeration: true, // force_moderation
})

The feed always runs the word list. It calls Azure AI Content Safety only when AZURE_CONTENT_SAFETY_ENDPOINT and AZURE_CONTENT_SAFETY_KEY are both set. Production should point the endpoint at an EU region. When either variable is unset, the word list still runs and the classifier is skipped.

  • Activities — the post a comment hangs off. comment_count is on every activity read; the text is not.
  • Webhooks — comment.added, comment.updated, and comment.removed.
  • Reactions — a different write, with its own counts.