Skip to content
Hoody.com

Comments let users leave inline feedback anchored to a document. Use these endpoints to list anchors, create new threads, edit or delete messages, re-anchor threads when content shifts, and resolve conversations.

All endpoints operate against the container-scoped notes service:

https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.icu

GET /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/comment-anchors

Returns lightweight thread anchor metadata for comment decorations. Use this endpoint to render comment markers in a document without fetching every full comment body.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook that owns the document node.
nodeIdpathstringYesIdentifier of the document node to list anchors for.
limitqueryintegerNoMaximum number of anchors to return. Default: 500.
offsetqueryintegerNoNumber of anchors to skip before returning results. Default: 0.
cursorquerystringNoOpaque cursor returned by a previous call for keyset pagination.
{
"anchors": [
{
"threadId": "cm_7a4f31c2",
"anchor": {
"anchorType": "text-range",
"anchorBlockId": null,
"startBlockId": "blk_8c91a2",
"startOffset": 14,
"endBlockId": "blk_8c91a2",
"endOffset": 42,
"anchorQuote": "experimental design",
"anchorContextBefore": "Review the ",
"anchorContextAfter": " before merging.",
"anchorStatus": "active",
"anchorUpdatedAt": "2026-03-12T18:22:05.117Z"
},
"anchorStatus": "active",
"resolvedAt": null,
"version": 3
}
],
"nextCursor": null,
"hasMore": false
}
await client.notes.comments.listAnchors("nb_8f3a1c2e", "nd_42b1e7", { limit: 200 });

GET /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/comments

Returns all comments for a document node, including reply threads.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook that owns the document node.
nodeIdpathstringYesIdentifier of the document node to list comments for.
limitqueryintegerNoMaximum number of comments to return. Default: 100.
offsetqueryintegerNoNumber of comments to skip before returning results. Default: 0.
cursorquerystringNoOpaque cursor returned by a previous call for keyset pagination.
{
"comments": [
{
"id": "cm_7a4f31c2",
"documentId": "nd_42b1e7",
"parentId": null,
"anchorBlockId": "blk_8c91a2",
"anchorType": "text-range",
"startBlockId": "blk_8c91a2",
"startOffset": 14,
"endBlockId": "blk_8c91a2",
"endOffset": 42,
"anchorQuote": "experimental design",
"anchorContextBefore": "Review the ",
"anchorContextAfter": " before merging.",
"anchorStatus": "active",
"anchorUpdatedAt": "2026-03-12T18:22:05.117Z",
"version": 3,
"content": "Can we tighten this sentence? It feels vague.",
"createdAt": "2026-03-10T14:01:22.991Z",
"createdBy": "u_91b3d4",
"createdByName": "Maya Patel",
"updatedAt": null,
"resolvedAt": null,
"resolvedBy": null
}
],
"nextCursor": null,
"hasMore": false
}
await client.notes.comments.list("nb_8f3a1c2e", "nd_42b1e7", { limit: 50 });

POST /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/comments

Creates a new comment on a document node. The anchor field can target the whole document, a specific block, or a text range.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook that owns the document node.
nodeIdpathstringYesIdentifier of the document node the comment is attached to.
NameTypeRequiredDescription
contentstringYesComment body. Between 1 and 10000 characters.
parentIdstringNoIdentifier of the parent comment when posting a reply.
anchorBlockIdstringNoIdentifier of the block the comment targets when anchor is not supplied.
anchorobjectNoAnchor descriptor. One of: document, block, or text-range.

The anchor field accepts one of the following shapes:

{ "type": "document" }
{ "type": "block", "blockId": "blk_8c91a2" }
{
"type": "text-range",
"startBlockId": "blk_8c91a2",
"startOffset": 14,
"endBlockId": "blk_8c91a2",
"endOffset": 42,
"quote": "experimental design",
"contextBefore": "Review the ",
"contextAfter": " before merging."
}
{
"id": "cm_7a4f31c2",
"documentId": "nd_42b1e7",
"parentId": null,
"anchorBlockId": "blk_8c91a2",
"anchorType": "text-range",
"startBlockId": "blk_8c91a2",
"startOffset": 14,
"endBlockId": "blk_8c91a2",
"endOffset": 42,
"anchorQuote": "experimental design",
"anchorContextBefore": "Review the ",
"anchorContextAfter": " before merging.",
"anchorStatus": "active",
"anchorUpdatedAt": "2026-03-12T18:22:05.117Z",
"version": 1,
"content": "Can we tighten this sentence? It feels vague.",
"createdAt": "2026-03-12T18:22:05.117Z",
"createdBy": "u_91b3d4",
"createdByName": "Maya Patel",
"updatedAt": null,
"resolvedAt": null,
"resolvedBy": null
}
await client.notes.comments.create("nb_8f3a1c2e", "nd_42b1e7", {
content: "Can we tighten this sentence? It feels vague.",
anchor: {
type: "text-range",
startBlockId: "blk_8c91a2",
startOffset: 14,
endBlockId: "blk_8c91a2",
endOffset: 42,
quote: "experimental design",
contextBefore: "Review the ",
contextAfter: " before merging."
}
});

PATCH /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/comments/{commentId}

