Skip to main content

VectorDB API

This page lists every endpoint on the VectorDB service (vectordb-api), with request fields, response shapes, and the exact error messages the server returns. Use it when you are writing code against the API; for a guided introduction, start with the VectorDB overview and quickstart.

Base URL

VectorDB has its own API service, separate from the core platform API. In-cluster it is vectordb-api.cai-system.svc:8080.

No public API hostname yet

The platform is in alpha and does not publish a public hostname for this API. Reach it through a port-forward (shown below), or through an endpoint your administrator provides. The platformctl CLI does this automatically when $CAI_VECTORDB_API is not set.

kubectl -n cai-system port-forward svc/vectordb-api 18080:8080 &
export CAI_VECTORDB_API=http://localhost:18080

Authentication

Every route except GET /healthz requires a bearer credential:

Authorization: Bearer <token-or-api-key>

The credential is a session token (from POST /v1/auth/login on the core API, 12-hour life), a service-account or personal API key (prefix cai_), or the platform automation token. Your project role is re-read from the database on every request, so a role change takes effect immediately. See API authentication.

Two roles matter here:

RoleCan call
memberlist, get, create index; :upsert, :query, :scroll, :delete-points
admineverything a member can, plus PATCH index, DELETE index, and GET /vectordb/credentials

Conventions

  • Error envelope. Every non-2xx response is {"error": "<message>", "request_id": "<id>"}. Unknown paths return a 404 in this envelope; a wrong HTTP method returns a 405.
  • The 404 rule. A request against an index (or project) you hold no grant on returns 404 — never 403 — so resource existence is not discoverable. You only see 403 when you are a member but the action needs admin.
  • Pagination. List endpoints accept page_size (1–200, default 50; limit is an alias) and page_token (opaque). Responses carry next_page_token, absent on the last page. Out-of-range sizes are rejected with 400 page_size must be between 1 and 200, never clamped. A token from a different list or project returns 400 page_token is invalid or was issued for a different list; start from the first page.
  • Custom methods. Data operations use GCP-style :verb suffixes on the index path (:upsert, :query, :scroll, :delete-points), always with POST.
  • Rate limiting. A per-principal token bucket wraps every route except /healthz; shed requests get 429.

Endpoints at a glance

MethodPathAuthPurpose
GET/v1/projects/{projectID}/indexesmemberList indexes
POST/v1/projects/{projectID}/indexesmemberCreate an index
GET/v1/projects/{projectID}/indexes/{name}memberGet one index
PATCH/v1/projects/{projectID}/indexes/{name}adminEdit mutable fields
DELETE/v1/projects/{projectID}/indexes/{name}adminDelete an index
POST/v1/projects/{projectID}/indexes/{name}:upsertmemberWrite points
POST/v1/projects/{projectID}/indexes/{name}:querymemberSimilarity search
POST/v1/projects/{projectID}/indexes/{name}:scrollmemberBrowse points in id order
POST/v1/projects/{projectID}/indexes/{name}:delete-pointsmemberDelete points by id or filter
GET/v1/projects/{projectID}/vectordb/credentialsadminDirect Qdrant access credential
GET/healthznoneHealth check

The index object

Every read returns this shape (the IndexResponse):

{
"name": "docs",
"resource_path": "projects/<short>/indexes/docs",
"collection": "p_<short>_docs",
"dimensions": 1536,
"distance": "cosine",
"on_disk": false,
"payload_on_disk": false,
"quantization": {"kind": "none", "quantile": "", "always_ram": false},
"shards": 1,
"replicas": 1,
"state": "ready",
"ready": true,
"collection_status": "green",
"points_count": 0,
"vectors_count": 0,
"segments_count": 0,
"message": "",
"created_at": "2026-08-06T12:00:00Z"
}
FieldTypeMeaning
namestringThe index name you chose
resource_pathstringStable path: projects/<short>/indexes/<name>
collectionstringThe internal storage name (p_<short>_<name>). Reported so you can find your data; never accepted as input anywhere
dimensionsintVector width. Immutable
distancestringcosine, dot, or euclid. Immutable
on_diskboolVectors stored on disk instead of memory. Immutable
payload_on_diskboolPayloads stored on disk. Editable via PATCH
quantizationobjectkind (none/scalar/binary), quantile, always_ram. Immutable
shardsintShard count. Immutable
replicasintReplica count (1–8). Editable via PATCH
statestringpending, creating, ready, degraded, conflict, or blocked
readyboolThe authoritative "you can use it now" signal
collection_statusstringThe engine's own health word (green/yellow/red/grey); present only once the collection exists
points_count, vectors_count, segments_countintLive counts
messagestringWhy the index is not ready (for example, a conflict explanation)
created_atstringRFC 3339 timestamp

List indexes

GET /v1/projects/{projectID}/indexes

Auth: member.

Query parameters:

NameTypeRequiredDefaultNotes
page_sizeintno501–200; limit is an alias
page_tokenstringnoOpaque cursor from a previous page

