Chanl

Knowledge Base

Ingest documents, search with hybrid queries, and manage knowledge programmatically

The Knowledge Base module gives your agents grounded, accurate answers from your actual documentation. Ingest PDFs, web pages, and text entries, then search with hybrid vector+text queries. Chanl handles chunking, embedding, and retrieval so you do not have to build a RAG pipeline from scratch.

Installation

npm install @chanl/sdk

Setup

import { Chanl } from '@chanl/sdk';

const chanl = new Chanl({
  apiKey: process.env.CHANL_API_KEY,
});

Create an entry

There are three source types. Text entries process synchronously (immediate). URL and file entries process asynchronously (return a task ID you can poll).

Text entry (sync)

const entry = await chanl.knowledge.create({
  title: 'Return Policy',
  source: 'text',
  content: `
    Our return policy allows returns within 30 days of purchase.
    Items must be unused and in original packaging.
    Refunds are processed within 5-7 business days.
  `,
  metadata: { category: 'policies', tags: ['returns', 'refunds'] },
});

console.log('Created:', entry.id);
console.log('Status:', entry.processingStatus); // "completed"

URL entry (async)

const result = await chanl.knowledge.create({
  title: 'Help Center Article',
  source: 'url',
  url: 'https://help.example.com/getting-started',
  chunkingOptions: { chunkSizeTokens: 512, overlapTokens: 50 },
});

console.log('Document ID:', result.id);
console.log('Task ID:', result.taskId); // Poll this for status

File upload (async)

import fs from 'fs';

const result = await chanl.knowledge.create({
  title: 'Product Manual v2',
  source: 'file',
  file: fs.readFileSync('./product-manual.pdf'),
  filename: 'product-manual.pdf',
});

// Poll for completion
let status = await chanl.knowledge.getTask(result.taskId);
while (status.status === 'processing') {
  await new Promise((r) => setTimeout(r, 2000));
  status = await chanl.knowledge.getTask(result.taskId);
}
console.log('Completed:', status.documentId);

Parameters:

ParameterTypeRequiredDescription
titlestringYesEntry title
sourcestringYestext, url, or file
contentstringFor text sourceDocument content
urlstringFor url sourceWeb page URL
fileBufferFor file sourceFile contents
filenamestringFor file sourceFile name
metadataobjectNoCustom metadata
tagsstring[]NoTags for filtering
folderIdstringNoFolder to place the entry in
chunkingOptionsobjectNoCustom chunking settings

Returns: Promise<KnowledgeEntry> (text) or Promise<{ id: string; taskId: string }> (url/file)

Query your knowledge base with hybrid (vector + text), pure vector, or pure text search.

Combines semantic understanding with keyword matching. Best general-purpose option.

const results = await chanl.knowledge.search({
  query: 'What is your return policy for electronics?',
  mode: 'hybrid',
  limit: 5,
  minScore: 0.7,
});

results.forEach((result) => {
  console.log(`[${result.score.toFixed(2)}] ${result.title}`);
  console.log(`  ${result.content.substring(0, 100)}...`);
});

Pure semantic search for conceptual queries where exact keywords may not match.

const results = await chanl.knowledge.search({
  query: 'Can I get my money back?',
  mode: 'vector',
  limit: 3,
});

Exact keyword matching for specific identifiers or technical terms.

const results = await chanl.knowledge.search({
  query: 'SKU-12345 warranty',
  mode: 'text',
  limit: 5,
});

Search with filters

const results = await chanl.knowledge.search({
  query: 'shipping options',
  mode: 'hybrid',
  limit: 10,
  filters: {
    tags: ['shipping'],
    source: 'faq',
    folderId: 'folder_shipping',
  },
});

Parameters:

ParameterTypeRequiredDescription
querystringYesSearch query
modestringNohybrid (default), vector, or text
limitnumberNoMax results to return
minScorenumberNoMinimum relevance score (0-1)
filtersobjectNoFilter by tags, source, or folder

Returns: Promise<KnowledgeSearchResult[]>

List and filter entries

// List all entries
const { entries, pagination } = await chanl.knowledge.list();

// With filters
const { entries } = await chanl.knowledge.list({
  source: 'document',
  tags: ['policy'],
  folderId: 'folder_policies',
  page: 1,
  limit: 20,
});

// Search by title
const { entries } = await chanl.knowledge.list({
  search: 'return policy',
});

entries.forEach((entry) => {
  console.log(`${entry.title} (${entry.source})`);
  console.log(`  Tags: ${entry.tags?.join(', ')}`);
});

Get, update, and delete

// Get entry details
const entry = await chanl.knowledge.get('kb_abc123');
console.log('Title:', entry.title);
console.log('Chunks:', entry.chunkCount);

// Update entry metadata
await chanl.knowledge.update('kb_abc123', {
  title: 'Updated Return Policy',
  tags: ['policy', 'returns', '2026'],
  metadata: { version: '2.0', lastReviewed: new Date().toISOString() },
});

// Delete entry
await chanl.knowledge.delete('kb_abc123');

// Delete with associated files
await chanl.knowledge.delete('kb_abc123', { deleteFiles: true });

Folders

Organize entries into folders and subfolders.

