Skip to content
Hoody.com

The Notes service organizes content as a tree of nodes within a notebook. Each node has a type such as page, section, channel, message, database, or record. Document-bearing nodes (pages, channels, and similar) carry rich-text content. This page covers node lifecycle, document read and write, secure HTML export, drawing block rendering, and per-user interaction tracking.

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

Section titled “GET /api/v1/notes/notebooks/{notebookId}/nodes”

Returns a paginated list of nodes the user has access to. Filterable by type, parentId, and rootId.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
typequerystringNoFilter by node type
parentIdquerystringNoFilter by direct parent
rootIdquerystringNoFilter by root ancestor
limitqueryintegerNoMaximum number of nodes to return. Default 50
offsetqueryintegerNoNumber of nodes to skip. Default 0
Terminal window
curl -X GET "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes?type=page&limit=25" \
-H "Authorization: Bearer <token>"
{
"nodes": [
{
"id": "node_8f3a2b",
"type": "page",
"name": "Onboarding",
"parentId": "node_root01",
"rootId": "node_root01",
"alias": "onboarding",
"createdAt": "2026-09-12T10:14:22.000Z",
"createdBy": "user_owner_1",
"updatedAt": "2026-09-15T08:02:11.000Z",
"updatedBy": "user_owner_1"
},
{
"id": "node_55cc91",
"type": "page",
"name": "Architecture",
"parentId": "node_root01",
"rootId": "node_root01",
"alias": "architecture",
"createdAt": "2026-09-13T14:48:00.000Z",
"createdBy": "user_owner_1",
"updatedAt": null,
"updatedBy": null
}
],
"total": 2
}

GET /api/v1/notes/notebooks/{notebookId}/nodes/alias/{alias}

Section titled “GET /api/v1/notes/notebooks/{notebookId}/nodes/alias/{alias}”

Resolves a page node by its safe alias within the notebook scope.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
aliaspathstringYesSafe alias of the page node
Terminal window
curl -X GET "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes/alias/onboarding" \
-H "Authorization: Bearer <token>"
{
"id": "node_8f3a2b",
"type": "page",
"name": "Onboarding",
"alias": "onboarding",
"parentId": "node_root01",
"rootId": "node_root01",
"createdAt": "2026-09-12T10:14:22.000Z",
"createdBy": "user_owner_1"
}

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

Section titled “GET /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}”

Returns the full details of a single node by ID.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
nodeIdpathstringYesIdentifier of the node
Terminal window
curl -X GET "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes/node_8f3a2b" \
-H "Authorization: Bearer <token>"
{
"id": "node_8f3a2b",
"type": "page",
"name": "Onboarding",
"alias": "onboarding",
"parentId": "node_root01",
"rootId": "node_root01",
"createdAt": "2026-09-12T10:14:22.000Z",
"createdBy": "user_owner_1",
"updatedAt": "2026-09-15T08:02:11.000Z",
"updatedBy": "user_owner_1"
}

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

Section titled “GET /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/children”

Returns a paginated list of direct children of the specified node.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
nodeIdpathstringYesIdentifier of the parent node
limitqueryintegerNoMaximum number of children to return. Default 50
offsetqueryintegerNoNumber of children to skip. Default 0
Terminal window
curl -X GET "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes/node_root01/children?limit=10" \
-H "Authorization: Bearer <token>"
{
"nodes": [
{
"id": "node_8f3a2b",
"type": "page",
"name": "Onboarding",
"parentId": "node_root01"
},
{
"id": "node_55cc91",
"type": "page",
"name": "Architecture",
"parentId": "node_root01"
}
],
"total": 2
}

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

Section titled “POST /api/v1/notes/notebooks/{notebookId}/nodes”