Response 200:

{"indexes": [], "next_page_token": "..."}

next_page_token is absent on the last page.

Errors: 400 page_size must be between 1 and 200; 400 page_token is invalid or was issued for a different list; start from the first page.

Create an index

POST /v1/projects/{projectID}/indexes

Auth: member. Returns 201 with an index object in state pending — the collection is created asynchronously. Poll GET until ready: true; data-plane calls return 409 until then.

Request body (only name is required — {"name":"docs"} is a complete request):

{
"name": "docs",
"dimensions": 256,
"distance": "cosine",
"on_disk": false,
"payload_on_disk": false,
"quantization": {"kind": "scalar", "quantile": "0.99", "always_ram": true},
"shards": 1,
"replicas": 1
}
FieldTypeRequiredDefaultRules
namestringyesLowercase letters, digits, hyphens; must start and end alphanumeric; max 48 chars
dimensionsintno15361–65536; omitted or 0 means 1536. Immutable after create
distancestringnocosineCase-insensitive: cosine; dot (aliases dotproduct, dot_product); euclid (aliases euclidean, l2)
on_diskboolnofalseImmutable
payload_on_diskboolnofalseEditable later
quantization.kindstringnononenone, scalar (alias int8), or binary. Immutable
quantization.quantilestringnoScalar quantization quantile
quantization.always_ramboolnofalseKeep quantized vectors in memory
shardsintno1Immutable
replicasintno11–8; editable later

Errors:

StatusMessage
400dimensions must be between 1 and 65536, or omit it for the default of 1536
400distance must be one of "cosine", "dot" or "euclid"
400quantization.kind must be one of "none", "scalar" or "binary"
400index name %q must match [a-z0-9]([a-z0-9-]*[a-z0-9])? - lowercase letters, digits and hyphens, starting and ending with an alphanumeric
400index name %q is longer than 48 characters
409an index with that name already exists

Get an index

GET /v1/projects/{projectID}/indexes/{name}

Auth: member. Returns 200 with the index object. A missing index, a malformed name, and another project's index all return the same 404 — deliberately indistinguishable.

Edit an index

PATCH /v1/projects/{projectID}/indexes/{name}

Auth: admin. Returns 200 with the updated index object. Only two fields are editable:

{"replicas": 2, "payload_on_disk": true}
FieldTypeRules
replicasint1–8. 400 replicas must be between 1 and 8
payload_on_diskbool

Sending an immutable field returns a 400 that names it:

Field sentMessage
dimensionsdimensions is immutable: existing points cannot be re-embedded, so a different width is a new index, not an edit
distancedistance is immutable: the metric is baked into the graph built over the existing points
shardsshards is immutable: resharding moves data and is not performed on a spec edit
on_diskon_disk is immutable: vectors are not restaged between memory and disk in place
quantizationquantization is immutable: it is fixed when the collection is built
(empty patch)no mutable fields to change; the editable fields are replicas and payload_on_disk

Delete an index

DELETE /v1/projects/{projectID}/indexes/{name}

Auth: admin. Returns 204 with no body.

No undo

Deleting an index destroys the collection and every vector in it. There are no snapshots, no backups, and no restore.

Custom methods

The four data operations are POSTs to the index path with a :verb suffix. Two routing errors apply to all of them:

  • A POST with an unknown verb returns 400 unknown method "<verb>"; expected upsert, query, scroll or delete-points.
  • A POST with no :verb at all returns 400 expected a custom method: POST .../indexes/{name}:upsert, :query, :scroll or :delete-points.
  • While the index is still pending, all four return 409 index is not ready yet; its collection has not been created.

Upsert points

POST /v1/projects/{projectID}/indexes/{name}:upsert

Auth: member.

{"points": [
{"id": 42, "vector": [0.1, 0.2], "payload": {"tag": "alpha"}},
{"vector": [0.3, 0.4]}
]}
FieldTypeRequiredRules
pointsarrayyes1–1000 points per request
points[].iduint or UUID stringnoOmitted means a UUID is generated
points[].vectorarray of numbersyesWidth must equal the index's dimensions
points[].payloadobjectnoArbitrary JSON metadata, filterable at query time

Batches are all-or-nothing: one bad point rejects the whole request, and nothing is written.

Response 200:

{"index": "docs", "upserted_count": 2}

Errors:

StatusMessage
400points must contain at least one point
400at most 1000 points per request (got N)
400point 3 has 1536 dimensions; index "docs" expects 256
400point 0: id must be an unsigned integer or a UUID string
400a string id must be a UUID (or use an unsigned integer)
POST /v1/projects/{projectID}/indexes/{name}:query

Auth: member.

