Manage Embedding Indexes
An embedding index is a policy that tells RushDB to vectorize a specific string property for a label. Once status is ready, every record matching that label+property pair is searchable via db.records.vectorSearch().
Indexes are scoped to (label, propertyName) — Book:description and Article:description are completely independent with separate vector stores.
Managed and external indexes
sourceType | Who makes the vectors | RushDB Cloud | Self-hosted engine |
|---|---|---|---|
managed (default) | RushDB Cloud embeds the property on every write, and backfills existing records. | ✓ | — |
external | You do, with your own model, and write them with the record or in bulk. | ✓ | ✓ |
A self-hosted engine never calls an embedding model. Create external indexes there (external: true), as in Bring Your Own Vectors; a managed index throws CloudOnlyError before anything is sent.
How RushDB stores embeddings
Most databases store vectors directly on the record — polluting the schema with fields like description_emb_v1_1536 alongside regular metadata. Agents retrieving records get back a mix of business fields and raw float arrays, making schemas noisy and hard to reason about.
RushDB keeps vectors out of the record. Each embedding index is its own vector store, and each vector in it is bound to one record. Your record properties stay clean and uniform; the vectors are read only when you search.
Decoupling vectors from record data means:
- Agents see clean schemas — no float arrays mixed with business fields
- Multiple indexes on one property — cosine + euclidean, different dimensions, side by side
- Zero record writes on index create/delete — vector policy changes don't touch your records
Create an Index
- Python
- TypeScript
- shell
db.ai.indexes.create()
# Simplest form — uses server-configured model and dimensions
response = db.ai.indexes.create({
"label": "Article",
"propertyName": "description"
})
print(response.data["status"]) # 'pending' → backfill starts immediately
# With explicit parameters
response = db.ai.indexes.create({
"label": "Article",
"propertyName": "description",
"similarityFunction": "cosine",
"dimensions": 1536
})
db.ai.indexes.create()
// Simplest form — uses server-configured model and dimensions
const { data: index } = await db.ai.indexes.create({
label: 'Book',
propertyName: 'description'
})
console.log(index.status) // 'pending' → backfill starts immediately
// With explicit parameters
const { data: index } = await db.ai.indexes.create({
label: 'Article',
propertyName: 'body',
similarityFunction: 'cosine',
dimensions: 1536
})
POST /api/v1/ai/indexes
# Simplest form
curl -X POST https://api.rushdb.com/api/v1/ai/indexes \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{"label": "Article", "propertyName": "description"}'
# With explicit parameters
curl -X POST https://api.rushdb.com/api/v1/ai/indexes \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"label": "Article",
"propertyName": "description",
"similarityFunction": "cosine",
"dimensions": 1536
}'
Create parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | no | Unique within the project: 1–64 letters, digits, ., _, : or -. Defaults to <label>.<property>.<model>.<dimensions>.<similarity>. Search, stats, vector writes and delete accept the name as well as the id. |
label | string | yes | Label to scope this index to (e.g. "Article") |
propertyName | string | yes | Property to embed (e.g. "description") |
sourceType | string | no | "managed" (default) or "external". See Bring Your Own Vectors. |
external | boolean | no | Shorthand for sourceType: "external". |
similarityFunction | string | no | "cosine" (default) or "euclidean" |
dimensions | number | no | Vector length. Defaults to the managed model's dimensions. Required for external indexes. |
Attempting to create a duplicate
(label, propertyName, sourceType, similarityFunction, dimensions)tuple returns409 Conflict.
The managed model is RushDB Cloud's. Each index remembers the model it was embedded with (
modelKey), which is part of its default name.POST /api/v1/ai/indexes/name-suggestionreturns the name a new index would get.
Index lifecycle
| Status | Description |
|---|---|
pending | Policy created, waiting for backfill scheduler |
indexing | Backfill in progress |
awaiting_vectors | External index — waiting for client to push vectors |
ready | All existing records have vectors; search is available |
error | Backfill failed 3 runs in a row (transient failures are retried automatically); check server logs for the cause |
List Indexes
- Python
- TypeScript
- shell
db.ai.indexes.find()
response = db.ai.indexes.find()
for index in response.data:
print(f"{index['label']}.{index['propertyName']} — {index['status']}")
db.ai.indexes.find()
const { data: indexes } = await db.ai.indexes.find()
/*
[
{
id: "01jb...",
label: "Book",
propertyName: "description",
sourceType: "managed",
similarityFunction: "cosine",
dimensions: 1536,
status: "ready",
modelKey: "text-embedding-3-small",
...
}
]
*/
GET /api/v1/ai/indexes
curl https://api.rushdb.com/api/v1/ai/indexes \
-H "Authorization: Bearer $RUSHDB_API_KEY"
{
"data": [
{
"id": "idx_abc123",
"label": "Article",
"propertyName": "description",
"sourceType": "managed",
"similarityFunction": "cosine",
"dimensions": 1536,
"status": "ready"
}
],
"success": true
}
Index Stats
Returns the fill rate for an index — useful for progress monitoring.
- Python
- TypeScript
- shell
db.ai.indexes.stats(index_id)
response = db.ai.indexes.stats(index_id)
stats = response.data
print(f"{stats['indexedRecords']} / {stats['totalRecords']} records indexed")
db.ai.indexes.stats(id)
const { data: stats } = await db.ai.indexes.stats(index.id)
console.log(`${stats.indexedRecords} / ${stats.totalRecords} records indexed`)
type EmbeddingIndexStats = {
totalRecords: number
indexedRecords: number
}
GET /api/v1/ai/indexes/:id/stats
curl https://api.rushdb.com/api/v1/ai/indexes/$INDEX_ID/stats \
-H "Authorization: Bearer $RUSHDB_API_KEY"
Delete an Index
- Python
- TypeScript
- shell
db.ai.indexes.delete(index_id)
db.ai.indexes.delete(index_id)
db.ai.indexes.delete(id)
await db.ai.indexes.delete(index.id)
DELETE /api/v1/ai/indexes/:id
curl -X DELETE https://api.rushdb.com/api/v1/ai/indexes/$INDEX_ID \
-H "Authorization: Bearer $RUSHDB_API_KEY"
Index Response Shape
{
"id": "idx_abc123",
"projectId": "proj_xyz",
"label": "Article",
"propertyName": "description",
"modelKey": "text-embedding-3-small",
"sourceType": "managed",
"similarityFunction": "cosine",
"dimensions": 1536,
"vectorPropertyName": "_emb_managed_cosine_1536",
"enabled": true,
"status": "ready",
"createdAt": "2025-01-10T12:00:00.000Z",
"updatedAt": "2025-01-10T12:05:00.000Z"
}
Wait for Index Ready
For managed indexes, backfill runs asynchronously. Poll until status is ready:
- Python
- TypeScript
- shell
import time
def wait_for_index_ready(db, index_id, timeout_s=90):
deadline = time.time() + timeout_s
while time.time() < deadline:
response = db.ai.indexes.find()
idx = next((i for i in response.data if i["id"] == index_id), None)
if idx and idx["status"] == "ready":
return
if idx and idx["status"] == "error":
raise RuntimeError("Index entered error state")
time.sleep(3)
raise TimeoutError("Index did not become ready in time")
response = db.ai.indexes.create({"label": "Book", "propertyName": "description"})
wait_for_index_ready(db, response.data["id"])
# now safe to call db.records.vectorSearch(...)
async function waitForIndexReady(db: RushDB, indexId: string, timeoutMs = 90_000): Promise<void> {
const deadline = Date.now() + timeoutMs
while (Date.now() < deadline) {
const { data: indexes } = await db.ai.indexes.find()
const idx = indexes.find((i) => i.id === indexId)
if (idx?.status === 'ready') return
if (idx?.status === 'error') throw new Error('Index entered error state')
await new Promise((r) => setTimeout(r, 3_000))
}
throw new Error('Index did not become ready in time')
}
const { data: index } = await db.ai.indexes.create({
label: 'Book',
propertyName: 'description'
})
await waitForIndexReady(db, index.id)
// now safe to call db.records.vectorSearch(...)
# Poll until status == "ready"
while true; do
STATUS=$(curl -s https://api.rushdb.com/api/v1/ai/indexes/$INDEX_ID \
-H "Authorization: Bearer $RUSHDB_API_KEY" | jq -r '.data.status')
echo "Status: $STATUS"
[ "$STATUS" = "ready" ] && break
sleep 3
done
Multiple Indexes on the Same Property
You can have more than one index per (label, propertyName) pair, provided the signature differs:
- Python
- TypeScript
- shell
# Cosine index
db.ai.indexes.create({
"label": "Product",
"propertyName": "description",
"similarityFunction": "cosine",
"dimensions": 768,
})
# Euclidean index on the same property
db.ai.indexes.create({
"label": "Product",
"propertyName": "description",
"similarityFunction": "euclidean",
"dimensions": 768,
})
When searching or writing vectors against a property with multiple indexes, specify similarityFunction to disambiguate.
// Two indexes: Product:description/cosine and Product:description/euclidean
await db.records.vectorSearch({
labels: ['Product'],
propertyName: 'description',
queryVector: vec,
similarityFunction: 'cosine' // required — otherwise 422 Unprocessable Entity
})
curl -X POST https://api.rushdb.com/api/v1/ai/search \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"labels": ["Product"],
"propertyName": "description",
"queryVector": [0.1, 0.2, 0.3],
"similarityFunction": "cosine"
}'
Error Reference
| HTTP | Cause |
|---|---|
409 | An index for this (label, propertyName, sourceType, similarityFunction, dimensions) tuple already exists |
422 | Property is not string type |
422 | Embedding model is not configured on the server |
See also
- Semantic Search — search using managed or external indexes
- Write Records with Vectors — attach vectors at write time
- Bring Your Own Vectors — external embedding workflow