Skip to main content

Rust SDK

Idiomatic async Rust client for the public VectorAmp API. Async/await on top of reqwest and tokio, with a small Transport trait so a different stack (gRPC, mocks) can be plugged in.

Install​

# Cargo.toml
[dependencies]
vectoramp = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Configure​

Client::new reads the API key you pass in; the only required input is the key, commonly read from VECTORAMP_API_KEY. Auth uses X-API-Key.

use vectoramp::Client;

let client = Client::new(std::env::var("VECTORAMP_API_KEY").unwrap());

let client = Client::builder()
.api_key(std::env::var("VECTORAMP_API_KEY").unwrap())
.build()?;

Quickstart​

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

use vectoramp::Client;

#[tokio::main]
async fn main() -> vectoramp::Result<()> {
let client = Client::new(std::env::var("VECTORAMP_API_KEY").unwrap());

// One-call create.
let dataset = client.datasets().create("product-docs").await?;

// Object -> method: operate on the returned dataset directly.
dataset
.add_texts(vec!["VectorAmp is a high-performance vector database."])
.await?;

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

println!("{:?}", results.results);
println!("{}", answer.answer);
Ok(())
}
info

CreateDatasetRequest has no index_type field. The SDK always sends index_type: "sable", and the create body field is dim (never dimension).

Creating datasets​

use vectoramp::CreateDatasetRequest;

// Minimal: name only (embedding + dim + metric defaulted/inferred).
let dataset = client.datasets().create("docs").await?;

// Hybrid (dense + sparse) index.
let hybrid = client
.datasets()
.create(CreateDatasetRequest::builder("docs").hybrid(true))
.await?;

// OpenAI embedding ("small" → 1536, "large" → 3072 inferred).
let openai = client
.datasets()
.create(CreateDatasetRequest::builder("docs").openai("small"))
.await?;

// Custom / unknown model requires an explicit dim.
let custom = client
.datasets()
.create(
CreateDatasetRequest::builder("docs")
.embedding(vectoramp::EmbeddingConfig {
provider: Some("acme".into()),
model: Some("acme-embed".into()),
})
.dim(1024),
)
.await?;

Object and service styles​

create, get, and list return Dataset resource handles bound to the originating client. Both the object → method and service styles work everywhere. Pagination accepts (), (limit, offset), or a bare limit.

// Object -> method style (preferred)
let page = client.datasets().list(()).await?;
let dataset = client.datasets().get(&page.datasets[0].id).await?;
let resp = dataset.search("release notes").await?;
dataset.delete().await?;

// Service style
let resp = client.datasets().search("dataset-id", "release notes").await?;
client.datasets().delete("dataset-id").await?;

Vector ids accept a string or an integer. Integer ids are serialized as JSON numbers so the API preserves them exactly.

use vectoramp::{TextDocument, Vector};

dataset
.insert(vec![
Vector::new(1, vec![0.1, 0.2, 0.3]), // numeric id → JSON number
Vector::new("doc-2", vec![0.4, 0.5, 0.6]), // string id → JSON string
])
.await?;

// add_texts embeds through the dataset model, copies the text into metadata.text, and inserts.
dataset.add_texts(vec!["Hello world", "Machine learning notes"]).await?;

dataset
.add_texts(vec![
TextDocument { id: "doc-1".into(), text: "Hello world".into(), metadata: None },
TextDocument { id: 2.into(), text: "Numeric id".into(), metadata: None },
])
.await?;

String queries default to top_k: 10. rerank: true expands to the full model-rerank object. Hybrid search accepts sparse_query/alpha via SearchRequest.

use vectoramp::{SearchInput, SearchOptions};

let resp = dataset.search("machine learning best practices").await?;

let resp = dataset
.search_with(
"machine learning best practices",
SearchOptions {
top_k: Some(10),
include_documents: Some(true),
..Default::default()
}
.with_rerank(true), // expands to vectoramp / VectorAmp-Rerank-v1
)
.await?;

// Vector search:
let resp = dataset.search(SearchInput::Vector(vec![0.1, 0.2, 0.3])).await?;
tip

SearchOptions::with_rerank(true) enables the optional model reranker after initial retrieval. The separate SearchRequest::rerank_depth_override 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".
let dataset = client
.datasets()
.create_with_openai_api_key(CreateDatasetRequest::new("openai-docs"), std::env::var("OPENAI_API_KEY")?)
.await?;

// Or manage the secret explicitly.
client.org_secrets().put_openai_api_key(std::env::var("OPENAI_API_KEY")?).await?;

// Delete individual vector ids from search.
client.datasets().delete_vectors(&dataset.id, vec!["doc-001".into(), 42.into()]).await?;

Ingestion​

Typed builders fill in source_type, sensible defaults, and a generated name. Supported types: web, s3, gcs, gdrive, jira, confluence, file_upload, plus GenericSource as an escape hatch.

use vectoramp::{ConfluenceSource, S3Source, WebSource};

let web = client
.sources()
.create_web(WebSource {
start_urls: vec!["https://docs.example.com".into()],
max_depth: Some(2),
..Default::default()
})
.await?;