{
"vector": [0.1, 0.2],
"top_k": 5,
"filter": {"must": [{"key": "tag", "match": {"value": "alpha"}}]},
"with_payload": true,
"with_vector": false,
"score_threshold": 0.5,
"offset": 0
}
FieldTypeRequiredDefaultRules
vectorarray of numbersyesWidth must equal the index's dimensions
top_kintno10Max 1000
filterobjectnoA Qdrant payload filter; must be a JSON object; max 64 KiB
with_payloadboolnotrueInclude each hit's payload
with_vectorboolnofalseInclude each hit's vector
score_thresholdnumbernoDrop hits scoring below this
offsetintno0Must not be negative

Response 200 (payload and vector appear per the flags):

{"index": "docs", "results": [{"id": 42, "score": 0.98, "payload": {"tag": "alpha"}, "vector": [0.1, 0.2]}]}

Errors:

StatusMessage
400top_k must be at most 1000
400filter must be a JSON object
400filter is too large
400offset must not be negative
400query vector has N dimensions; index "docs" expects M
400vector database rejected the request: <qdrant message> (engine-side rejections passed through)

Scroll (browse points)

POST /v1/projects/{projectID}/indexes/{name}:scroll

Auth: member. Lists points in id order — no vector needed. This is what the console's data browser uses.

{"offset": null, "limit": 50}
FieldTypeRequiredDefaultRules
limitintno50Max 1000
offsetuint or UUID stringnoThe previous page's next_offset, echoed back

Response 200:

{"index": "docs", "points": [{"id": 42, "payload": {"tag": "alpha"}}], "next_offset": 99}

next_offset is omitted when the walk is exhausted. Scrolled points never carry vectors.

Errors: 400 limit must be at most 1000; 400 offset must be an unsigned integer or a UUID string.

Delete points

POST /v1/projects/{projectID}/indexes/{name}:delete-points

Auth: member. Provide exactly one of ids or filter:

{"ids": [42, "6f1c..."]}
{"filter": {"must": [{"key": "tag", "match": {"value": "beta"}}]}}
FieldTypeRules
idsarrayMax 1000 ids; each an unsigned integer or UUID string
filterobjectA Qdrant payload filter; an empty object is refused

Response 200 (ids_requested is omitted for filter deletes):

{"index": "docs", "status": "ok", "ids_requested": 2}

Errors:

StatusMessage
400provide exactly one of ids or filter
400at most 1000 ids per request
400an empty filter would delete every point; delete the index instead if that is the intent

Direct Qdrant credentials

GET /v1/projects/{projectID}/vectordb/credentials

Auth: admin. Returns a credential for talking to the underlying Qdrant engine directly, scoped to your project's collections only.

Response 200:

{
"service_url": "http://qdrant.cai-services.svc.cluster.local:6333",
"external_service_url": "https://qdrant.<domain>",
"token": "<HS256 JWT>",
"collection_prefix": "p_<short>_",
"collections": ["p_<short>_docs"],
"truncated": false,
"note": "this credential is scoped to only this project's index collections..."
}
FieldMeaning
service_urlIn-cluster Qdrant address
external_service_urlTLS endpoint reachable from your Crusoe VPC (never the public internet). Present only alongside a token
tokenA JWT you present to Qdrant as the api-key header (or as a Bearer token). Read-write on the listed collections; cannot create collections
collection_prefixYour project's collection name prefix
collectionsThe exact collection names the token covers, snapshotted at mint time
truncatedtrue when more than 256 collections exist; only 256 are enumerated per token
notePlain-English explanation of exactly which of the above applies to this response
Token availability and scope

The token is minted only when the platform enforces per-project access control on Qdrant — which is off by default in the current alpha. Without it you get the addresses and collection names but no credential. The token's scope is a snapshot of exact collection names: an index created later needs a fresh fetch. The raw Qdrant master key is never returned.

Health check

GET /healthz

No auth. Response 200:

{"status": "ok", "dependencies": {
"vector_store": {"ok": true, "latency_ms": 3},
"kubernetes": {"ok": true, "latency_ms": 12},
"database": {"ok": true, "latency_ms": 1}}}

When any dependency fails, the status becomes "degraded", the HTTP status is 503, and each failing dependency carries an error field. The internal probe budget is 5 seconds.

Server limits

These are enforced server-side. Some are operator-tunable environment variables; the values below are the defaults.

LimitValue
Points per upsert request1000 (MAX_POINTS_PER_REQUEST)
Any request body32 MiB (MAX_BODY_BYTES)
top_kmax 1000 (MAX_TOP_K), default 10
Payload filter size64 KiB
Scroll pagedefault 50, max 1000
List page_sizedefault 50, max 200
Index name length48 characters
dimensions1–65536, default 1536
replicas1–8
Collections per direct-access token256

See platform limits for cross-service limits.

Status codes

CodeWhen
400Validation failure; malformed project id (project id must be a UUID); Qdrant 4xx passthrough
401Missing or invalid credential
403You are a member but the route needs admin
404Not found, or not yours — deliberately identical
409Index name taken; index not reconciled yet
429Per-principal rate limit
500internal error
503Health degraded; session signing key not loaded