> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useskald.com/llms.txt
> Use this file to discover all available pages before exploring further.

# NodeJS SDK

> Documentation on how to use the Skald NodeJS SDK.

Node.js client library for the Skald API.

## Installation

```bash theme={null}
npm install @skald-labs/skald-node
```

## Requirements

* Node 18.0.0 or higher

## Usage

### Initialize the client

```javascript theme={null}
import { Skald } from '@skald-labs/skald-node';

const skald = new Skald('your-api-key-here');
```

### Memo Management

#### Create a Memo

Create a new memo that will be automatically processed (summarized, tagged, chunked, and indexed for search):

```javascript theme={null}
const result = await skald.createMemo({
  title: 'Meeting Notes',
  content: 'Full content of the memo...',
  metadata: {
    type: 'notes',
    author: 'John Doe'
  },
  reference_id: 'external-id-123',
  tags: ['meeting', 'q1'],
  source: 'notion',
  expiration_date: '2024-12-31T23:59:59Z'
});

console.log(result); // { memo_uuid: '550e8400-e29b-41d4-a716-446655440000' }
```

**Required Fields:**

* `title` (string, max 255 chars) - The title of the memo
* `content` (string) - The full content of the memo

**Optional Fields:**

* `project_id` (string) - Project UUID (required when using Token Authentication)
* `metadata` (object) - Custom JSON metadata
* `reference_id` (string, max 255 chars) - An ID from your side that you can use to match Skald memo UUIDs with e.g. documents on your end
* `tags` (array of strings) - Tags for categorization
* `source` (string, max 255 chars) - An indication from your side of the source of this content, useful when building integrations
* `expiration_date` (string) - ISO 8601 timestamp for automatic memo expiration

#### Create a Memo from File Upload

Upload a document file (PDF, DOC, DOCX, PPTX) that will be processed asynchronously and converted into a memo:

```javascript theme={null}
import * as fs from 'fs';

const fileBuffer = fs.readFileSync('./document.pdf');

const result = await skald.createMemoFromFile({
  file: fileBuffer,
  filename: 'document.pdf',
  metadata: {
    type: 'report',
    department: 'engineering'
  },
  tags: ['report', '2024'],
  source: 'google-drive',
  reference_id: 'external-file-123'
});

console.log(result); // { ok: true, memo_uuid: '550e8400-e29b-41d4-a716-446655440000' }

// Poll for processing status
const status = await skald.checkMemoStatus(result.memo_uuid);
console.log(status.status); // 'processing' | 'processed' | 'error'
```

**Supported File Types:**

* PDF (.pdf)
* Microsoft Word (.doc, .docx)
* Microsoft PowerPoint (.pptx)
* Maximum file size: 100MB

**Required Fields:**

* `file` (Buffer | Blob) - The file content
* `filename` (string) - The name of the file including extension

**Optional Fields:**

* `reference_id` (string, max 255 chars) - Your external reference ID
* `metadata` (object) - Custom JSON metadata
* `tags` (array of strings) - Tags for categorization
* `source` (string, max 255 chars) - Source system identifier

**Note:** File processing is asynchronous. Use `checkMemoStatus()` to monitor the processing status.

#### Check Memo Processing Status

Monitor the processing status of a memo, especially useful after uploading files:

```javascript theme={null}
// Check status by UUID
const status = await skald.checkMemoStatus('550e8400-e29b-41d4-a716-446655440000');
console.log(status.status); // 'processing' | 'processed' | 'error'

if (status.status === 'error') {
  console.error('Processing failed:', status.error_reason);
}

// Check by reference ID
const status2 = await skald.checkMemoStatus('external-id-123', 'reference_id');

// Poll until processing completes
while (true) {
  const status = await skald.checkMemoStatus(memoUuid);

  if (status.status === 'processed') {
    console.log('Processing complete!');
    break;
  } else if (status.status === 'error') {
    console.error('Processing failed:', status.error_reason);
    break;
  }

  // Wait 2 seconds before checking again
  await new Promise(resolve => setTimeout(resolve, 2000));
}
```

**Status Values:**

* `processing` - The memo is currently being processed (parsed, summarized, chunked, indexed)
* `processed` - Processing completed successfully, memo is ready to use
* `error` - An error occurred during processing, check `error_reason` for details

**Parameters:**

* `memoId` (string, required) - The memo UUID or client reference ID
* `idType` (string, optional) - Either `'memo_uuid'` or `'reference_id'` (default: `'memo_uuid'`)

