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.

Intelligence​

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)id—GET /datasets/{id}
create(request)namedim, embedding, metric, hybrid, metadataPOST /datasets
delete(id) / ds.delete()id—DELETE /datasets/{id}
stats(id) / ds.stats()id—GET /datasets/{id}/stats
listDocuments(id, params?) / ds.listDocuments(params?)idlimit, cursor, statusGET /datasets/{id}/documents
downloadDocument(id, docId) / ds.downloadDocument(docId)id, docId—GET …/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)id—GET /intelligence/sessions/{id}
deleteSession(id)id—DELETE /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)sourceId—GET /ingestion/sources/{id}
validate(sourceType, config)sourceType, config—POST /ingestion/sources/validate
getReferences(sourceId)sourceId—GET /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)jobId—GET /ingestion/jobs/{id}
retryJob(jobId)jobId—POST /ingestion/jobs/{id}/retry

client.connections​

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

client.schedules​

MethodRequiredOptionalHTTP
list(params?)—limit, offsetGET /ingestion/schedules
get(id)id—GET /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)id—DELETE /ingestion/schedules/{id}
trigger(id)id—POST /ingestion/schedules/{id}/trigger

Source helpers​

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