<!--
hoody-notes Subskill (sdk)
Auto-generated by Hoody Skills Generator
Generated: 2026-05-06T20:06:11.404Z
Model: mimo-v2.5-pro
Mode: sdk


Tokens: 16816

DO NOT EDIT MANUALLY - Changes will be overwritten on next generation
-->

# hoody-notes

## Overview

### What is hoody-notes?

hoody-notes is a collaborative document and knowledge management service within the Hoody Kit ecosystem. It provides structured storage for notebooks, nodes (pages, sections, channels, messages, databases, records), documents, files, comments, reactions, and version history. The service supports real-time collaboration through WebSocket connections and batch mutation synchronization.

### When to Use hoody-notes

Use hoody-notes when you need to:

- **Create and manage notebooks** — organizational containers for collaborative content
- **Work with structured documents** — pages, sections, channels, and databases with rich block-based content
- **Upload and manage files** — resumable uploads via TUS protocol for attachments and media
- **Collaborate in real-time** — WebSocket sessions, comments, reactions, and collaborator management
- **Track document history** — version snapshots and restoration of previous document states
- **Manage database records** — structured data with filtering, sorting, and search capabilities
- **Handle user identity and permissions** — notebook membership, roles, and collaborator access

### How It Fits Into Hoody Philosophy

hoody-notes follows the Hoody Kit service pattern: each deployment is an isolated, project-scoped instance accessible via automatic domain routing. Authentication is handled through the Hoody proxy layer — no manual DNS or SSL configuration required. The service auto-provisions users and notebooks on first identity call, enabling zero-configuration onboarding.

All endpoints use the `/api/v1/notes/` path prefix. The service exposes 59 endpoints across 16 files, covering the full lifecycle of collaborative document management.

### Service Architecture

```
Notebook (container)
├── Nodes (pages, sections, channels, messages, databases, records)
│   ├── Documents (block-based content)
│   ├── Files (TUS resumable uploads)
│   ├── Collaborators (role-based access)
│   ├── Comments (threaded discussions)
│   ├── Reactions (emoji responses)
│   ├── Versions (document snapshots)
│   └── Interactions (seen/opened tracking)
├── Users (notebook membership)
└── Databases → Records (structured data)
```

### SDK Setup

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

