Skip to content

Conversation comments API

Comment threads anchored to a conversation message (or to the whole conversation), with @mention of participants and resolve/unresolve. All endpoints require an authenticated admin session and are nested under a conversation:

  • GET /admin/v1/conversations/<CID>/comments
  • POST /admin/v1/conversations/<CID>/comments
  • PATCH /admin/v1/conversations/<CID>/comments/<ID>
  • DELETE /admin/v1/conversations/<CID>/comments/<ID>

Access model (trust boundary)

<CID> is the authorization anchor. Every request first resolves the caller's read access to the conversation — the caller must be the conversation owner or a member of the project a conversation is shared into (shared_in_project). This is tenant-scoped for free (a conversation carries no tenant column; tenant derives from its owner), so a user from another tenant is neither owner nor member and is refused. There is no platform-admin bypass: an admin who is neither owner nor project member cannot read a conversation's comments.

Failure is closed and does not leak existence: no read access, no such conversation, or a comment whose conversation_id does not match <CID> all return 404.

Comment bodies and @mention targets are user-authored conversation content and are stored in the clear, consistent with the platform's documented at-rest posture for conversation content (PII masking protects the upstream provider, not at-rest storage of a user's own conversation). Comments are deleted with their conversation by the tenant retention Löschfrist and by GDPR user-erase (via database cascade; an erased user's authored comments are additionally counted in the deletion record).

List comments

GET /admin/v1/conversations/<CID>/comments

Returns the conversation's non-deleted comments (oldest first) as a JSON array ([] when there are none). A database error returns 500 — never an empty array — so an outage is never mistaken for an empty thread. Each element:

Field Type Description
id string Comment identifier.
conversation_id string Parent conversation.
message_id string | null Anchored message; null = conversation-level (or the anchored message was hard-deleted).
anchor_start integer | null Inline text-range start (codepoint offset).
anchor_end integer | null Inline text-range end (codepoint offset).
author_id string Comment author.
body string Comment text.
mention_user_ids string[] Validated mentioned user ids (always an array).
resolved_at unix seconds | null null = open thread.
resolved_by string | null Who resolved it.
created_at unix seconds Creation time.
updated_at unix seconds Last update time.

Creating a comment

POST /admin/v1/conversations/<CID>/comments

Required role: authenticated with read access to <CID> (else 404).

Accepted body:

Field Type Required Rules
body string yes Valid UTF-8, non-empty after trimming, ≤ 4000 codepoints.
message_id string no Must be a live (non-deleted) message of <CID>.
anchor_start, anchor_end integer no Both-or-neither; require message_id; 0 ≤ start ≤ end; end ≤ the message's codepoint length.
mention_user_ids string[] no ≤ 20 entries; each must be a conversation participant (owner or a non-deleted project member).

On success returns 201 with { "id": "<comment id>" }.

Rejected inputs (all 400 unless noted):

Condition Status
body missing / not a string / empty / whitespace only 400
body is not valid UTF-8 400
body longer than 4000 codepoints 400
message_id not a string, or not a live message of this conversation 400
anchor offsets present without message_id 400
only one of anchor_start / anchor_end present 400
anchor offsets not numbers, or start < 0, end < start, or end beyond the message length 400
mention_user_ids not an array 400
more than 20 mentions 400
a mentioned id is not a participant of this conversation 400
no read access to <CID> 404

@mention targets are validated against the conversation's participant set (owner plus, for a project-shared conversation, that project's non-deleted members). A non-participant id is rejected with the same 400 as a non-existent id, so the endpoint is not a cross-tenant / cross-project user-existence oracle. Mentions are deduplicated and stored as a JSON array; an empty list is stored as SQL NULL. Delivery of mention notifications is out of scope for this endpoint (a future capability).

Resolve / unresolve a comment

PATCH /admin/v1/conversations/<CID>/comments/<ID>

Body: { "resolved": true | false } (a non-boolean is 400). Allowed for the comment author or the conversation owner; anyone else with read access gets 403. Returns the updated comment row (so a client can reconcile to the server's resolved_by / resolved_at). A comment id not under <CID>, or an already-deleted comment, returns 404.

Deleting a comment

DELETE /admin/v1/conversations/<CID>/comments/<ID>

Soft-deletes the comment. Allowed for the comment author or the conversation owner (owner moderation); anyone else with read access gets 403. A missing or already-deleted comment, or one not under <CID>, returns 404.