let s3 = client
.sources()
.create_s3(S3Source {
bucket: "my-bucket".into(),
prefix: Some("docs/".into()),
region: Some("us-east-1".into()),
access_key_id: Some(std::env::var("AWS_ACCESS_KEY_ID").unwrap()),
secret_access_key: Some(std::env::var("AWS_SECRET_ACCESS_KEY").unwrap()),
..Default::default()
})
.await?;

let confluence = client
.sources()
.create_confluence(ConfluenceSource {
base_url: Some("https://acme.atlassian.net".into()),
username: Some("bot@acme.com".into()),
api_token: Some(std::env::var("CONFLUENCE_API_TOKEN").unwrap()),
spaces: vec!["ENG".into()],
..Default::default()
})
.await?;

create_source accepts any builder directly, and the per-type create_web/create_s3/create_gcs/create_google_drive/create_jira/create_confluence/create_file_upload/create_generic helpers are thin wrappers over it.

use vectoramp::StartIngestionRequest;

let dataset = client.datasets().get("dataset-id").await?;

// Create a source and start a job in one call.
let job = dataset
.ingest_new_source(WebSource {
start_urls: vec!["https://example.com/releases".into()],
..Default::default()
})
.await?;

// Start a job from an existing source.
let job = dataset.ingest_source("source-id").await?;

let jobs = client.ingestion().list_jobs(Some("dataset-id"), ()).await?;
let job = client.ingestion().get_job(&job.job_id).await?;

Manage and clean up sources​

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

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

// See what's using a source (active schedules + in-flight jobs).
let refs = client.sources().get_references("source-id").await?; // refs.schedules, refs.schedule_count, refs.active_job_count, refs.in_use

// Delete. Returns Error::Api(409) if in use unless force is true (force also disables its schedules).
client.sources().delete("source-id", false).await?;
client.sources().delete("source-id", true).await?;

// Find unused sources, or remove all of them in one call.
let unused = client.sources().list_unused().await?;
let result = client.sources().cleanup_unused().await?; // result.deleted, result.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.

use vectoramp::{CreateConnectionRequest, GoogleDriveSource};

// Option 1 — service account (headless, no browser).
let drive = client
.sources()
.create_google_drive(GoogleDriveSource {
folder_ids: vec!["google-drive-folder-id".into()],
service_account_json: Some(std::env::var("GOOGLE_SERVICE_ACCOUNT_JSON").unwrap()),
..Default::default()
})
.await?;

// Option 2 — connection (interactive once, reused server-side).
// connect() creates the grant, prints/opens authorization_url, and polls until connected.
let conn = client
.connections()
.connect(CreateConnectionRequest { provider: "google".into(), source_type: Some("gdrive".into()) })
.await?;
let drive = client
.sources()
.create_google_drive(GoogleDriveSource {
folder_ids: vec!["google-drive-folder-id".into()],
connection_id: Some(conn.id.clone()),
..Default::default()
})
.await?;

Drive the grant flow yourself when you need to:

let conn = client
.connections()
.create(CreateConnectionRequest { provider: "google".into(), source_type: Some("gdrive".into()) })
.await?;
println!("Open in a browser: {}", conn.authorization_url.unwrap()); // conn.status == "pending"
let conn = client.connections().get(&conn.id).await?; // poll until conn.status == "connected"
let list = client.connections().list(Some("google")).await?;
client.connections().delete(&conn.id).await?;

Minimal file upload ingestion​

ingest_files hides the presigned-upload flow: it creates a file_upload source, initializes presigned uploads, PUTs the bytes, and completes the job.

use std::path::PathBuf;

let job = dataset
.ingest_files(vec![PathBuf::from("./docs/guide.pdf")], None)
.await?;

Dataset documents​

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

use vectoramp::DocumentListOptions;

let page = dataset
.list_documents(DocumentListOptions {
limit: Some(50),
status: Some("ready".into()),
..Default::default()
})
.await?;

for doc in &page.documents {
if !doc.download_available { continue; }
let bytes = dataset.download_document(&doc.id).await?;
if let Some(name) = &doc.file_name {
std::fs::write(name, bytes)?;
}
}

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

Intelligence​

ask defaults top_k = 5, include_sources = true, and dataset scope "all" when unscoped.

use vectoramp::AskOptions;

// Unscoped (defaults to all datasets).
let answer = client.ask("What are the key product features?").await?;

// Scoped to a dataset.
let answer = dataset.ask("What are the key product features?").await?;

// Explicit options.
let answer = client
.ask_with(
"What changed in the latest release?",
AskOptions::default().with_all_datasets().with_top_k(8),
)
.await?;

// Streaming SSE.
let mut stream = dataset.ask_stream("Summarize the launch plan").await?;
while let Some(event) = stream.next_event().await? {
if event.chunk_type == "text" {
print!("{}", event.content);
}
}

Persistent Intelligence sessions​

Durable Intelligence conversations persisted server-side instead of resending history.

let session = client.intelligence().create_session("Launch planning").await?;

