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.

Ask / RAG

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 RAG 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).