Creates a new node (section, page, channel, message, database, or record) in the notebook. Every type except section needs a parentId, such as the notebook’s Home section for a page, folder, database or channel. A section can only sit at the root, so it takes no parentId; without attributes.collaborators the caller becomes its admin.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
X-Idempotency-KeyheaderstringNoOptional idempotency key (max 256 chars), such as a random UUID per node. Reusing the same key with an identical request body replays the original response instead of creating a second node; reusing it with a different body returns 409.
NameRequiredTypeDescription
typeYesstringNode type, for example page, section, channel, message, database, or record
attributesYesobjectNode attributes (name, description, alias, and other type-specific fields)
idNostringOptional client-supplied node identifier
parentIdNostringIdentifier of the parent node
Terminal window
curl -X POST "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"type": "page",
"parentId": "node_root01",
"attributes": {
"name": "Roadmap",
"alias": "roadmap",
"description": "Quarterly roadmap notes"
}
}'
{
"id": "node_77ab02",
"type": "page",
"name": "Roadmap",
"alias": "roadmap",
"parentId": "node_root01",
"rootId": "node_root01",
"createdAt": "2026-09-16T11:00:00.000Z",
"createdBy": "user_owner_1"
}

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

Section titled “PATCH /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}”

Updates node attributes (name, description, alias, and so on). type, parentId, and databaseId sent in attributes are ignored rather than refused.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
nodeIdpathstringYesIdentifier of the node
NameRequiredTypeDescription
attributesYesobjectPartial or full set of node attributes to merge
Terminal window
curl -X PATCH "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes/node_8f3a2b" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"attributes": {
"name": "Onboarding (revised)",
"description": "Updated onboarding steps for Q4"
}
}'
{
"id": "node_8f3a2b",
"type": "page",
"name": "Onboarding (revised)",
"description": "Updated onboarding steps for Q4",
"parentId": "node_root01",
"updatedAt": "2026-09-16T11:14:00.000Z",
"updatedBy": "user_owner_1"
}

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

Section titled “DELETE /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}”

Permanently deletes a node and its associated data (documents, files, reactions).

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
nodeIdpathstringYesIdentifier of the node
Terminal window
curl -X DELETE "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes/node_8f3a2b" \
-H "Authorization: Bearer <token>"
{
"success": true
}

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

Section titled “GET /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/document”

Retrieves document content for a node. Supports block filtering via blockIds, line range queries, and HTML/Markdown output for export pipelines.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
nodeIdpathstringYesIdentifier of the node
blockIdsquerystringNoComma-separated list of block IDs to include
linesquerystringNoLine range, for example 1-40
outputquerystringNoOutput format. One of json, md, html
includeCommentsquerystringNoWhether to include comments. One of none, appendix. Default none
ticketquerystringNoExport ticket returned by createExportTicket (required when output=html)
If-None-MatchheaderstringNoOptional (RFC 9110): ETags of copies the caller already holds, or *. When the document is still at one of them the JSON output answers 304 with no body. Ignored by the Markdown and HTML outputs.
Terminal window
curl -X GET "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes/node_8f3a2b/document?output=md&includeComments=none" \
-H "Authorization: Bearer <token>"
{
"id": "node_8f3a2b",
"content": {
"type": "rich_text",
"blocks": {
"b_welcome": {
"id": "b_welcome",
"type": "heading1",
"parentId": "node_8f3a2b",
"index": "a0",
"content": [
{ "type": "text", "text": "Welcome to the project" }
]
},
"b_intro": {
"id": "b_intro",
"type": "paragraph",
"parentId": "node_8f3a2b",
"index": "a1",
"content": [
{ "type": "text", "text": "This page outlines our onboarding process." }
]
}
}
},
"createdAt": "2026-09-12T10:14:22.000Z",
"createdBy": "user_owner_1",
"updatedAt": "2026-09-15T08:02:11.000Z",
"updatedBy": "user_owner_1"
}

PUT /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/document

Section titled “PUT /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/document”

Creates a new document or fully replaces an existing document for a node.

The content field is a block map. Each block’s id repeats its map key; parentId is the document’s node id for a top-level block; index is a sibling sort key (a0, a1, …); content is an array of inline text leaves; attrs holds optional block attributes. The service rejects block ids placed inside attrs, top-level content arrays, or a wrapper with type: "doc".