Edits the content of an existing comment. Pass expectedVersion to enforce optimistic concurrency.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook that owns the document node.
nodeIdpathstringYesIdentifier of the document node the comment belongs to.
commentIdpathstringYesIdentifier of the comment to edit.
NameTypeRequiredDescription
contentstringYesNew comment body. Between 1 and 10000 characters.
expectedVersionintegerNoCurrent comment version. Update fails with 409 if it does not match.
{
"id": "cm_7a4f31c2",
"documentId": "nd_42b1e7",
"parentId": null,
"anchorBlockId": "blk_8c91a2",
"anchorType": "text-range",
"startBlockId": "blk_8c91a2",
"startOffset": 14,
"endBlockId": "blk_8c91a2",
"endOffset": 42,
"anchorQuote": "experimental design",
"anchorContextBefore": "Review the ",
"anchorContextAfter": " before merging.",
"anchorStatus": "active",
"anchorUpdatedAt": "2026-03-12T18:22:05.117Z",
"version": 4,
"content": "Can we tighten this sentence? It feels vague in the intro.",
"createdAt": "2026-03-10T14:01:22.991Z",
"createdBy": "u_91b3d4",
"createdByName": "Maya Patel",
"updatedAt": "2026-03-13T09:14:51.502Z",
"resolvedAt": null,
"resolvedBy": null
}
await client.notes.comments.edit("nb_8f3a1c2e", "nd_42b1e7", "cm_7a4f31c2", {
content: "Can we tighten this sentence? It feels vague in the intro.",
expectedVersion: 3
});

POST /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/comments/{commentId}/reanchor

Updates the root comment anchor for a comment thread. Use this when surrounding content has moved and the original block IDs no longer resolve.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook that owns the document node.
nodeIdpathstringYesIdentifier of the document node the comment belongs to.
commentIdpathstringYesIdentifier of the root comment of the thread to re-anchor.
NameTypeRequiredDescription
anchorobjectYesNew anchor descriptor. One of: document, block, or text-range.
expectedVersionintegerNoCurrent thread version. Update fails with 409 if it does not match.

The anchor field accepts one of the following shapes:

{ "type": "document" }
{ "type": "block", "blockId": "blk_8c91a2" }
{
"type": "text-range",
"startBlockId": "blk_8c91a2",
"startOffset": 0,
"endBlockId": "blk_8c91a2",
"endOffset": 24,
"quote": "experimental design",
"contextBefore": "Review the ",
"contextAfter": " before merging."
}
{
"id": "cm_7a4f31c2",
"documentId": "nd_42b1e7",
"parentId": null,
"anchorBlockId": "blk_8c91a2",
"anchorType": "text-range",
"startBlockId": "blk_8c91a2",
"startOffset": 0,
"endBlockId": "blk_8c91a2",
"endOffset": 24,
"anchorQuote": "experimental design",
"anchorContextBefore": "Review the ",
"anchorContextAfter": " before merging.",
"anchorStatus": "active",
"anchorUpdatedAt": "2026-03-14T11:02:48.661Z",
"version": 5,
"content": "Can we tighten this sentence? It feels vague.",
"createdAt": "2026-03-10T14:01:22.991Z",
"createdBy": "u_91b3d4",
"createdByName": "Maya Patel",
"updatedAt": "2026-03-14T11:02:48.661Z",
"resolvedAt": null,
"resolvedBy": null
}
await client.notes.comments.reanchor("nb_8f3a1c2e", "nd_42b1e7", "cm_7a4f31c2", {
anchor: {
type: "text-range",
startBlockId: "blk_8c91a2",
startOffset: 0,
endBlockId: "blk_8c91a2",
endOffset: 24,
quote: "experimental design",
contextBefore: "Review the ",
contextAfter: " before merging."
},
expectedVersion: 4
});

POST /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/comments/{commentId}/resolve

Marks a comment as resolved. The comment stays in the thread history but no longer surfaces as an open item.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook that owns the document node.
nodeIdpathstringYesIdentifier of the document node the comment belongs to.
commentIdpathstringYesIdentifier of the comment to resolve.
NameTypeRequiredDescription
expectedVersionintegerNoCurrent comment version. Update fails with 409 if it does not match.
{
"id": "cm_7a4f31c2",
"documentId": "nd_42b1e7",
"parentId": null,
"anchorBlockId": "blk_8c91a2",
"anchorType": "text-range",
"startBlockId": "blk_8c91a2",
"startOffset": 14,
"endBlockId": "blk_8c91a2",
"endOffset": 42,
"anchorQuote": "experimental design",
"anchorContextBefore": "Review the ",
"anchorContextAfter": " before merging.",
"anchorStatus": "active",
"anchorUpdatedAt": "2026-03-12T18:22:05.117Z",
"version": 6,
"content": "Can we tighten this sentence? It feels vague.",
"createdAt": "2026-03-10T14:01:22.991Z",
"createdBy": "u_91b3d4",
"createdByName": "Maya Patel",
"updatedAt": null,
"resolvedAt": "2026-03-15T08:30:12.044Z",
"resolvedBy": "u_91b3d4"
}
await client.notes.comments.resolve("nb_8f3a1c2e", "nd_42b1e7", "cm_7a4f31c2", {
expectedVersion: 5
});

DELETE /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/comments/{commentId}

Deletes a comment and its replies.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook that owns the document node.
nodeIdpathstringYesIdentifier of the document node the comment belongs to.
commentIdpathstringYesIdentifier of the comment to delete.
expectedVersionqueryintegerNoCurrent comment version. Delete fails with 409 if it does not match.
{
"success": true
}
await client.notes.comments.delete("nb_8f3a1c2e", "nd_42b1e7", "cm_7a4f31c2", {
expectedVersion: 6
});