Skip to main content

Node.js SDK

Official TypeScript/JavaScript SDK for VectorAmp. Dual ESM/CJS, TypeScript-first, with a REST transport today and a transport interface ready for gRPC later.

Install

npm install @vectoramp/vectoramp

From a checkout for local development:

git clone https://github.com/VectorAmp/vectoramp-node.git
cd vectoramp-node
npm ci
npm run build

Configure

import { VectorAmp } from '@vectoramp/vectoramp';

// Reads VECTORAMP_API_KEY from the environment when apiKey is omitted.
const client = new VectorAmp();

// Or configure explicitly.
const configured = new VectorAmp({
apiKey: 'vsk_...',
baseUrl: 'https://api.vectoramp.com' // default; override for dedicated gateways
});

Auth uses the X-API-Key header. Paths are unprefixed against the public gateway (the apiPrefix option defaults to '').

Quickstart

A minimal init, a one-call create, then object → method calls. Only a name is required: the embedding defaults to VectorAmp-Embedding-4B, the dimension is inferred (2560), the metric defaults to cosine, and the index is always SABLE.

import { VectorAmp } from '@vectoramp/vectoramp';

const client = new VectorAmp();

// One call creates a SABLE dataset using the default VectorAmp embedding model.
const dataset = await client.datasets.create({ name: 'product-docs' });

// Object -> method: operate on the returned dataset directly.
await dataset.addTexts([
'VectorAmp stores and searches vectors at scale.',
{ text: "SABLE is VectorAmp's index architecture.", metadata: { source: 'whitepaper' } }
]);

const results = await dataset.search('How does VectorAmp search documents?');
const answer = await dataset.ask('What index does VectorAmp use?');

console.log(results.results);
console.log(answer.answer);
info

Dataset creation always requests SABLE. The SDK intentionally does not expose an index_type option.

Creating datasets

import { openai } from '@vectoramp/vectoramp';

// Minimal: only a name is required.
const docs = await client.datasets.create({ name: 'docs' });

// Custom embeddings infer the dimension automatically for built-in models.
const openaiDataset = await client.datasets.create({ name: 'docs', embedding: openai('large') });

// Custom/unknown models require an explicit dim.
const customDataset = await client.datasets.create({
name: 'docs',
embedding: { provider: 'acme', model: 'acme-embed-1' },
dim: 1024
});

// Hybrid (dense + sparse) datasets:
const hybrid = await client.datasets.create({ name: 'docs', hybrid: true });

Built-in dimension inference: vectoramp/VectorAmp-Embedding-4B → 2560, openai/text-embedding-3-small → 1536, openai/text-embedding-3-large → 3072.

Object and service styles

Both access styles work everywhere. Prefer the object → method style (dataset.search(...)) over the service style (client.datasets.search(id, ...)).

// Object -> method style (preferred)
const page = await client.datasets.list({ limit: 20, offset: 0 });
const dataset = await client.datasets.get(page.data[0].id);
await dataset.search('release notes');
await dataset.delete();

// Service style
await client.datasets.search('dataset_id', { queryText: 'release notes', topK: 10 });
await client.datasets.delete('dataset_id');

List calls return pagination envelopes with data, total, and nextOffset.

Vector ids accept a string or an integer; integers are serialized as JSON numbers and preserved by the API.

await dataset.insert([
{ id: 1, values: [0.12, 0.98, 0.42], metadata: { title: 'Intro' } }, // numeric id stays numeric
{ id: 'vec_2', values: [0.22, 0.18, 0.62] }
]);

addTexts embeds each text with the dataset's model, copies the source text into metadata.text, generates ids when omitted, then inserts.

await dataset.addTexts('Plain text chunk');
await dataset.addTexts([
'Another plain text chunk',
{ id: 'doc_2', text: 'Text with metadata', metadata: { url: 'https://example.com' } }
]);

search accepts a bare string (text query) or a float vector; topK defaults to 10.

await dataset.search('semantic query');   // text query
await dataset.search([0.1, 0.2, 0.3]); // vector query

