SDK
Explanation — what & why
The Vectros SDK is a typed client library for the platform's REST API. Rather
than hand-rolling HTTP requests, signing headers, and decoding paginated
envelopes, you construct one client and call typed methods grouped by resource:
client.records.createRecord(...), client.search.content(...),
client.inference.ragInference(...), and so on.
The SDK is generated from the API specification, not written by hand. The spec is the single source of truth: the same definition drives the API gateway, the rendered API reference, and the SDK. That is why the method names, request shapes, and response types in your editor match the API exactly — they are the same artifact, expressed in TypeScript.
The SDK does the mechanical work you would otherwise repeat in every project:
- Typed requests and responses — your editor autocompletes fields and flags mistakes before you run anything.
- Streaming as async iteration — chat, RAG, and document-ask endpoints are
server-sent-event streams; the SDK exposes them as async iterables you can
for awaitover. - A uniform list envelope — list, lookup, and version endpoints return a cursor-paginated envelope (see below) so draining a full result set is one loop, the same every time.
Language coverage — state it honestly
The SDK is generated for Node, Java, and Python, and all three are now
exercised end-to-end against staging at parity — each ships a smoke suite
(smoke-tests/{,sdk-python/,sdk-java/}) covering the same MVP surface (auth,
the list envelope, the error contract, chat/RAG streaming, and cross-context
isolation) against the live API, so the call shapes that back this documentation
are verified in every language. The snippets here use Node idioms; the Java and
Python equivalents follow the same resource grouping and are validated against
the same wire contract. See the generated API reference for each language's
exact idioms.
The three clients are generated per language, not hand-aligned — so the client class and its constructor differ, and methods follow each language's casing. Don't assume the Node shape transfers verbatim:
| Language | Install | Construct |
|---|---|---|
| Node | npm install @vectros-ai/sdk | new VectrosClient({ token, environment }) |
| Python | pip install vectros | VectrosApi(base_url=..., token=...) |
| Java | ai.vectros:vectros-sdk (Maven Central) | VectrosApiClient.builder().token(...).url(...).build() |
Method names mirror the API operations in each language's idiom — Node and Java
camelCase (createRecord), Python snake_case (create_record). Runnable
Python and Java equivalents of the Node snippets below are in
Python and Java.
The list envelope (cursor pagination)
List, lookup, and version endpoints return a uniform envelope:
{ data: T[], nextCursor: string | null }
To drain every page, feed nextCursor back as startFrom until it comes back
null:
const ids: string[] = [];
let cursor: string | null | undefined;
do {
const page = await client.schemas.listSchemas(
cursor ? { startFrom: cursor, limit: 100 } : { limit: 100 },
);
ids.push(...(page.data ?? []).map((s) => s.id!));
cursor = page.nextCursor;
} while (cursor);
Two endpoints are not enveloped and do not paginate this way:
client.search.content(...)returns a results object directly (its hits are onresults), withlimit/offsetfor paging.getUsagereturns its usage payload directly.
How-to
Install
npm install @vectros-ai/sdk
Construct a client
The client takes a token and a target environment (the API base URL):
import { VectrosClient } from '@vectros-ai/sdk';
const client = new VectrosClient({
token: process.env.VECTROS_API_KEY!, // sk_*, ssk_*, or st_*
environment: 'https://api.vectros.ai', // staging: https://api.staging.vectros.ai
});
token accepts any of the three credential types — a root key (sk_*), a
scoped permanent key (ssk_*), or a short-lived scoped token (st_*). The
client makes no distinction; the platform enforces each credential's reach.
Sub-clients are grouped by resource tag:
| Sub-client | Covers |
|---|---|
client.records | structured records |
client.schemas | record/surface schemas |
client.documents | documents (ingest, upload, retrieve) |
client.folders | folder hierarchy |
client.search | unified search |
client.inference | chat, RAG, document-ask, model catalog |
client.identity | users, identity entities (namespaced — org/client built in, plus any you register), namespaces |
client.auth | ping, scoped-token mint, and related auth calls |
First call — define a schema, write a record, read it back
// 1. Define a record type. HYBRID indexing = keyword + semantic.
const schema = await client.schemas.createSchema({
body: {
typeName: 'patient',
displayName: 'Patient Record',
indexMode: 'HYBRID',
allowedSurfaces: ['record'],
fields: [
{ fieldId: 'name', fieldType: 'string', required: true, searchable: true },
{ fieldId: 'notes', fieldType: 'string', searchable: true },
{ fieldId: 'department', fieldType: 'string', filterable: true },
{ fieldId: 'email', fieldType: 'string', required: true },
],
lookupFields: [{ fieldName: 'email', unique: true }],
capabilities: { auditHistory: true },
},
});
// 2. Write a record. (Synthetic, clearly-fictional data only.)
const record = await client.records.createRecord({
body: {
typeName: 'patient',
schemaId: schema.id!,
payload: {
name: 'Jane Doe',
notes: 'presents with hypertension and complains of chest pain',
department: 'cardiology',
email: 'jane.doe@example.test',
},
},
});
// 3. Read it back.
const loaded = await client.records.getRecord({ id: record.id! });
Expected result: createRecord returns immediately with
indexStatus: 'PENDING_INDEX'; indexing is asynchronous. getRecord returns
the stored record, and once indexing completes indexStatus becomes
'INDEXED'.
Search across records and documents
searchContent is unified — it returns both record and document hits unless you
narrow with contentTypes. It is not enveloped; read results.
const results = await client.search.content({
query: 'hypertension chest pain',
mode: 'HYBRID', // TEXT | SEMANTIC | HYBRID
limit: 50,
});
for (const hit of results.results ?? []) {
console.log(hit.documentId, hit.semanticScore, hit.textScore);
}
Stream a RAG answer
Streaming endpoints are async iterables. Each yielded event has an event
discriminator (search_results, content_delta, done, and so on):
const stream = await client.inference.ragInference({
query: 'What treatment is recommended for stage 1 hypertension?',
search: { mode: 'HYBRID', limit: 5 },
maxTokens: 256,
});
let answer = '';
for await (const ev of stream) {
if (ev.event === 'content_delta') answer += ev.delta ?? '';
if (ev.event === 'done') console.log('charged:', ev.inferenceBalanceCentsCharged);
}
Plain chat (client.inference.chatInference) and document-scoped ask
(client.inference.documentAsk) follow the same async-iterable pattern.
Drain a paginated list
See the envelope loop in the Explanation section above — that is the canonical
drain pattern for any list*, lookup*, or get*Versions call.
Update strategies — full replace vs. partial patch (SDK 0.26+)
The default update is a full replace (PUT-style): you send the complete desired body.
await client.records.updateRecord({
id: record.id!,
body: {
typeName: 'patient',
schemaId: schema.id!,
payload: { name: 'Jane Doe', notes: 'updated note', department: 'cardiology', email: 'jane.doe@example.test' },
},
});
A partial patch (RFC-7386 merge-patch) is available on records, documents,
and folders with SDK 0.26+: send only the fields you want to change; omitted
fields are left untouched, and an explicit null deletes a field.
// (SDK 0.26+) — patch only the notes field.
await client.records.patchRecord({
id: record.id!,
body: { payload: { notes: 'patched note' } },
});
Create a record by type name or by schema id (SDK 0.26+)
With SDK 0.26+, createRecord accepts either typeName or
schemaId (you no longer need both) — supply whichever you have on hand.
Earlier SDK pins expect both; the first-call example above passes both, which
works on every version.
Python and Java
The snippets above are Node. The Python (vectros on PyPI) and Java
(ai.vectros:vectros-sdk on Maven Central) SDKs are generated from the same
spec and cover the same surface; the install, client construction, and method
casing differ per language. Each block below is the cross-language twin of the
Construct a client + first call (define a schema, write a record, read it
back) above.
Python
pip install vectros
import os
import vectros
from vectros import VectrosApi
# Note: the keyword is `base_url` in Python — NOT Node's `environment`.
client = VectrosApi(
base_url="https://api.vectros.ai", # staging: https://api.staging.vectros.ai
token=os.environ["VECTROS_API_KEY"], # sk_*, ssk_*, or st_*
)
# 1. Define a record type. HYBRID indexing = keyword + semantic.
schema = client.schemas.create_schema(
type_name="patient",
display_name="Patient Record",
index_mode="HYBRID", # HYBRID | SEMANTIC | TEXT
allowed_surfaces=["record"],
fields=[
vectros.FieldDef(field_id="name", field_type="string", required=True, searchable=True),
vectros.FieldDef(field_id="email", field_type="string", required=True),
],
lookup_fields=[vectros.LookupDef(field_name="email", unique=True)],
)
# 2. Write a record. (Synthetic, clearly-fictional data only.)
record = client.records.create_record(
type_name="patient",
schema_id=schema.id,
payload={"name": "Jane Doe", "email": "jane.doe@example.test"},
)
# 3. Read it back.
loaded = client.records.get_record(record.id)
Streaming inference is an iterator of discriminated events (event =
content_delta, done, ...), the same shape as Node:
stream = client.inference.chat_inference(
messages=[vectros.ChatMessage(role="user", content="Say hello.")],
max_tokens=64,
)
answer = "".join(ev.delta or "" for ev in stream if ev.event == "content_delta")
Sub-clients (client.records, client.schemas, client.documents,
client.folders, client.search, client.inference, client.identity,
client.auth) and the list envelope (page.data / page.next_cursor) match
the Node grouping. API errors raise vectros.core.api_error.ApiError
(err.status_code carries the HTTP status).
Java
Maven:
<dependency>
<groupId>ai.vectros</groupId>
<artifactId>vectros-sdk</artifactId>
<version>0.38.0</version>
</dependency>
Gradle:
implementation 'ai.vectros:vectros-sdk:0.38.0'
(The version above is the current published release; confirm the latest on Maven Central.)
import ai.vectros.VectrosApiClient;
import ai.vectros.types.FieldDef;
import ai.vectros.types.FieldDefFieldType;
import ai.vectros.types.LookupDef;
import ai.vectros.types.RecordRequest;
import ai.vectros.types.SchemaRequest;
import ai.vectros.types.SchemaRequestAllowedSurfacesItem;
import ai.vectros.types.SchemaRequestIndexMode;
import java.util.List;
import java.util.Map;
VectrosApiClient client = VectrosApiClient.builder()
.token(System.getenv("VECTROS_API_KEY")) // sk_*, ssk_*, or st_*
.url("https://api.vectros.ai") // staging: https://api.staging.vectros.ai
.build();
// 1. Define a record type. HYBRID indexing = keyword + semantic.
var schema = client.schemas().createSchema(SchemaRequest.builder()
.typeName("patient")
.displayName("Patient Record")
.indexMode(SchemaRequestIndexMode.HYBRID) // HYBRID | SEMANTIC | TEXT
.allowedSurfaces(List.of(SchemaRequestAllowedSurfacesItem.RECORD))
.fields(List.of(
FieldDef.builder().fieldId("name").fieldType(FieldDefFieldType.STRING).required(true).searchable(true).build(),
FieldDef.builder().fieldId("email").fieldType(FieldDefFieldType.STRING).required(true).build()))
.lookupFields(List.of(LookupDef.builder().fieldName("email").unique(true).build()))
.build());
// 2. Write a record. (Synthetic, clearly-fictional data only.)
var record = client.records().createRecord(RecordRequest.builder()
.typeName("patient")
.schemaId(schema.getId().orElseThrow())
.payload(Map.of("name", "Jane Doe", "email", "jane.doe@example.test"))
.build());
// 3. Read it back.
var loaded = client.records().getRecord(record.getId().orElseThrow());
Java request DTOs live under ai.vectros.types.*; per-endpoint request wrappers
(e.g. ChatRequest, ListRecordsRequest) under
ai.vectros.resources.<area>.requests.*. Sub-clients are accessor methods
(client.records(), client.inference(), ...), responses use Optional
getters (record.getId().orElseThrow()), and streaming is an Iterable of
discriminated events (event.isContentDelta() / event.getDone()). API errors
throw ai.vectros.core.VectrosApiApiException (ex.statusCode()).
Reference
Client construction
| Option | Type | Notes |
|---|---|---|
token | string | sk_*, ssk_*, or st_*. Required. |
environment | string | API base URL, e.g. https://api.vectros.ai (production) or https://api.staging.vectros.ai (staging). Required. |
Sub-clients (by tag)
records, schemas, documents, folders, search, inference,
identity, auth. Method names mirror the API operations
(createRecord, getRecord, updateRecord, patchRecord, deleteRecord,
listRecords, lookupRecords, getRecordVersions, getRecordTombstone,
createSchema, ingestDocument, uploadDocument, getDocument,
createFolder, chatInference, ragInference, documentAsk,
listInferenceModels, createUser, createEntity/listEntities/lookupEntities
(namespace-parameterized — org/client are two built-in namespaces among any
you register, see identity-access/reference.md),
registerNamespace, ping, and so
on). The complete, exhaustive list of methods, parameters, and field types is in
the generated API reference — link to it by name rather than recreating it here.
Envelope shape
| Field | Type | Meaning |
|---|---|---|
data | T[] | The page of items. |
nextCursor | string | null | Pass back as startFrom for the next page; null means no more pages. |
Applies to list*, lookup*, and get*Versions. Does not apply to
search.content or getUsage.
On list* and lookup*, treat nextCursor as an opaque, encrypted token,
not a value to construct: pass back exactly what the previous page returned,
never a hand-built or reused startFrom — the API rejects one it did not issue
with a 400. A cursor is valid only for the exact query it was issued for.
get*Versions shares the same envelope but resumes from a plain row id
rather than a sealed cursor — echo nextCursor back the same way, but it is
not encrypted, and (unlike list*/lookup*) a caller-supplied id is the
expected shape there rather than a rejected one. On every one of these, a page
filled to limit is not itself a signal to keep going — the only
continue-or-stop signal is whether nextCursor came back null.
Index modes
HYBRID (keyword + semantic, the general-purpose default), SEMANTIC
(meaning-based only), TEXT (keyword only). Declared per schema (records) or
per document at ingest.
Concurrency
Record and document updates accept an optional expectedVersion in the body for
optimistic concurrency; a mismatch is rejected with HTTP 409. Omit it for
last-writer-wins.
Version reality (read before pinning)
- The API spec / SDK is at version 0.38.0 (this figure is sourced from the live release at build time, so it always reflects the current version). The generated Node SDK is a continuous-integration artifact built from that spec — it is not committed to this repository, so you install it from the package registry.
- Among the in-repo consumers — the CLI, MCP server, and the reference web apps
(admin-app, app-vectros-ai) — all currently pin the same SDK release; the
React toolkit's dev/test pin tracks it too, though the toolkit itself (an auth/UI
library, not a data client) accepts any SDK
>=0.9.0via its peer dependency. These pins are not guaranteed to move together release-to-release — a consumer can be repinned independently of the others — so if you depend on a specific consumer's bundled SDK, check that consumer's ownpackage.jsonfor its exact pin rather than trusting a snapshot figure in this doc. - 0.26+ surface — these require SDK 0.26 or newer and are absent on older pins:
patchRecord/patchDocument/patchFolder(RFC-7386 merge-patch).createRecordwithtypeNameorschemaId(the either-or form). Passing both works on all versions.- All in-repo consumers (CLI, MCP server, and the reference web apps) are past 0.26, so these are reachable everywhere today, including through the reference apps' own UI.
- 0.27 added an optional lower-cost global-region path for inference
(
allowGlobalRegionon chat / RAG / document-ask) for tenants entitled to it; see Search and RAG reference. - 0.37 added schema lineage (
basedOn— a schema customizing another schema sharing itstypeName; see data model reference),specificityRankon namespaces,contextId-targeted token minting (root-key-only),userId/scopeselectors on document lookup, andexternalId/hasMoreon search results (see Search and RAG reference). It also replaced the400 AMBIGUOUS_RECORD_TYPEon a cross-ownertypeNamecollision with ownership- shadowed resolution — see the data model reference for the fullbasedOncontract. - 0.38 added composite (multi-field) lookups, and with it the one
compile-breaking change in this release for typed clients:
lookupFields[].fieldNameonLookupDefis now optional. A lookup declares eitherfieldName(one field) or the newfieldNames(2–3 fields indexed together, in a fixed declared order). Code that readsfieldNameoff a schema response must handle it being absent — a multi-field lookup reportsfieldNamesinstead — and a GET-modify-PUT round-trip on a composite lookup must carryfieldNamesthrough, or the update is rejected. Query a composite lookup with a comma-joined field name plus the newvaluesarray parameter (alongside the existing singularvalue);sortFrom/sortTonarrow an exact match to a window of the sort key and, on a composite lookup, need a value for every declared field — a partial (grouped) match has no single ordering to window. A composite lookup can't beuniqueorrangeEnabled, and only applies torecordschemas.RecordLookupResponsewas also renamed toRecordLookupPage(SDK type name only — the JSON wire shape is unchanged, so a client reading raw JSON needs no change). - 0.38 defines a
Vectros-Versionrequest header for pinning a call's response shape to a specific API version. The SDKs do not send it yet — every SDK call falls back to the calling credential's ownwire_version_pin(stamped at mint time, not your account's current setting), and from there to the platform floor, regardless of the SDK version you're on. Don't assume the SDK negotiates or pins a wire version today; that lands in a future release.
Notes & limits — what the SDK does NOT do
- Schemas have no patch. Schema updates are full-replace (PUT) only; there is
no
patchSchema. - Folders cannot be moved or reparented after creation. There is no move operation.
- There is no identity patch. Users and identity entities update by full replace.
search.contentandgetUsageare not paginated by the cursor envelope — do not look fornextCursoron them.- Indexing is asynchronous. A freshly created/ingested item is
PENDING_INDEXand not immediately searchable; poll its status (or its appearance in search) before asserting it is queryable. - All three SDKs (Node, Java, Python) are smoke-tested end-to-end against
staging at parity — see
smoke-tests/{,sdk-python/,sdk-java/}. The staging pipeline smokes all three on every change, so a cross-language wire-contract drift surfaces on staging before a release is cut.
Where to go next
- cli.md — provision schemas, contexts, and scoped keys from the terminal instead of writing this by hand.
- mcp.md — expose these same operations to an agent as tools.
- blueprints.md — declare a whole schema set + access profile in one file.
- The generated API reference — the exhaustive, per-endpoint contract the SDK is generated from.