NameRequiredTypeDescription
content.blocks.{blockId}.idYesstringBlock id; matches its key {blockId} in content.blocks
content.blocks.{blockId}.typeYesstringBlock type, for example paragraph, heading1, heading2, heading3, codeBlock, horizontalRule, blockquote, bulletList, orderedList, listItem, taskList, taskItem, table, drawing
content.blocks.{blockId}.parentIdYesstringContaining node id (for top-level blocks) or containing block id (for nested blocks)
content.blocks.{blockId}.indexYesstringSort key ordering siblings
content.blocks.{blockId}.contentNoarrayInline content. Each item has type (text), text, and optional marks
content.blocks.{blockId}.attrsNoobjectOptional block attributes
NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
nodeIdpathstringYesIdentifier of the node
If-MatchheaderstringNoOptional precondition (RFC 9110): write only if the document is still at one of these ETags, as returned in the ETag header of a document read or write (a quoted tag, or a comma-separated list of them), or * for “the document exists”. Otherwise the write is refused with 412 version_conflict and nothing changes: read the document again and retry. Without it the write is unconditional: it merges into, or replaces, whatever is stored (last writer wins).
NameRequiredTypeDescription
contentYesobjectRich-text document content (block map)
Terminal window
curl -X PUT "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes/node_8f3a2b/document" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"content": {
"type": "rich_text",
"blocks": {
"b_title": {
"id": "b_title",
"type": "heading1",
"parentId": "node_8f3a2b",
"index": "a0",
"content": [
{ "type": "text", "text": "Welcome to the project" }
]
},
"b_intro": {
"id": "b_intro",
"type": "paragraph",
"parentId": "node_8f3a2b",
"index": "a1",
"content": [
{ "type": "text", "text": "Read the " },
{ "type": "text", "text": "onboarding checklist", "marks": [{ "type": "bold" }] },
{ "type": "text", "text": " before your first standup." }
]
}
}
}
}'
{
"id": "node_8f3a2b",
"content": {
"type": "rich_text",
"blocks": {
"b_title": {
"id": "b_title",
"type": "heading1",
"parentId": "node_8f3a2b",
"index": "a0",
"content": [
{ "type": "text", "text": "Welcome to the project" }
]
},
"b_intro": {
"id": "b_intro",
"type": "paragraph",
"parentId": "node_8f3a2b",
"index": "a1",
"content": [
{ "type": "text", "text": "Read the " },
{ "type": "text", "text": "onboarding checklist", "marks": [{ "type": "bold" }] },
{ "type": "text", "text": " before your first standup." }
]
}
}
},
"createdAt": "2026-09-12T10:14:22.000Z",
"createdBy": "user_owner_1",
"updatedAt": "2026-09-16T11:30:00.000Z",
"updatedBy": "user_owner_1"
}

PATCH /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/document

Section titled “PATCH /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/document”

Merges content into an existing document. Top-level keys replace the stored ones; blocks (a map by block id, or a list of blocks with ids) merges by block id: a sent block replaces the stored block with that id, and every other stored block is kept. Removing blocks takes a PUT.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
nodeIdpathstringYesIdentifier of the node
If-MatchheaderstringNoOptional precondition (RFC 9110): write only if the document is still at one of these ETags, as returned in the ETag header of a document read or write (a quoted tag, or a comma-separated list of them), or * for “the document exists”. Otherwise the write is refused with 412 version_conflict and nothing changes: read the document again and retry. Without it the write is unconditional: it merges into, or replaces, whatever is stored (last writer wins).
NameRequiredTypeDescription
contentYesobjectPartial rich-text document content to merge (a blocks map or list of blocks with ids)
Terminal window
curl -X PATCH "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes/node_8f3a2b/document" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"content": {
"blocks": {
"b_intro": {
"id": "b_intro",
"type": "paragraph",
"parentId": "node_8f3a2b",
"index": "a1",
"content": [
{ "type": "text", "text": "Read the onboarding checklist today." }
]
}
}
}
}'
{
"id": "node_8f3a2b",
"content": {
"type": "rich_text",
"blocks": {
"b_welcome": {
"id": "b_welcome",
"type": "heading1",
"parentId": "node_8f3a2b",
"index": "a0",
"content": [
{ "type": "text", "text": "Welcome to the project" }
]
},
"b_intro": {
"id": "b_intro",
"type": "paragraph",
"parentId": "node_8f3a2b",
"index": "a1",
"content": [
{ "type": "text", "text": "Read the onboarding checklist today." }
]
}
}
},
"createdAt": "2026-09-12T10:14:22.000Z",
"createdBy": "user_owner_1",
"updatedAt": "2026-09-16T12:00:00.000Z",
"updatedBy": "user_owner_1"
}

