Indexes and points
This page is the complete data model for VectorDB. It covers what an index is, what a point is, and every setting you choose when you create one. It also covers which of those settings can never change afterward, which matters just as much.
The curl tabs use the same three variables as the quickstart. The platformctl tabs need none of them.
export VDB="https://api.codyhill.dev" # VectorDB is served on the shared public API
export CAI_PROJECT="00000000-0000-0000-0000-000000000000" # your project UUID
export CAI_TOKEN="<your session token or API key>"
Index
An index is a named container for vectors. Everything in one index shares two things:
- one fixed width (the number of dimensions in every vector), and
- one distance metric (the rule for measuring how close two vectors are).
Other vector databases call this same thing a "collection" (Pinecone-style products) or an "index" (Vertex AI, Azure AI Search). Here, the resource you create and name is the index. The word collection means only the internal storage name, explained below.
Name your index
An index name uses lowercase letters, digits, and hyphens. It has to start and end with a letter or digit, and it can be at most 48 characters long. Uppercase letters and underscores are refused. Written as a pattern, that is [a-z0-9]([a-z0-9-]*[a-z0-9])?. Real rejection messages:
{"error":"index name \"My_Index\" must match [a-z0-9]([a-z0-9-]*[a-z0-9])? - lowercase letters, digits and hyphens, starting and ending with an alphanumeric","request_id":"..."}
{"error":"index name \"...\" is longer than 48 characters","request_id":"..."}
The internal collection name
Every read of an index reports a collection field, such as p_k3xq9m_docs. That is the storage name the platform built from your project plus your index name. Because your project is baked into it, two projects can both own an index named docs without colliding.
The platform shows you this name only so you can recognize your own data — for example, through direct-access credentials. No endpoint ever accepts it as input.
Dimensions
dimensions is how many numbers each vector holds. It can be anywhere from 1 to 65536. Omit it, or send 0, and you get the width of the embedding model this platform hosts — so an index you create without thinking about it fits the vectors our /embeddings endpoint produces. Every vector you ever write must have exactly this many numbers.
Confirm the width for yourself with platformctl inference embed "hello", which prints a DIMENSIONS column. If you bring your own embeddings from somewhere else, set dimensions to match that model instead.
Choose it by reading your embedding model's documentation. The model's output size is your index's dimensions. Get it wrong and every write fails with a dimension-mismatch error (see the quickstart's failure example).
:::caution Dimensions are permanent Width is fixed when the index is created. Existing vectors cannot be re-embedded into a different width, so the only way to change it is to delete the index and build it again. Get it right the first time — or leave it blank and let the platform match its own model. :::
Out-of-range values are rejected at creation:
{"error":"dimensions must be between 1 and 65536, or omit it for the platform default","request_id":"..."}
Distance metrics
The distance metric defines what "close" means. Case does not matter: Cosine and cosine are both accepted.
| Metric | Aliases | What it measures | When to pick it |
|---|---|---|---|
cosine (default) | — | The angle between vectors, ignoring their length | The usual choice for text embeddings; most embedding models are trained for it |
dot | dotproduct, dot_product | The inner product (direction and length) | When your model's docs say to use dot product / inner product |
euclid | euclidean, l2 | Straight-line distance between the points | When your model's docs say to use Euclidean or L2 distance |
Anything else is rejected:
{"error":"distance must be one of \"cosine\", \"dot\" or \"euclid\"","request_id":"..."}
Advanced creation options
These settings trade memory against speed and durability. The defaults are fine for most workloads, which is why every interface tucks them out of the way.
| Field | Default | What it does |
|---|---|---|
on_disk | false | Store vectors on disk instead of RAM. Cheaper for big indexes, slower to search. |
payload_on_disk | false | Store payloads on disk instead of RAM. |
quantization.kind | none | Compress vectors to save memory at a small accuracy cost. scalar (alias int8) or binary. |
quantization.quantile | — | Scalar-quantization tuning value, e.g. "0.99". |
quantization.always_ram | false | Keep the compressed copy in RAM even when vectors are on disk. |
shards | 1 | How many pieces the index is split into. |
replicas | 1 | How many copies exist. Range 1–8. |
Setting them looks like this — a large index whose vectors live on disk, compressed to int8:
- platformctl
- curl
- Console
platformctl vectordb create big-docs --dimensions 3072 \
--on-disk --quantization scalar --quantile 0.99 --always-ram
--quantile and --always-ram mean something only alongside --quantization. Pass them on their own and the CLI refuses, rather than sending a request that would quietly build an unquantized index you cannot change afterward.
curl -sX POST "$VDB/v1/projects/$CAI_PROJECT/indexes" \
-H "Authorization: Bearer $CAI_TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"big-docs","dimensions":3072,"on_disk":true,
"quantization":{"kind":"scalar","quantile":"0.99","always_ram":true}}'
quantization is one nested object, and omitting it entirely is how you get the none default.
In the Create vector index dialog, open Advanced options. Distance, on-disk vectors, on-disk payloads, quantization, keeping quantized vectors in RAM, shards, and replicas all live behind that toggle; name and dimensions stay on the main form. The quantile is the one tuning value the dialog does not offer — set it from the CLI or the API if you need it.
Invalid quantization kinds are rejected with:
{"error":"quantization.kind must be one of \"none\", \"scalar\" or \"binary\"","request_id":"..."}
Index lifecycle and status
Creating an index returns HTTP 201 right away, with state: "pending". The API does not wait for the storage to be built. Keep re-reading GET .../indexes/{name} — every second or so is plenty — until it reports ready: true. Reads and writes before that point return 409 index is not ready yet; its collection has not been created.
| Field | Meaning |
|---|---|
state | Coarse phase: pending, creating, ready, degraded, conflict, or blocked |
ready | The authoritative "you can use it now" boolean |
collection_status | The engine's own health word — green, yellow, red, or grey — present once storage exists |
points_count, vectors_count, segments_count | Live size counters |
message | Human-readable explanation when something is wrong (for example, why a conflict happened) |
What can change, and what can't
Only two fields are editable after creation — the replica count and whether payloads live on disk — and changing either needs the project admin role.
- platformctl
- curl
- Console
platformctl vectordb update docs --replicas 2 --payload-on-disk
Only the flags you pass are sent, so raising the replica count cannot silently revert a payload placement someone else just set. Passing neither flag is an error rather than a request that changes nothing.
curl -sX PATCH "$VDB/v1/projects/$CAI_PROJECT/indexes/docs" \
-H "Authorization: Bearer $CAI_TOKEN" -H 'Content-Type: application/json' \
-d '{"replicas":2,"payload_on_disk":true}'
You should see the whole index back, the same shape a GET returns.
Open the index and click Edit. The dialog offers exactly those two fields, because they are the only two the platform will change on a live index.
replicas must be between 1 and 8, or you get replicas must be between 1 and 8. Every other field is immutable — fixed for the life of the index. The API tells you why rather than just refusing:
| Field you tried to change | Exact 400 error |
|---|---|
dimensions | dimensions is immutable: existing points cannot be re-embedded, so a different width is a new index, not an edit |
distance | distance is immutable: the metric is baked into the graph built over the existing points |
shards | shards is immutable: resharding moves data and is not performed on a spec edit |
on_disk | on_disk is immutable: vectors are not restaged between memory and disk in place |
quantization | quantization 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 |
To change dimensions, distance, or any other locked setting, create a new index and load your data into it yourself. The platform does not move points between indexes. Deleting an index (HTTP 204) permanently removes all points contained in it.
Points
A point is one entry in an index. It has up to three parts:
{"id": 42, "vector": [0.1, 0.2, 0.3, 0.4], "payload": {"tag": "alpha", "source": "manual"}}
Vector (required)
The list of numbers. Its length must equal the index's dimensions exactly.
Each point carries exactly one dense vector, meaning one plain list of numbers with a value in every slot. VectorDB does not support named vectors (several vectors attached to one point) or sparse vectors (the mostly-zero kind used for keyword-style scoring).
Id (optional)
Either an unsigned integer — a whole number, zero or greater — or a UUID string, the 36-character random kind like 7f3c1e02-9b5d-4a11-8c6f-2d0e5a91b4cc. Nothing else is accepted. Omit the id and the platform generates a UUID for you.
Writing a point with an id that already exists overwrites that point instead of adding a second one. That update-or-insert behavior is why the write operation is called upsert.
Invalid ids are rejected:
{"error":"point 0: id must be an unsigned integer or a UUID string","request_id":"..."}
{"error":"a string id must be a UUID (or use an unsigned integer)","request_id":"..."}
Payload (optional)
Any JSON metadata you want to keep alongside the point. Payloads are what make search useful in practice: at query time you can narrow results to points whose payload matches a condition (see Search).
Store whatever you will want to filter on or show to a user. Tags, source URLs, timestamps, and the original text the vector was made from are all common choices.
Write limits
- At most 1000 points per upsert request (
at most 1000 points per request (got N)), and at least one (points must contain at least one point). - Batches are all-or-nothing: one bad point rejects the whole request; nothing is written.
- Any request body is capped at 32 MiB.
Summary
| Decision | Rule of thumb |
|---|---|
| Name | Lowercase, digits, hyphens; ≤ 48 chars; pick something you can grep for |
| Dimensions | Copy your embedding model's output size; you cannot change it later |
| Distance | cosine unless your model's docs say otherwise; you cannot change it later |
| Ids | Let the platform generate UUIDs unless you need to overwrite points deterministically |
| Payload | Store everything you'll filter on or need to show in results |
| Advanced options | Leave at defaults until memory cost forces the question |
Next steps
- Search — how to query all of this.