// Create folder
const folder = await chanl.knowledge.createFolder({
  name: 'Policies',
  description: 'Company policies and procedures',
  parentId: null, // root level
});

// Create nested folder
const subfolder = await chanl.knowledge.createFolder({
  name: 'Return Policies',
  parentId: folder.id,
});

// List folders
const { folders } = await chanl.knowledge.listFolders();
folders.forEach((f) => {
  console.log(`${f.name} (${f.entryCount} entries)`);
});

// Move entry to folder
await chanl.knowledge.move('kb_abc123', { folderId: folder.id });

// Delete folder (moves contents to root)
await chanl.knowledge.deleteFolder('folder_abc123', {
  moveContentsToRoot: true,
});

URL sync

Keep URL-sourced entries up to date automatically.

// Enable auto-sync
await chanl.knowledge.updateSyncConfig('kb_abc123', {
  enabled: true,
  frequency: 'daily', // 'manual' | 'daily' | 'weekly' | 'monthly'
});

// Trigger manual sync
const task = await chanl.knowledge.sync('kb_abc123');
console.log('Sync started:', task.taskId);

// Check sync config
const config = await chanl.knowledge.getSyncConfig('kb_abc123');
console.log('Next Sync:', config.nextSyncAt);

Processing tasks

Monitor the status of async operations (URL ingestion, file uploads, syncs).

// Get task status
const task = await chanl.knowledge.getTask('task_abc123');
console.log('Status:', task.status); // pending, processing, completed, failed
console.log('Progress:', task.progress);

if (task.status === 'failed') {
  console.log('Error:', task.error);
}

// List active tasks
const { tasks } = await chanl.knowledge.listTasks({
  status: 'processing',
});

Statistics and configuration

// Get usage stats
const stats = await chanl.knowledge.getStats();
console.log('Total Entries:', stats.totalEntries);
console.log('Total Chunks:', stats.totalChunks);
console.log('Storage Used:', stats.storageBytes);

// Get config limits
const config = await chanl.knowledge.getConfig();
console.log('Max Documents:', config.maxDocuments);
console.log('Max File Size:', config.maxFileSize);

// Update config
await chanl.knowledge.updateConfig({
  searchDefaults: { mode: 'hybrid', limit: 10, minScore: 0.6 },
  chunkingConfig: { chunkSize: 1200, chunkOverlap: 250 },
});

Batch operations

Bulk upload

import fs from 'fs';
import path from 'path';

const files = [
  { path: './docs/manual.pdf', title: 'Product Manual' },
  { path: './docs/faq.md', title: 'FAQ' },
  { path: './docs/policy.txt', title: 'Policy' },
];

const tasks = await Promise.all(
  files.map((file) =>
    chanl.knowledge.uploadFile({
      file: fs.readFileSync(file.path),
      filename: path.basename(file.path),
      title: file.title,
      tags: ['batch-upload'],
    })
  )
);

console.log('Queued:', tasks.length, 'documents');

Bulk delete

const { entries } = await chanl.knowledge.list({
  tags: ['old-version'],
});

await Promise.all(
  entries.map((entry) => chanl.knowledge.delete(entry.id))
);

Full example

Build a customer support knowledge retrieval helper.

import { Chanl } from '@chanl/sdk';

const chanl = new Chanl({
  apiKey: process.env.CHANL_API_KEY,
});

class SupportKB {
  constructor(private client: Chanl) {}

  async findAnswer(question: string): Promise<string | null> {
    const results = await this.client.knowledge.search({
      query: question,
      mode: 'hybrid',
      limit: 3,
      minScore: 0.75,
    });

    return results.length > 0 ? results[0].content : null;
  }

  async addFAQ(question: string, answer: string) {
    return this.client.knowledge.create({
      title: question,
      source: 'text',
      content: answer,
      tags: ['faq', 'customer-support'],
    });
  }

  async syncHelpCenter(urls: string[]) {
    return Promise.all(
      urls.map((url) =>
        this.client.knowledge.upload({
          type: 'url',
          url,
          source: 'webpage',
          tags: ['help-center'],
          syncConfig: { enabled: true, frequency: 'daily' },
        })
      )
    );
  }

  async getHealth() {
    const stats = await this.client.knowledge.getStats();
    const config = await this.client.knowledge.getConfig();

    return {
      documentsUsed: stats.totalEntries,
      documentsLimit: config.maxDocuments,
      utilizationPercent: (stats.totalEntries / config.maxDocuments) * 100,
      storageUsed: stats.storageBytes,
      chunksIndexed: stats.totalChunks,
    };
  }
}

// Usage
const kb = new SupportKB(chanl);

const answer = await kb.findAnswer('How do I track my order?');
if (answer) {
  console.log('Found answer:', answer);
} else {
  console.log('No relevant answer found');
}

Types

import type {
  KnowledgeEntry,
  KnowledgeSource,
  KnowledgeFolder,
  KnowledgeTask,
  KnowledgeSearchResult,
  KnowledgeStats,
  KnowledgeConfig,
  CreateKnowledgeRequest,
  UpdateKnowledgeRequest,
  SearchKnowledgeRequest,
  UploadKnowledgeRequest,
  SyncConfig,
} from '@chanl/sdk';

Next steps

On this page