POST /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/document/append

Section titled “POST /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/document/append”

Appends one or more blocks to the END of a node’s rich-text document. The server assigns each block id, parentId, and index; client-supplied id, parentId, index, or attrs.id are rejected. Pass an X-Idempotency-Key header to make retries safe.

The body is a union of two forms. Provide exactly one.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
nodeIdpathstringYesIdentifier of the node
X-Idempotency-KeyheaderstringNoOptional idempotency key (max 256 chars). Reusing the same key with an identical request body and node replays the original response; reusing it with a different body or node returns 409
If-MatchheaderstringNoOptional precondition (RFC 9110): write only if the document is still at one of these ETags, as returned in the ETag header of a document read or write (a quoted tag, or a comma-separated list of them), or * for “the document exists”. Otherwise the write is refused with 412 version_conflict and nothing changes: read the document again and retry. Without it the write is unconditional: it merges into, or replaces, whatever is stored (last writer wins).

Use this when you want to append a single block derived from raw text.

NameRequiredTypeDescription
textYesstringBlock content. Newlines are preserved as a single literal block
typeNostringBlock type. One of paragraph, heading1, heading2, heading3, codeBlock. Default paragraph
attrsNoobject | nullOptional block attributes

Use this when you want to append one or more structured blocks (with marks, headings, horizontal rules, and so on).

NameRequiredTypeDescription
blocksYesarrayFlat array of 1 to 100 root-level blocks

Each block in blocks has the following shape:

NameRequiredTypeDescription
typeNostringBlock type. One of paragraph, heading1, heading2, heading3, codeBlock, horizontalRule. Default paragraph
contentNoarrayInline content. Each item has type (text), text, and optional marks
attrsNoobject | nullOptional block attributes
Terminal window
curl -X POST "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes/node_8f3a2b/document/append" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: key_123" \
-d '{
"text": "All new joiners should pair with a buddy in week one.",
"type": "paragraph"
}'
{
"id": "node_8f3a2b",
"content": {
"type": "rich_text",
"blocks": {
"b_existing_1": {
"id": "b_existing_1",
"type": "heading1",
"parentId": "node_8f3a2b",
"index": "a0",
"content": [
{ "type": "text", "text": "Welcome to the project" }
]
},
"b_existing_2": {
"id": "b_existing_2",
"type": "paragraph",
"parentId": "node_8f3a2b",
"index": "a1",
"content": [
{ "type": "text", "text": "This page outlines our onboarding process." }
]
},
"b_server_001": {
"id": "b_server_001",
"type": "heading3",
"parentId": "node_8f3a2b",
"index": "a2",
"content": [
{ "type": "text", "text": "Buddy program" }
]
},
"b_server_002": {
"id": "b_server_002",
"type": "paragraph",
"parentId": "node_8f3a2b",
"index": "a3",
"content": [
{ "type": "text", "text": "Every new joiner is paired with an experienced teammate." }
]
},
"b_server_003": {
"id": "b_server_003",
"type": "horizontalRule",
"parentId": "node_8f3a2b",
"index": "a4"
},
"b_server_004": {
"id": "b_server_004",
"type": "paragraph",
"parentId": "node_8f3a2b",
"index": "a5",
"content": [
{ "type": "text", "text": "Questions? Ask in the team channel." }
]
}
}
},
"createdAt": "2026-09-12T10:14:22.000Z",
"createdBy": "user_owner_1",
"updatedAt": "2026-09-16T12:30:00.000Z",
"updatedBy": "user_owner_1",
"appendedBlockIds": ["b_server_001", "b_server_002", "b_server_003", "b_server_004"]
}