// Token-based authentication
const client = new HoodyClient({
  baseURL: 'https://{projectId}-{containerId}-notes-{serviceId}.{node}.containers.hoody.icu',
  token: 'YOUR_TOKEN'
})
```

---

## Common Workflows

### 1. Health Check and Identity

#### Check Service Health

Verify the notes service is running and responsive.

```
const health = await client.notes.health.check()
console.log('Service status:', health.status)
console.log('Build timestamp:', health.buildTimestamp)
console.log('Memory usage:', health.memory)
```

#### Get Current User Identity

Retrieve the authenticated user's identity. This call auto-provisions the user and their default notebook on first invocation.

```
const identity = await client.notes.identity.get()
console.log('User ID:', identity.userId)
console.log('Username:', identity.username)
console.log('Role:', identity.role)
console.log('Default notebook:', identity.notebookId)
```

**Use this notebookId** as the starting point for all subsequent notebook-scoped operations.

---

### 2. Notebook Management

#### List All Notebooks

Retrieve all notebooks the current user is a member of. Excludes notebooks with role "none" and inactive status.

```
const notebooks = await client.notes.notebooks.listNotebooks()
console.log('Notebook count:', notebooks.length)
for (const nb of notebooks) {
  console.log(`- ${nb.name} (${nb.id})`)
}
```

#### Create a New Notebook

```
const notebook = await client.notes.notebooks.create({
  name: 'Project Alpha',
  description: 'Main project documentation'
})
console.log('Created notebook:', notebook.id)
```

#### Get Notebook Details

```
const notebook = await client.notes.notebooks.get({ notebookId: 'notebook-id-here' })
console.log('Name:', notebook.name)
console.log('Status:', notebook.status)
console.log('Your role:', notebook.role)
```

#### Update Notebook Settings

Only notebook owners can update. The `name` field is required.

```
const updated = await client.notes.notebooks.update('notebook-id-here', {
  name: 'Project Alpha v2',
  description: 'Updated project documentation'
})
```

#### Delete a Notebook

Permanently deletes a notebook and all its data. Only owners can delete.

```
await client.notes.notebooks.delete({ notebookId: 'notebook-id-here' })
```

---

### 3. Node Operations

Nodes are the fundamental content units: pages, sections, channels, messages, databases, and records.

#### List Nodes in a Notebook

```
const nodes = await client.notes.nodes.list({
  notebookId: 'notebook-id-here'
})
for (const node of nodes.items) {
  console.log(`- [${node.type}] ${node.name} (${node.id})`)
}
```

#### List Nodes with Filters

```
const pages = await client.notes.nodes.list({
  notebookId: 'notebook-id-here',
  type: 'page',
  parentId: 'section-node-id'
})
```

#### Create a New Node

Required fields: `type` and `attributes`.

```
const node = await client.notes.nodes.create('notebook-id-here', {
  type: 'page',
  attributes: {
    name: 'Getting Started',
    description: 'Introduction to the project'
  }
})
console.log('Created node:', node.id)
```

#### Get a Node by ID

```
const node = await client.notes.nodes.get({ notebookId: 'notebook-id-here', nodeId: 'node-id-here' })
console.log('Type:', node.type)
console.log('Name:', node.name)
```

#### Resolve a Page by Alias

```
const node = await client.notes.nodes.getByAlias({ notebookId: 'notebook-id-here', alias: 'getting-started' })
console.log('Resolved page:', node.id)
```

#### Update a Node

The `attributes` field is required. Type and parentId cannot be changed.

```
const updated = await client.notes.nodes.update('notebook-id-here', 'node-id-here', {
  attributes: {
    name: 'Updated Page Name',
    description: 'New description'
  }
})
```

#### List Child Nodes

```
const children = await client.notes.nodes.listChildren({
  notebookId: 'notebook-id-here',
  nodeId: 'parent-node-id'
})
```

#### Delete a Node

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

```
await client.notes.nodes.delete({ notebookId: 'notebook-id-here', nodeId: 'node-id-here' })
```

---

### 4. Document Operations

Documents contain block-based content attached to nodes.

#### Get Document Content

```
const doc = await client.notes.documents.get({
  notebookId: 'notebook-id-here',
  nodeId: 'node-id-here'
})
console.log('Document blocks:', doc.content)
```

#### Get Document with Block Filtering

```
const doc = await client.notes.documents.get({
  notebookId: 'notebook-id-here',
  nodeId: 'node-id-here',
  blockIds: 'block-1,block-2'
})
```

#### Create or Replace a Document

Required field: `content`.

```
const doc = await client.notes.documents.put('notebook-id-here', 'node-id-here', {
  content: {
    blocks: [
      { type: 'paragraph', content: 'Hello world' }
    ]
  }
})
```

#### Merge Content into Existing Document

Required field: `content`. Existing blocks are preserved unless overwritten.

```
const doc = await client.notes.documents.patch('notebook-id-here', 'node-id-here', {
  content: {
    blocks: [
      { type: 'heading', content: 'New Section' }
    ]
  }
})
```

#### Export a Drawing Block as SVG

```
const svg = await client.notes.documents.exportBlockSvg({
  notebookId: 'notebook-id-here',
  nodeId: 'node-id-here',
  blockId: 'block-id-here',
  bg: '#ffffff',
  scale: 2
})
```

#### Create an Export Ticket for HTML

```
const ticket = await client.notes.documents.createExportTicket(
  'notebook-id-here',
  'node-id-here',
  {}
)
console.log('Export ticket:', ticket.ticketId)
```

---

### 5. File Management

Files use the TUS protocol for resumable uploads.

#### List Files in a Notebook

```
const files = await client.notes.files.list({
  notebookId: 'notebook-id-here'
})
for (const file of files.items) {
  console.log(`- ${file.name} (${file.id})`)
}
```

#### Upload a File via TUS Protocol

```
// Step 1: Initialize upload
const init = await client.notes.files.tusCreateUpload({ notebookId: 'notebook-id-here', fileId: 'file-id-here' })

// Step 2: Upload chunks
await client.notes.files.tusUploadChunk({ notebookId: 'notebook-id-here', fileId: 'file-id-here' })

