Post a comment (or reply) on a post
POST /posts/{id}/comments
Top-level comment when parent_id is omitted; reply when set.
Depth and root_id are computed server-side from the parent’s
row. Requires posts.comment capability (Base default).
THE POST’S OWN SETTING IS A SEPARATE GATE (#1119 sprint 21d).
When Post.comments_enabled is false the request is refused
with 409 and the stable error value comments_disabled, for a
top-level comment and a reply alike, and nothing is written: no
comment row, no comment_count change, no activity, no
notification, no @-mention. system.admin bypasses the
capability check as before and does NOT bypass this one; it is
the author’s decision about their post, not a permission.
The refusal is evaluated AFTER the read gate, so an unreadable post still answers 404 and its setting is never disclosed. It is read under the post’s row lock inside the insert transaction, so a disable that has committed cannot be raced by a comment that started earlier.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters ”Request Body required
Section titled “Request Body required ”object
When set, the new comment is a reply to this parent. Depth and root_id are computed server-side from the parent row.
Responses
Section titled “ Responses ”Comment created
A single comment in a thread. Threading is via parent_id + root_id + depth (caller renders nested replies indented by depth). annotation_* fields are NULL for plain comments; the review-mode feature reuses the same row shape.
Federation: exactly one of {author_user_ref} XOR {peer_id + actor_uri} is set per row. Local-authored comments have author_user_ref set + peer_id NULL. Remote-authored comments (from a paired peer per ADR 0043 / phase 1.22.D) have author_user_ref NULL + peer_id + actor_uri + display_name populated. Clients render “<display_name> @ <peer’s host>” for remote rows.
object
Local author user.ref. NULL for remote-authored comments (see peer_id / actor_uri).
Server-rendered safe HTML; clients should render this rather than reformatting body.
object
The federation peer that originated this comment. NULL for local-authored rows. Set in tandem with actor_uri
- display_name when the comment came in via the federation inbox.
The remote actor’s URI on their home instance (e.g. https://studio-b.example/users/bob). NULL for local-authored rows.
Display name resolved from federation_remote_actors cache for remote-authored comments. May be empty if the remote instance hasn’t shipped display hints yet; clients should fall back to the actor_uri’s host + local-part when this is empty.
Malformed request
object
Human-readable error summary
Example
the request could not be completedAuthentication required, missing, or invalid
object
Human-readable error summary
Example
the request could not be completedExample
{ "error": "authentication required: sign in and retry with a valid session or API token"}Authenticated but missing required capabilities
object
Human-readable error summary
Example
the request could not be completedResource not found
object
Human-readable error summary
Example
the request could not be completedThe post does not accept comments. error is the stable
value comments_disabled. Nothing was written; the
thread, its count and every existing comment are as they
were. A client holding an older representation of the post
that still said comments_enabled: true should withdraw
its composer on this answer.
object
Human-readable error summary
Example
the request could not be completed