Skip to main content

Working with Comments

What the UI calls a comment is an Expression asset inside a Conversation — and "commenting on" a workitem is not a special relationship: it is a mention.

The Model​

TypeRole
ConversationThe thread container. Has Join/Leave operations — and no Delete: conversations cannot be removed through the API.
ExpressionOne comment. Author and Content are required; AuthoredAt is marked required but the server fills it in when omitted.

The relations that make it work:

  • Expression.BelongsTo → its Conversation.
  • Expression.InReplyTo → the Expression it replies to (Replies is the read-only reverse).
  • Expression.Mentions → any assets the comment references — typed BaseAsset, so workitems, members, projects, anything. Mentioning an asset is what places the comment in that asset's conversation stream. The reverse is MentionedInExpressions, available on every asset.

One rule the server enforces: an Expression must have either a BelongsTo conversation or an InReplyTo parent. Creating one with neither fails with Violation'AssignExpressionConversation'ConversationOrInReplyToRequired — a top-level comment needs a thread; a reply inherits its parent's.

Comment on a Workitem (with a Cross-Reference and a Reply)​

One Bulk API request can create the thread, the comment and a reply — aliases carry the links, including InReplyTo referencing the sibling command:

[
{ "@conv": { "AssetType": "Conversation" } },
{
"@comment": {
"AssetType": "Expression",
"BelongsTo": "@conv",
"Author": "Member:20",
"Content": "This duplicates the login work — see S-01002.",
"Mentions": ["Story:1207", "Story:1208"]
}
},
{
"AssetType": "Expression",
"Author": "Member:20",
"Content": "Agreed — closing this one.",
"InReplyTo": "@comment"
}
]

The first entry in Mentions anchors the comment to the workitem being commented on; the second is the cross-reference. The reply carries no Mentions — and therefore does not appear in either story's stream, only in the thread.

Starting from Numbers​

A create cannot resolve a Number inline (alias limitations) — resolve first, then create:

[
{ "from": "Story", "filter": ["Number=$A"], "select": ["Number"], "with": { "$A": "S-01001" } },
{ "from": "Story", "filter": ["Number=$B"], "select": ["Number"], "with": { "$B": "S-01002" } }
]

Each result's _oid feeds the Mentions array above.

Reading Comments​

From the asset's side — every asset carries MentionedInExpressions:

{
"from": "Story",
"where": { "ID": "Story:1207" },
"select": [{ "from": "MentionedInExpressions", "select": ["Content", "Author.Name", "AuthoredAt"] }]
}

Or from the comment side, filtering the mention relation by OID:

{
"from": "Expression",
"filter": ["Mentions='Story:1207'"],
"select": ["Content", "Author.Name", "AuthoredAt", "InReplyTo"],
"sort": ["AuthoredAt"],
"page": { "size": 50, "start": 0 }
}

Remember the reply nuance: replies without their own mentions appear only through the thread (InReplyTo/Replies), not in the asset-side view.

Deleting​

Expressions delete normally (?op=Delete, recoverable like everything else). Conversations do not — an emptied thread simply remains. Plan accordingly if your integration creates threads programmatically.