// Step 3: Check upload status
const status = await client.notes.files.tusCheckUpload({ notebookId: 'notebook-id-here', fileId: 'file-id-here' })
```

#### Download a File

```
const fileData = await client.notes.files.download({ fileId: 'file-id-here', notebookId: 'notebook-id-here' })
```

#### Abort an Upload

```
await client.notes.files.tusAbortUpload({ notebookId: 'notebook-id-here', fileId: 'file-id-here' })
```

---

### 6. Collaborator Management

#### List Collaborators on a Node

```
const collaborators = await client.notes.collaborators.list({ notebookId: 'notebook-id-here', nodeId: 'node-id-here' })
for (const c of collaborators) {
  console.log(`- ${c.userId}: ${c.role}`)
}
```

#### Add a Collaborator

Required fields: `collaboratorId` and `role`. Requires admin permission.

```
const collaborator = await client.notes.collaborators.add(
  'notebook-id-here',
  'node-id-here',
  {
    collaboratorId: 'user-id-to-add',
    role: 'editor'
  }
)
```

#### Update Collaborator Role

```
await client.notes.collaborators.update(
  'notebook-id-here',
  'node-id-here',
  'collaborator-id',
  { role: 'admin' }
)
```

#### Remove a Collaborator

```
await client.notes.collaborators.remove(
  'notebook-id-here',
  'node-id-here',
  'collaborator-id'
)
```

---

### 7. Comments

#### List Comments on a Node

```
const comments = await client.notes.comments.list({
  notebookId: 'notebook-id-here',
  nodeId: 'node-id-here'
})
```

#### Create a Comment

```
const comment = await client.notes.comments.create(
  'notebook-id-here',
  'node-id-here',
  {
    content: 'This section needs review.'
  }
)
```

#### Edit a Comment

```
await client.notes.comments.edit(
  'notebook-id-here',
  'node-id-here',
  'comment-id',
  { content: 'Updated comment text.' }
)
```

#### Delete a Comment

```
await client.notes.comments.delete({
  notebookId: 'notebook-id-here',
  nodeId: 'node-id-here',
  commentId: 'comment-id'
})
```

#### List Comment Anchors

```
const anchors = await client.notes.comments.listAnchors({
  notebookId: 'notebook-id-here',
  nodeId: 'node-id-here'
})
```

#### Resolve a Comment

```
await client.notes.comments.resolve(
  'notebook-id-here',
  'node-id-here',
  'comment-id',
  {}
)
```

#### Re-anchor a Comment Thread

```
await client.notes.comments.reanchor(
  'notebook-id-here',
  'node-id-here',
  'comment-id',
  { anchor: 'new-anchor-position' }
)
```

---

### 8. Reactions

#### List Reactions on a Node

```
const reactions = await client.notes.reactions.list({ notebookId: 'notebook-id-here', nodeId: 'node-id-here' })
for (const r of reactions) {
  console.log(`- ${r.emoji} by ${r.userId}`)
}
```

#### Add a Reaction

```
await client.notes.reactions.add('notebook-id-here', 'node-id-here', {
  emoji: '👍'
})
```

#### Remove a Reaction

```
await client.notes.reactions.remove({ notebookId: 'notebook-id-here', nodeId: 'node-id-here', reaction: '👍' })
```

---

### 9. Interactions

#### Mark a Node as Seen

```
await client.notes.interactions.markSeen('notebook-id-here', 'node-id-here', {})
```

#### Mark a Node as Opened

```
await client.notes.interactions.markOpened('notebook-id-here', 'node-id-here', {})
```

---

### 10. Version History

#### List Document Versions

```
const versions = await client.notes.versions.list({
  notebookId: 'notebook-id-here',
  nodeId: 'node-id-here'
})
for (const v of versions.items) {
  console.log(`- Version ${v.id} at ${v.createdAt}`)
}
```

#### Create a Version Snapshot

```
const version = await client.notes.versions.create({ notebookId: 'notebook-id-here', nodeId: 'node-id-here' })
console.log('Snapshot created:', version.id)
```

#### Get a Specific Version

```
const version = await client.notes.versions.get(
  'notebook-id-here',
  'node-id-here',
  'version-id'
)
```

#### Restore a Previous Version

```
await client.notes.versions.restore(
  'notebook-id-here',
  'node-id-here',
  'version-id'
)
```

#### Delete a Version

```
await client.notes.versions.delete(
  'notebook-id-here',
  'node-id-here',
  'version-id'
)
```

---

### 11. Database Records

#### List Records in a Database

```
const records = await client.notes.databases.list({
  notebookId: 'notebook-id-here',
  databaseId: 'database-node-id'
})
```

#### List Records with Filters and Sorts

```
const records = await client.notes.databases.list({
  notebookId: 'notebook-id-here',
  databaseId: 'database-node-id',
  filters: JSON.stringify([{ field: 'status', op: 'eq', value: 'active' }]),
  sorts: JSON.stringify([{ field: 'createdAt', order: 'desc' }]),
  count: 25
})
```

#### Create a Database Record

```
const record = await client.notes.databases.create(
  'notebook-id-here',
  'database-node-id',
  {
    name: 'New Task',
    fields: {
      status: 'todo',
      priority: 'high'
    }
  }
)
```

#### Search Records by Name

```
const results = await client.notes.databases.search({
  notebookId: 'notebook-id-here',
  databaseId: 'database-node-id',
  q: 'task'
})
```

#### Get a Single Record

```
const record = await client.notes.databases.get(
  'notebook-id-here',
  'database-node-id',
  'record-id'
)
```

#### Update a Record

Fields are merged with existing values.

```
const updated = await client.notes.databases.update(
  'notebook-id-here',
  'database-node-id',
  'record-id',
  {
    name: 'Updated Task',
    fields: { status: 'done' }
  }
)
```

#### Delete a Record

```
await client.notes.databases.delete(
  'notebook-id-here',
  'database-node-id',
  'record-id'
)
```

---

### 12. User Management

#### Invite Users to a Notebook

```
const result = await client.notes.users.invite('notebook-id-here', {
  usernames: ['alice', 'bob']
})
console.log('Created:', result.created)
console.log('Errors:', result.errors)
```

#### Update a User's Role

Requires owner or admin permission.

```
await client.notes.users.updateRole('user-id', {
  role: 'admin'
})
```

---

### 13. Avatar Management

#### Upload an Avatar

Accepts JPEG, PNG, or WebP. Resized to 500x500 and converted to JPEG.

```
const avatar = await client.notes.avatars.upload()
console.log('Avatar ID:', avatar.avatarId)
```

#### Download an Avatar

```
const imageData = await client.notes.avatars.download({ avatarId: 'avatar-id' })
```

---

### 14. WebSocket Sessions

#### Initialize a Socket Session

```
const socket = await client.notes.sockets.init()
console.log('Socket ID:', socket.socketId)
```

#### Open a WebSocket Connection

```
await client.notes.sockets.open({ socketId: 'socket-id-here' })
```

---

## Advanced Operations

### Multi-Step Workflow: Create a Complete Page with Content

This workflow demonstrates creating a notebook, adding a page node, writing document content, and setting up collaboration.

```
// Step 1: Get identity and default notebook
const identity = await client.notes.identity.get()
const notebookId = identity.notebookId