#### Get a Memo

Retrieve a memo by its UUID or your reference ID:

```javascript theme={null}
// Get by UUID
const memo = await skald.getMemo('550e8400-e29b-41d4-a716-446655440000');

// Get by reference ID
const memo = await skald.getMemo('external-id-123', 'reference_id');

console.log(memo.title);
console.log(memo.content);
console.log(memo.summary);
console.log(memo.tags);
console.log(memo.chunks);
```

The `getMemo()` method returns complete memo details including content, AI-generated summary, tags, and content chunks.

#### List Memos

List all memos with pagination:

```javascript theme={null}
// Get first page with default page size (20)
const memos = await skald.listMemos();

// Get specific page with custom page size
const memos = await skald.listMemos({ page: 2, page_size: 50 });

console.log(`Total memos: ${memos.count}`);
console.log(`Results: ${memos.results.length}`);
console.log(`Next page: ${memos.next}`);
```

**Parameters:**

* `page` (number, optional) - Page number (default: 1)
* `page_size` (number, optional) - Results per page (default: 20, max: 100)

#### Update a Memo

Update an existing memo by UUID or reference ID:

```javascript theme={null}
// Update by UUID
await skald.updateMemo('550e8400-e29b-41d4-a716-446655440000', {
  title: 'Updated Title',
  metadata: { status: 'reviewed' }
});

// Update by reference ID and trigger reprocessing
await skald.updateMemo('external-id-123', {
  content: 'New content that will be reprocessed'
}, 'reference_id');
```

**Note:** When you update the `content` field, the memo will be automatically reprocessed (summary, tags, and chunks regenerated).

**Updatable Fields:**

* `title` (string)
* `content` (string)
* `metadata` (object)
* `client_reference_id` (string)
* `source` (string)
* `expiration_date` (string)

#### Delete a Memo

Permanently delete a memo and all associated data:

```javascript theme={null}
// Delete by UUID
await skald.deleteMemo('550e8400-e29b-41d4-a716-446655440000');

// Delete by reference ID
await skald.deleteMemo('external-id-123', 'reference_id');
```

**Warning:** This operation permanently deletes the memo and all related data (content, summary, tags, chunks) and cannot be undone.

### Search Memos

Search through your memos using semantic search:

```javascript theme={null}
// Basic semantic search
const results = await skald.search({
  query: 'quarterly goals',
  limit: 10
});

// Search with filters
const filtered = await skald.search({
  query: 'python tutorial',
  filters: [
    {
      field: 'source',
      operator: 'eq',
      value: 'notion',
      filter_type: 'native_field'
    },
    {
      field: 'level',
      operator: 'eq',
      value: 'beginner',
      filter_type: 'custom_metadata'
    }
  ]
});

console.log(`Found ${filtered.results.length} results`);
filtered.results.forEach(memo => {
  console.log(`- ${memo.title} (distance: ${memo.distance})`);
});
```

#### Search Parameters

* `query` (string, required) - The search query
* `limit` (integer, optional) - Maximum results to return (1-50, default 10)
* `filters` (array, optional) - Array of filter objects to narrow results (see Filters section below)

#### Search Response

```typescript theme={null}
interface SearchResponse {
  results: Array<{
    memo_uuid: string;
    chunk_uuid: string;
    memo_title: string;
    memo_summary: string;
    content_snippet: string;
    distance: number | null;
  }>;
}
```

* `memo_uuid` - Unique identifier for the memo
* `memo_uuid` - Unique identifier for the chunk
* `memo_title` - Memo title
* `memo_summary` - Auto-generated summary for the memo
* `content_snippet` - A snippet containing the beginning of the chunk content.
* `distance` - A decimal from 0 to 2 determining how close the result was deemed to be to the query when using semantic search (`chunk_vector_search`). The closer to 0 the more related the content is to the query. `null` if using `title_contains` or `title_startswith`.

### Chat with Your Knowledge Base

Ask questions about your memos using an AI agent. The agent retrieves relevant context and generates answers with inline citations.

#### Non-Streaming Chat

```javascript theme={null}
const result = await skald.chat({
  query: 'What were the main points discussed in the Q1 meeting?'
});

console.log(result.response);
// "The main points discussed in the Q1 meeting were:
// 1. Revenue targets
// 2. Hiring plans
// 3. Product roadmap"

console.log(result.ok); // true
```