await dataset.search({
queryText: 'noise cancelling headphones',
topK: 10,
includeMetadata: true,
hybrid: true,
alpha: 0.5,
rerank: true // expands to vectoramp / VectorAmp-Rerank-v1
});
tip

rerank: true (or rerank: { enabled: true }) enables the optional model reranker after initial retrieval. topN, maxDocuments, and failOpen are optional and use server defaults. The separate rerankDepthOverride only tunes SABLE's vector-index candidate depth and is not the model reranker.

Organization secrets and vector deletion

Store external provider credentials as organization secrets, then reference the secret from datasets. The default OpenAI BYOM reference is emb:openai:api_key.

// One-step convenience: saves/updates the OpenAI org secret, then creates
// the dataset with embedding.secret_ref = "emb:openai:api_key".
const dataset = await client.datasets.create({
name: 'openai-docs',
openaiApiKey: process.env.OPENAI_API_KEY
});

// Or manage the secret explicitly.
await client.orgSecrets.putOpenAIApiKey(process.env.OPENAI_API_KEY!);

// Delete individual vector ids from search.
await dataset.deleteVectors(['doc-001', 42], { writeConcern: 'quorum' });
await client.datasets.deleteVectors(dataset.id, ['doc-002']);

Ingestion

Source builders keep payloads typed while matching the REST source_type contract (web, s3, gcs, gdrive, file_upload, jira, confluence).

import { webSource, s3Source, confluenceSource } from '@vectoramp/vectoramp';

// One-liner: the SDK creates the source, then starts the ingestion job.
await dataset.ingestSource(webSource('https://docs.example.com'));
await dataset.ingestSource(s3Source('s3://my-bucket/docs/'));
await dataset.ingestSource(confluenceSource({ baseUrl: 'https://acme.atlassian.net', username: 'me', config: { apiToken: '…' } }));

// Existing source ids start a job directly.
await dataset.ingestSource('source_id');

// Optional pipeline override.
await dataset.ingestSource(webSource('https://docs.example.com'), { pipelineId: 'pl_1' });

Create reusable sources through client.sources (an alias of client.ingestion):

const web = await client.sources.createWeb('https://docs.example.com');
const s3 = await client.sources.createS3('s3://my-bucket/docs/');
const drive = await client.sources.createGoogleDrive({ folderId: 'google-drive-folder-id' });
const confluence = await client.sources.createConfluence({ cloudId: 'cloud-id', accessToken: 'token', spaceKeys: ['DOCS'] });

await dataset.ingestSource(web.id!);

Use genericSource(sourceType, payload) for source types not yet modeled by the SDK.

Manage and clean up sources

Validate a config, delete a source, or find and remove sources nothing is using.

// Validate without persisting.
await client.sources.validate('web', { start_urls: ['https://docs.example.com'] });

// See what's using a source (active schedules + in-flight jobs).
const refs = await client.sources.getReferences('source_id'); // { schedules, scheduleCount, activeJobCount, inUse }

// Delete. Throws 409 if in use unless force: true (force also disables its schedules).
await client.sources.delete('source_id');
await client.sources.delete('source_id', { force: true });

// Find unused sources, or remove all of them in one call.
const unused = await client.sources.listUnused();
const result = await client.sources.cleanupUnused(); // { deleted: [...], count }

Authenticated sources & connections

Authenticate a gdrive/gcs/confluence/jira source with a headless service account, or a reusable connection granted once in the browser.

// Option 1 — service account (headless, no browser).
await client.sources.createGoogleDrive({
folderId: 'google-drive-folder-id',
serviceAccountJson: { type: 'service_account', client_email: 'ingest@proj.iam.gserviceaccount.com', private_key: '…' }
});

// Option 2 — connection (interactive once, reused server-side).
// connect() creates the grant, opens/prints authorization_url, and polls until connected.
const conn = await client.connections.connect({ provider: 'google', sourceType: 'gdrive' });
await client.sources.createGoogleDrive({ folderId: 'google-drive-folder-id', connectionId: conn.id });

Drive the grant flow yourself when you need to:

const conn = await client.connections.create({ provider: 'google', sourceType: 'gdrive' }); // { id, status: 'pending', authorizationUrl }
console.log('Open in a browser:', conn.authorizationUrl);
await client.connections.get(conn.id); // poll until status === 'connected'
await client.connections.list({ provider: 'google' });
await client.connections.delete(conn.id);

Upload local files

ingestFiles hides the full presigned upload flow: it auto-creates a file_upload source, initializes presigned uploads, PUTs each file, and completes the upload.

await dataset.ingestFiles(['./docs/intro.md', './docs/guide.pdf']);

// Walk a directory and upload matching text files.
await dataset.ingestFilesystem('./docs', {
extensions: ['.md', '.txt'],
maxBytesPerFile: 512_000
});

Ingestion jobs

const job = await client.ingestion.startJob({ sourceId: 'src_1', datasetId: dataset.id });
const jobs = await client.ingestion.listJobs({ datasetId: dataset.id });
const detail = await client.ingestion.getJob(job.id!);
await client.ingestion.retryJob(job.id!);

Dataset documents

List retained source documents and download originals when download_available is true. Pagination is cursor-based; keep passing nextCursor until it is empty.

import { writeFile } from 'node:fs/promises';

let cursor: string | undefined;
do {
const page = await client.datasets.listDocuments('dataset_id', { limit: 50, cursor, status: 'ready' });
for (const document of page.data) {
if (!document.download_available) continue;
const bytes = await client.datasets.downloadDocument('dataset_id', document.id);
await writeFile(document.file_name ?? document.id, Buffer.from(bytes));
}
cursor = page.nextCursor;
} while (cursor);

// Object style
await dataset.listDocuments({ limit: 50 });
await dataset.downloadDocument('document_id');

The SDK follows download redirects and returns an ArrayBuffer. Storage buckets, object keys, and payload references are never exposed.

Ask / RAG

ask defaults topK: 5, includes sources, and queries all accessible datasets when unscoped.

const answer = await dataset.ask('What changed in the Q4 planning docs?');
console.log(answer.answer, answer.sources);

// Unscoped questions query across all accessible datasets.
const acrossAll = await client.ask('Summarize everything about onboarding.');

// Streaming SSE.
for await (const event of client.askStream({ query: 'Summarize this dataset', datasetId: dataset.id })) {
if (event.event === 'done') break;
console.log(event.data);
}

Multi-turn conversations

The Intelligence API is stateless. To ask a follow-up, send the prior turns as conversation history; you decide how many previous messages to include.

const history = [
{ role: 'user', content: 'What are the key features?' },
{ role: 'assistant', content: 'Hybrid search, reranking, and managed ingestion.' }
];
const answer = await client.ask({
query: 'Which of those help with relevance?',
conversationHistory: history
});

Persistent Intelligence sessions

const session = await client.intelligence.createSession({ title: 'Q4 planning', datasetId: dataset.id });
await client.intelligence.appendMessage(session.id, { role: 'user', content: 'Summarize the latest planning docs' });
const messages = await client.intelligence.listMessages(session.id, { limit: 100 });
const sessions = await client.intelligence.listSessions({ limit: 50 });

Intelligence answers return sources[] and chunks[]. Inline [1] citations refer to sources[0]; previewRef is an opaque preview token, never a raw storage key.

Schedules

const schedule = await client.schedules.create({
sourceId: 'src_1',
datasetId: dataset.id,
cron: '0 2 * * *',
timezone: 'UTC'
});
await client.schedules.trigger(schedule.id!);

Method reference

Both client.datasets.X(id, …) and datasetObj.X(…) work for dataset-scoped methods.

client.datasets