// Step 2: Create a section node
const section = await client.notes.nodes.create(notebookId, {
  type: 'section',
  attributes: { name: 'Documentation' }
})

// Step 3: Create a page under the section
const page = await client.notes.nodes.create(notebookId, {
  type: 'page',
  attributes: {
    name: 'API Reference',
    parentId: section.id
  }
})

// Step 4: Write document content
await client.notes.documents.put(notebookId, page.id, {
  content: {
    blocks: [
      { type: 'heading', content: 'API Reference' },
      { type: 'paragraph', content: 'This page documents the API endpoints.' }
    ]
  }
})

// Step 5: Create a version snapshot
await client.notes.versions.create(notebookId, page.id)

// Step 6: Add a collaborator
await client.notes.collaborators.add(notebookId, page.id, {
  collaboratorId: 'other-user-id',
  role: 'editor'
})

// Step 7: Verify the page is accessible
const verifyPage = await client.notes.nodes.get(notebookId, page.id)
console.log('Page created:', verifyPage.name)
```

### Multi-Step Workflow: Database Setup and Record Management

```
const identity = await client.notes.identity.get()
const notebookId = identity.notebookId

// Step 1: Create a database node
const database = await client.notes.nodes.create(notebookId, {
  type: 'database',
  attributes: {
    name: 'Task Tracker',
    description: 'Project task management'
  }
})

// Step 2: Create initial records
const record1 = await client.notes.databases.create(notebookId, database.id, {
  name: 'Setup CI/CD',
  fields: { status: 'todo', priority: 'high', assignee: 'alice' }
})

const record2 = await client.notes.databases.create(notebookId, database.id, {
  name: 'Write documentation',
  fields: { status: 'in-progress', priority: 'medium', assignee: 'bob' }
})