client
.intelligence()
.append_message(&session.id, "user", "What is our launch date?")
.await?;

let messages = client.intelligence().list_messages(&session.id, ()).await?;
let sessions = client.intelligence().list_sessions(()).await?;
let one = client.intelligence().get_session(&session.id).await?;

answer.sources contains source-level citations. Inline [1] markers refer to sources[0]; preview_ref is an opaque preview token, never a raw storage key.

Errors​

Non-2xx responses surface as Error::Api(ApiError).

match client.datasets().get("missing").await {
Ok(dataset) => { let _ = dataset; }
Err(vectoramp::Error::Api(err)) => {
eprintln!("api error {}: {}", err.status, err.message);
}
Err(err) => eprintln!("transport error: {err}"),
}

Method reference​

R = required, O = optional. Pagination arguments accept (), (limit, offset), or a bare limit.

client.datasets() — DatasetService (and the bound Dataset object)​

MethodArgsReturns
list(pagination)pagination (O)DatasetList
get(id)id (R)Dataset
create(req)name/builder/request (R); dim?, metric?, embedding?, hybrid?Dataset
delete(id) / Dataset::delete()id (R)()
search(id, input) / Dataset::search(input)id (R), text|vector|SearchRequest (R)SearchResponse
search_with(id, input, opts) / Dataset::search_with(input, opts)+ SearchOptions (O)SearchResponse
insert(id, vectors) / Dataset::insert(vectors) (+ insert_vectors)id (R), Vec<Vector> (R, string or numeric id)InsertVectorsResponse
embed(id, req) / Dataset::embed(req)id (R), EmbedRequest (R)EmbedResponse
add_texts(id, input) / Dataset::add_texts(input)id (R), texts/docs (R)AddTextsResponse
add_texts_with(id, input, opts) / Dataset::add_texts_with(input, opts)+ AddTextsOptions (O)AddTextsResponse
list_documents(id, opts) / Dataset::list_documents(opts)id (R), DocumentListOptions (O)DatasetDocumentList
download_document(id, docId) / Dataset::download_document(docId)id (R), docId (R)Vec<u8>
ingest_source(id, srcId) / Dataset::ingest_source(srcId)id (R), srcId (R)Job
ingest_new_source(id, builder) / Dataset::ingest_new_source(builder)id (R), source builder (R)Job
ingest_files(id, paths, opts) / Dataset::ingest_files(paths, opts)id (R), paths (R), IngestFilesOptions (O)Job
ask(id, query) / Dataset::ask(query)id (R), query (R)AskResponse
ask_with(id, query, opts) / Dataset::ask_with(query, opts)+ AskOptions (O)AskResponse
ask_stream(id, query) / Dataset::ask_stream(query)id (R), query (R)AskStream

Embedding helper: CreateDatasetRequest::builder(name).openai("small"|"large").

client.ask* (top level)​

ask(query), ask_with(query, opts), ask_stream(query).

client.ingestion() / client.sources() — IngestionService​

MethodArgsReturns
list_sources(pagination)pagination (O)SourceList
get_source(id)id (R)Source
create_source(builder)source builder (R)Source
create_web/_s3/_gcs/_google_drive/_jira/_confluence/_file_upload/_generic(source)typed builder (R)Source
validate(source_type, config)source_type (R), config (R)ValidateResult
get_references(id)id (R)SourceReferences (schedules, schedule_count, active_job_count, in_use)
delete(id, force)id (R), force (R)()
list_unused()—SourceList
cleanup_unused()—CleanupResult (deleted, count)
start_job(req)StartIngestionRequest (R)Job
list_jobs(dataset_id, pagination)dataset_id (O), pagination (O)JobList
get_job(id) / retry_job(id)id (R)Job
ingest_files(dataset_id, paths, opts)dataset_id (R), paths (R), opts (O)Job

Source builders: WebSource, S3Source, GcsSource, GoogleDriveSource (service_account_json or connection_id for auth), JiraSource, ConfluenceSource, FileUploadSource, GenericSource.

client.connections() — ConnectionService​

MethodArgsReturns
list(provider)provider (O)ConnectionList
create(req)CreateConnectionRequest (R)Connection (status: "pending", authorization_url)
get(id)id (R)Connection (poll until status == "connected")
delete(id)id (R)()
connect(req)CreateConnectionRequest (R)Connection — create + open authorization_url + poll until connected

client.intelligence() — IntelligenceService​

MethodArgsReturns
ask(query) / ask_with(query, opts)query (R), AskOptions (O)AskResponse
stream(query, opts)query (R), AskOptions (O)AskStream
create_session(req)title/CreateSessionRequest (O)IntelligenceSession
list_sessions(pagination)pagination (O)SessionList
get_session(id) / delete_session(id)id (R)IntelligenceSession / ()
append_message(id, role, content)id (R), role (R), content (R)SessionMessage
append_message_with(id, req)id (R), AppendMessageRequest (R)SessionMessage
list_messages(id, pagination)id (R), pagination (O)MessageList

client.schedules() — ScheduleService​

list(pagination), get(id), create(req), update(id, req), delete(id), trigger(id).