MethodRequiredOptionalHTTP
list(params?)limit, offsetGET /datasets
get(id)idGET /datasets/{id}
create(request)namedim, embedding, metric, hybrid, metadataPOST /datasets
delete(id) / ds.delete()idDELETE /datasets/{id}
stats(id) / ds.stats()idGET /datasets/{id}/stats
listDocuments(id, params?) / ds.listDocuments(params?)idlimit, cursor, statusGET /datasets/{id}/documents
downloadDocument(id, docId) / ds.downloadDocument(docId)id, docIdGET …/documents/{docId}/download
search(id, query) / ds.search(query)id, query (string, vector, or options)topK (10), filters, hybrid, sparseQuery, alpha, rerank, includeDocuments, includeMetadataPOST …/search
insert(id, vectors) / ds.insert(vectors)id, vectors— (string or numeric id)POST …/insert
addTexts(id, texts) / ds.addTexts(texts)id, textsper-record id/metadataembed + POST …/insert
embed(id, …) / ds.embed(…)idtext/textsPOST …/embed
ingestSource(id, source, options?) / ds.ingestSource(source, options?)id, sourcepipelineIdcreate-source? + POST /ingestion/jobs
ingestFiles(id, paths, options?) / ds.ingestFiles(paths, options?)id, pathssourceName, description, metadatapresigned upload flow
ingestFilesystem(id, root, options?) / ds.ingestFilesystem(root, options?)id, rootextensions, maxBytesPerFile, sourceName, metadatapresigned upload flow
ask(id, request) / ds.ask(query, options?)id, querytopK (5), conversationHistory, includeSourcesPOST /intelligence/query

client.intelligence (and client.ask / client.askStream)

MethodRequiredOptionalHTTP
ask(request)querydatasetId ("all"), topK (5), conversationHistory, includeSourcesPOST /intelligence/query
askStream(request)querysame as askPOST /intelligence/query (SSE)
createSession(input?)title, workspaceId, datasetId, metadataPOST /intelligence/sessions
listSessions(params?)limitGET /intelligence/sessions
getSession(id)idGET /intelligence/sessions/{id}
deleteSession(id)idDELETE /intelligence/sessions/{id}
appendMessage(sessionId, input)sessionId, role, contentmetadataPOST …/messages
listMessages(sessionId, params?)sessionIdlimitGET …/messages

client.sources / client.ingestion

MethodRequiredOptionalHTTP
create(request)source_typesource configPOST /ingestion/sources
createWeb/S3/Gcs/GoogleDrive/Jira/Confluence/FileUpload(input)per helperper helperPOST /ingestion/sources
list(params?)limit, offsetGET /ingestion/sources
get(sourceId)sourceIdGET /ingestion/sources/{id}
validate(sourceType, config)sourceType, configPOST /ingestion/sources/validate
getReferences(sourceId)sourceIdGET /ingestion/sources/{id}/references
delete(sourceId, options?)sourceIdforceDELETE /ingestion/sources/{id}
listUnused()GET /ingestion/sources/unused
cleanupUnused()POST /ingestion/sources/cleanup
startJob(request)sourceId, datasetIdpipelineIdPOST /ingestion/jobs
listJobs(params?)datasetId, limit, offsetGET /ingestion/jobs
getJob(jobId)jobIdGET /ingestion/jobs/{id}
retryJob(jobId)jobIdPOST /ingestion/jobs/{id}/retry

client.connections

MethodRequiredOptionalHTTP
list(params?)provider (google|atlassian)GET /connections
create(request)providersourceTypePOST /connections
get(id)idGET /connections/{id}
delete(id)idDELETE /connections/{id}
connect(request)providersourceTypecreate + open authorizationUrl + poll get until connected

client.schedules

MethodRequiredOptionalHTTP
list(params?)limit, offsetGET /ingestion/schedules
get(id)idGET /ingestion/schedules/{id}
create(request)sourceId, datasetId, crontimezone, pipelineId, enabled, name, metadataPOST /ingestion/schedules
update(id, updates)idall create fieldsPATCH /ingestion/schedules/{id}
delete(id)idDELETE /ingestion/schedules/{id}
trigger(id)idPOST /ingestion/schedules/{id}/trigger

Source helpers

webSource, s3Source, gcsSource, googleDriveSource, jiraSource, confluenceSource, fileUploadSource, and genericSource(sourceType, payload). Embedding helper: openai('small' | 'large').