// Step 3: Search for records
const searchResults = await client.notes.databases.search({
  notebookId,
  databaseId: database.id,
  q: 'documentation'
})

// Step 4: Update a record
await client.notes.databases.update(notebookId, database.id, record1.id, {
  fields: { status: 'in-progress' }
})

// Step 5: List all records with filtering
const activeRecords = await client.notes.databases.list({
  notebookId,
  databaseId: database.id,
  filters: JSON.stringify([{ field: 'status', op: 'neq', value: 'done' }])
})
```

### Multi-Step Workflow: Document Review Cycle

```
const notebookId = 'notebook-id'
const nodeId = 'page-node-id'

// Step 1: Get current document
const doc = await client.notes.documents.get({ notebookId, nodeId })

// Step 2: Create a version before editing
await client.notes.versions.create(notebookId, nodeId)

// Step 3: Patch document with new content
await client.notes.documents.patch(notebookId, nodeId, {
  content: {
    blocks: [
      { type: 'heading', content: 'Updated Section' },
      { type: 'paragraph', content: 'Revised content here.' }
    ]
  }
})

// Step 4: Add review comment
await client.notes.comments.create(notebookId, nodeId, {
  content: 'Please review the updated section.'
})

// Step 5: Mark as seen by reviewer
await client.notes.interactions.markSeen(notebookId, nodeId, {})

// Step 6: Add approval reaction
await client.notes.reactions.add(notebookId, nodeId, { emoji: '✅' })

// Step 7: If needed, restore previous version
const versions = await client.notes.versions.list({ notebookId, nodeId })
if (versions.items.length > 1) {
  await client.notes.versions.restore(notebookId, nodeId, versions.items[1].id)
}
```

### Batch Mutations

Process multiple operations in a single request for efficiency.

```
const identity = await client.notes.identity.get()
const notebookId = identity.notebookId

const result = await client.notes.mutations.sync(notebookId, {
  mutations: [
    {
      type: 'node.create',
      payload: {
        type: 'page',
        attributes: { name: 'Batch Page 1' }
      }
    },
    {
      type: 'node.create',
      payload: {
        type: 'page',
        attributes: { name: 'Batch Page 2' }
      }
    },
    {
      type: 'reaction.add',
      payload: {
        nodeId: 'existing-node-id',
        emoji: '🎉'
      }
    }
  ]
})

console.log('Mutation results:', result.results)
```

### Error Recovery Patterns

#### Retry on Transient Failures

```
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await fn()
    } catch (error) {
      if (attempt === maxRetries) throw error
      const delay = Math.pow(2, attempt) * 1000
      await new Promise(resolve => setTimeout(resolve, delay))
    }
  }
  throw new Error('Unreachable')
}

// Usage
const notebook = await withRetry(() =>
  client.notes.notebooks.create({ name: 'Resilient Notebook' })
)
```

#### Verify State Before Operations

```
// Ensure node exists before updating
try {
  const node = await client.notes.nodes.get(notebookId, nodeId)
  if (node.type !== 'page') {
    throw new Error('Cannot edit document on non-page node')
  }
  await client.notes.documents.patch(notebookId, nodeId, {
    content: { blocks: [{ type: 'paragraph', content: 'Safe update' }] }
  })
} catch (error) {
  if (error.status === 404) {
    console.error('Node not found:', nodeId)
  } else {
    throw error
  }
}
```

#### Cleanup on Failure

```
async function createPageWithContent(
  notebookId: string,
  name: string,
  content: object
) {
  let nodeId: string | undefined
  try {
    const node = await client.notes.nodes.create(notebookId, {
      type: 'page',
      attributes: { name }
    })
    nodeId = node.id

    await client.notes.documents.put(notebookId, nodeId, { content })
    await client.notes.versions.create(notebookId, nodeId)

    return node
  } catch (error) {
    if (nodeId) {
      await client.notes.nodes.delete(notebookId, nodeId).catch(() => {})
    }
    throw error
  }
}
```

### Performance Considerations

#### Paginate Large Result Sets

```
async function getAllNodes(notebookId: string) {
  const allNodes: any[] = []
  let offset = 0
  const limit = 50

  while (true) {
    const page = await client.notes.nodes.list({
      notebookId,
      limit,
      offset
    })
    allNodes.push(...page.items)
    if (page.items.length < limit) break
    offset += limit
  }

  return allNodes
}
```

#### Use SDK Pagination Helpers

```
// Collect all database records across pages
const allRecords = await client.notes.databases.listAll({
  notebookId: 'notebook-id',
  databaseId: 'database-id',
  count: 100
})