#### Streaming Chat

For real-time responses, use streaming chat:

```javascript theme={null}
const stream = skald.streamedChat({
  query: 'What are our quarterly goals?'
});

for await (const event of stream) {
  if (event.type === 'token') {
    // Write each token as it arrives
    process.stdout.write(event.content);
  } else if (event.type === 'done') {
    console.log('\nDone!');
  }
}
```

#### Chat Parameters

* `query` (string, required) - The question to ask
* `system_prompt` (string, optional) - A system prompt to guide the AI's behavior
* `filters` (array, optional) - Array of filter objects to focus chat context on specific sources (see Filters section below)
* `project_id` (string, optional) - Project UUID (required when using Token Authentication)

#### Chat Response

Non-streaming responses include:

* `ok` (boolean) - Success status
* `response` (string) - The AI's answer
* `intermediate_steps` (array) - Steps taken by the agent (for debugging)

Streaming responses yield events:

* `{ type: 'token', content: string }` - Each text token as it's generated
* `{ type: 'done' }` - Indicates the stream has finished

### Filters

Filters allow you to narrow down results based on memo metadata. You can filter by native fields or custom metadata fields. Filters are supported in `search()`, `chat()`, `generateDoc()`, and their streaming variants.

See [Filters](/docs/api-reference/filters) for complete documentation.

#### Filter Structure

```typescript theme={null}
interface Filter {
  field: string;                      // Field name to filter on
  operator: FilterOperator;           // Comparison operator
  value: string | string[];           // Value(s) to compare against
  filter_type: 'native_field' | 'custom_metadata';
}
```

#### Native Fields

Native fields are built-in memo properties:

* `title` - Memo title
* `source` - Source system (e.g., "notion", "confluence")
* `client_reference_id` - Your external reference ID
* `tags` - Memo tags (array)

#### Custom Metadata Fields

You can filter on any field from the `metadata` object you provided when creating the memo.

#### Filter Operators

* **`eq`** - Equals (exact match)
* **`neq`** - Not equals
* **`contains`** - Contains substring (case-insensitive)
* **`startswith`** - Starts with prefix (case-insensitive)
* **`endswith`** - Ends with suffix (case-insensitive)
* **`in`** - Value is in array (requires array value)
* **`not_in`** - Value is not in array (requires array value)

#### Filter Examples

```javascript theme={null}
// Filter by source
{
  field: 'source',
  operator: 'eq',
  value: 'notion',
  filter_type: 'native_field'
}

// Filter by multiple tags
{
  field: 'tags',
  operator: 'in',
  value: ['security', 'compliance'],
  filter_type: 'native_field'
}

// Filter by title containing text
{
  field: 'title',
  operator: 'contains',
  value: 'meeting',
  filter_type: 'native_field'
}

// Filter by custom metadata field
{
  field: 'department',
  operator: 'eq',
  value: 'engineering',
  filter_type: 'custom_metadata'
}

// Exclude specific sources
{
  field: 'source',
  operator: 'not_in',
  value: ['draft', 'archive'],
  filter_type: 'native_field'
}
```

#### Combining Multiple Filters

When you provide multiple filters, they are combined with AND logic (all filters must match):

```javascript theme={null}
const results = await skald.search({
  query: 'security best practices',
  filters: [
    {
      field: 'source',
      operator: 'eq',
      value: 'security-docs',
      filter_type: 'native_field'
    },
    {
      field: 'tags',
      operator: 'in',
      value: ['approved', 'current'],
      filter_type: 'native_field'
    },
    {
      field: 'status',
      operator: 'neq',
      value: 'draft',
      filter_type: 'custom_metadata'
    }
  ]
});
```

#### Filters with Chat

Focus chat context on specific sources:

```javascript theme={null}
const result = await skald.chat({
  query: 'What are our security practices?',
  filters: [
    {
      field: 'tags',
      operator: 'in',
      value: ['security', 'compliance'],
      filter_type: 'native_field'
    }
  ]
});
```

### Scopes

Scopes provide strict data isolation for multi-tenant applications.

#### Creating Memos with Scopes

```javascript theme={null}
await skald.createMemo({
  title: 'Memo title',
  content: 'Scoped content',
  scopes: { organizationId: '123', admin: 'true' }
});
```

#### Querying with Scopes

When using `chat()` or `search()`, provide the same scopes to access scoped content:

```javascript theme={null}
const response = await skald.chat({
  query: 'what was discussed in the meeting on 2026-01-25?',
  scopes: { organizationId: '123', admin: 'true' }
});
```

Only memos that were created with matching scope values will be included in the context.

If you don't provide any scopes, `chat` will query all memos for its context. However, if you pass any scopes, only memos that match exactly the scopes will be included in the context.

You **must** include all relevant scopes in order for them to have effect. Scopes operate on an AND basis.

For example, a memo with scopes: `{ organizationId: '123', admin: 'true' }` will behave as follows:

* If the query includes `{ organizationId: '123', admin: 'true' }`, the memo will be included in the context.
* If the query includes `{ organizationId: '123' }`, the memo **will be included** in the context.
* If the query includes `{ admin: 'true' }`, the memo **will be included** in the context.
* If the query includes `{ organizationId: '123', admin: false }`, the memo **will not be included** in the context.

### Error Handling

```javascript theme={null}
try {
  const result = await skald.createMemo({
    title: 'My Memo',
    content: 'Content here'
  });
  console.log('Success:', result);
} catch (error) {
  console.error('Error:', error.message);
}
```

## TypeScript Support

This package includes TypeScript type definitions out of the box.

```typescript theme={null}
import {
  Skald,
  MemoData,
  CreateMemoResponse,
  MemoFileData,
  CreateMemoFromFileResponse,
  MemoStatus,
  MemoStatusResponse,
  Memo,
  ListMemosResponse,
  UpdateMemoData,
  UpdateMemoResponse,
  IdType,
  Filter,
  FilterOperator,
  FilterType,
  SearchRequest,
  SearchResponse,
  ChatRequest,
  ChatResponse,
  ChatStreamEvent
} from '@skald-labs/skald-node';

const skald = new Skald('your-api-key-here');

// Create a memo with types
const memoData: MemoData = {
  title: 'My Memo',
  content: 'Content here',
  tags: ['tag1', 'tag2'],
  metadata: { department: 'engineering' }
};

const createResponse: CreateMemoResponse = await skald.createMemo(memoData);

// Get memo with types
const memo: Memo = await skald.getMemo('550e8400-e29b-41d4-a716-446655440000');
const memoByRef: Memo = await skald.getMemo('external-id-123', 'reference_id' as IdType);

// List memos with types
const memos: ListMemosResponse = await skald.listMemos({ page: 1, page_size: 20 });

// Update memo with types
const updateData: UpdateMemoData = {
  title: 'Updated Title',
  metadata: { status: 'reviewed' }
};
const updateResponse: UpdateMemoResponse = await skald.updateMemo(
  '550e8400-e29b-41d4-a716-446655440000',
  updateData
);

// Delete memo
await skald.deleteMemo('550e8400-e29b-41d4-a716-446655440000');

// Upload a file
import * as fs from 'fs';
const fileBuffer = fs.readFileSync('./document.pdf');
const fileData: MemoFileData = {
  file: fileBuffer,
  filename: 'document.pdf',
  metadata: { type: 'report' },
  tags: ['document'],
  source: 'local'
};
const uploadResponse: CreateMemoFromFileResponse = await skald.createMemoFromFile(fileData);

// Check processing status
const statusResponse: MemoStatusResponse = await skald.checkMemoStatus(uploadResponse.memo_uuid);
const status: MemoStatus = statusResponse.status; // 'processing' | 'processed' | 'error'

// Search with filters and types
const filters: Filter[] = [
  {
    field: 'source',
    operator: 'eq' as FilterOperator,
    value: 'notion',
    filter_type: 'native_field' as FilterType
  },
  {
    field: 'department',
    operator: 'eq' as FilterOperator,
    value: 'engineering',
    filter_type: 'custom_metadata' as FilterType
  }
];

const searchRequest: SearchRequest = {
  query: 'quarterly goals',
  limit: 10,
  filters
};

const searchResponse: SearchResponse = await skald.search(searchRequest);

// Chat with filters and types
const chatResponse: ChatResponse = await skald.chat({
  query: 'What are our quarterly goals?',
  filters
});

// Streaming chat with types
const stream = skald.streamedChat({
  query: 'What are our quarterly goals?',
  filters
});

for await (const event of stream) {
  const typedEvent: ChatStreamEvent = event;
  if (typedEvent.type === 'token') {
    process.stdout.write(typedEvent.content || '');
  }
}

```