POST /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/export-ticket

Section titled “POST /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/export-ticket”

Creates a short-lived export ticket used to fetch the document as static HTML. Pass the ticket back via the ticket query parameter on GET .../document?output=html.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
nodeIdpathstringYesIdentifier of the node
NameRequiredTypeDescription
outputNostringOutput format. One of html. Default html
includeCommentsNostringWhether to include comments. One of none, appendix. Default none
includeBackgroundNobooleanWhether to embed the page background in the HTML. Default true
themeModeNostringOne of light, dark. Default dark
themeIdNostring | nullOptional theme identifier (max 64 chars)
themeVariablesNoobjectOptional theme variable overrides (string values)
fileNameNostringOptional filename hint for the downloaded file (max 128 chars)
Terminal window
curl -X POST "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes/node_8f3a2b/export-ticket" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"output": "html",
"includeComments": "appendix",
"themeMode": "light",
"fileName": "Onboarding.html"
}'
{
"ticket": "tkt_8a4b2d9c",
"expiresAt": "2026-09-16T13:00:00.000Z",
"usesRemaining": 1
}

GET /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/blocks/{blockId}/svg

Section titled “GET /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/blocks/{blockId}/svg”

Renders a drawing block as an SVG image. Supports an optional background color and scale factor. Only drawing blocks can be exported; any other block type is a 400.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
nodeIdpathstringYesIdentifier of the node
blockIdpathstringYesIdentifier of the drawing block
bgquerystringNoBackground color, for example #ffffff or transparent
scalequerynumberNoScale factor applied to the rendered SVG
Terminal window
curl -X GET "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes/node_8f3a2b/blocks/block_d12/svg?bg=transparent&scale=2" \
-H "Authorization: Bearer <token>" \
-o drawing.svg

Content-Type: image/svg+xml

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 400" width="600" height="400">
<rect x="0" y="0" width="600" height="400" fill="#ffffff"/>
<path d="M 50 200 Q 150 50 300 200 T 550 200" stroke="#1f2937" stroke-width="3" fill="none"/>
<circle cx="300" cy="200" r="40" fill="#3b82f6"/>
<text x="300" y="350" text-anchor="middle" font-family="sans-serif" font-size="18" fill="#1f2937">Architecture sketch</text>
</svg>

POST /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/interactions/opened

Section titled “POST /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/interactions/opened”

Records that the current user has opened the node. Tracks first and last opened timestamps.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
nodeIdpathstringYesIdentifier of the node
NameRequiredTypeDescription
openedAtNostringOptional ISO 8601 timestamp at which the open occurred
Terminal window
curl -X POST "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes/node_8f3a2b/interactions/opened" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{}'
{
"nodeId": "node_8f3a2b",
"collaboratorId": "user_owner_1",
"firstSeenAt": "2026-09-12T10:14:00.000Z",
"lastSeenAt": "2026-09-15T08:01:50.000Z",
"firstOpenedAt": "2026-09-15T08:01:55.000Z",
"lastOpenedAt": "2026-09-16T13:00:00.000Z"
}

POST /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/interactions/seen

Section titled “POST /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/interactions/seen”

Records that the current user has seen the node. Tracks first and last seen timestamps.

NameInTypeRequiredDescription
notebookIdpathstringYesIdentifier of the notebook
nodeIdpathstringYesIdentifier of the node
NameRequiredTypeDescription
seenAtNostringOptional ISO 8601 timestamp at which the seen event occurred
Terminal window
curl -X POST "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/nb_abc123/nodes/node_8f3a2b/interactions/seen" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{}'
{
"nodeId": "node_8f3a2b",
"collaboratorId": "user_owner_1",
"firstSeenAt": "2026-09-12T10:14:00.000Z",
"lastSeenAt": "2026-09-16T13:00:00.000Z",
"firstOpenedAt": "2026-09-15T08:01:55.000Z",
"lastOpenedAt": "2026-09-15T08:02:11.000Z"
}