// Or use async iterator for memory-efficient processing
for await (const record of client.notes.databases.listIterator({
  notebookId: 'notebook-id',
  databaseId: 'database-id'
})) {
  await processRecord(record)
}
```

#### Use Batch Mutations for Bulk Operations

Instead of making individual API calls for each operation, batch them:

```
// Inefficient: 10 separate calls
for (const name of pageNames) {
  await client.notes.nodes.create(notebookId, {
    type: 'page',
    attributes: { name }
  })
}

// Efficient: 1 batched call
await client.notes.mutations.sync(notebookId, {
  mutations: pageNames.map(name => ({
    type: 'node.create',
    payload: { type: 'page', attributes: { name } }
  }))
})
```

---

## Quick Reference

### Most Common Endpoints

| Operation | SDK Method | HTTP |
|-----------|-----------|------|
| Health check | `client.notes.health.check()` | GET /api/v1/notes/health |
| Get identity | `client.notes.identity.get()` | GET /api/v1/notes/me |
| List notebooks | `client.notes.notebooks.listNotebooks()` | GET /api/v1/notes/notebooks |
| Create notebook | `client.notes.notebooks.create(data)` | POST /api/v1/notes/notebooks |
| List nodes | `client.notes.nodes.list(opts)` | GET /api/v1/notes/notebooks/{notebookId}/nodes |
| Create node | `client.notes.nodes.create(notebookId, data)` | POST /api/v1/notes/notebooks/{notebookId}/nodes |
| Get document | `client.notes.documents.get(opts)` | GET /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/document |
| Put document | `client.notes.documents.put(notebookId, nodeId, data)` | PUT /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/document |
| List files | `client.notes.files.list(opts)` | GET /api/v1/notes/notebooks/{notebookId}/files |
| List comments | `client.notes.comments.list(opts)` | GET /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/comments |
| List versions | `client.notes.versions.list(opts)` | GET /api/v1/notes/notebooks/{notebookId}/nodes/{nodeId}/versions |
| List records | `client.notes.databases.list(opts)` | GET /api/v1/notes/notebooks/{notebookId}/databases/{databaseId}/records |
| Batch mutations | `client.notes.mutations.sync(notebookId, data)` | POST /api/v1/notes/notebooks/{notebookId}/mutations |

### Essential Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `notebookId` | string | Notebook identifier (required for most operations) |
| `nodeId` | string | Node identifier (page, section, database, etc.) |
| `type` | string | Node type: page, section, channel, message, database, record |
| `limit` | integer | Pagination page size |
| `offset` | integer | Pagination offset |
| `filters` | string | JSON-encoded filter array for database queries |
| `sorts` | string | JSON-encoded sort array for database queries |

### Typical Response Formats

**Single Resource:**
```
{
  "id": "resource-id",
  "type": "page",
  "name": "Resource Name",
  "createdAt": "2025-01-01T00:00:00Z",
  "updatedAt": "2025-01-01T00:00:00Z"
}
```

**Paginated List:**
```
{
  "items": [],
  "total": 0,
  "limit": 50,
  "offset": 0
}
```

**Health Check:**
```
{
  "status": "ok",
  "service": "notes",
  "buildTimestamp": "2025-01-01T00:00:00Z",
  "startedAt": "2025-01-01T00:00:00Z",
  "pid": 12345,
  "memory": {},
  "fds": {}
}
```

**Identity:**
```
{
  "userId": "user-id",
  "username": "username",
  "role": "owner",
  "notebookId": "notebook-id"
}
```

### Node Types

| Type | Description |
|------|-------------|
| `section` | Organizational folder for grouping pages |
| `page` | Document page with block-based content |
| `channel` | Communication channel |
| `message` | Individual message in a channel |
| `database` | Structured data container |
| `record` | Individual record in a database |

### Collaborator Roles

| Role | Permissions |
|------|-------------|
| `owner` | Full control, can delete notebook |
| `admin` | Manage collaborators, edit content |
| `editor` | Edit content |
| `viewer` | Read-only access |
| `none` | No access (excluded from listings) |