Indexes
Single-Field Indexes
Without an index, every filtered query performs a full strand scan — O(n) over every record in the strand. That is fine for small datasets, but once a strand grows past 100k records, unindexed filters become the dominant latency source. A single-field index reduces that to an O(k) lookup where k is the number of matching records.
When to Add an Index
Add a single-field index when you have a strand with more than a few thousand records and you frequently filter on a specific field using equality or prefix operators. Good candidates are fields like plan, status, region, tenant_id, or any foreign-key-style reference field that your queries filter on in a tight loop.
You do not need an index if the strand is small, if you only ever retrieve records by their content hash directly, or if your filters always use operators that cannot use indexes (see the table below).
Creating an Index
Send a POST request to the indexes endpoint for the agent that owns the strand. The name field is a stable identifier you choose — it must be unique within the agent. The field value is the JSON key inside each strand record that you want to index.
POST /v1/agents/users/indexes
Authorization: Bearer spx_root_YOUR_ROOT_KEY
Content-Type: application/json
{
"name": "idx_plan",
"field": "plan"
}{
"name": "idx_plan",
"field": "plan",
"type": "single",
"status": "building"
}The index build runs asynchronously in the background. The agent continues serving queries and writes normally while the build progresses. You can check status at any time with the list endpoint.
Checking Build Status
GET /v1/agents/users/indexes Authorization: Bearer spx_root_YOUR_ROOT_KEY
{
"indexes": [
{
"name": "idx_plan",
"field": "plan",
"type": "single",
"status": "ready"
}
]
}Possible status values:
| Status | Meaning |
|---|---|
building | The agent is scanning existing strand records to populate the index. New writes are indexed in real-time during this phase. |
ready | The index is fully built and active. Queries on the indexed field now use it automatically. |
error | The build failed. Delete the index entry and recreate it to retry. |
building state, queries still work — they fall back to a full scan for records not yet indexed. Once the build completes, all subsequent queries use the index. There is no read window where results could be incorrect.Querying with an Index
You do not need to name the index in your query. The query engine inspects the filter fields and automatically selects the best available index. If an index exists for the filtered field and the operator is index-compatible, the engine uses an index scan instead of a full scan.
POST /v1/agents/users/query
Authorization: Bearer spx_root_YOUR_ROOT_KEY
Content-Type: application/json
{
"type": "scan",
"limit": 50,
"filter": {
"field": "plan",
"op": "eq",
"value": "pro"
}
}To confirm whether a query is using the index, run an explain query first — see the Query Explain page.
Operator Compatibility
Not all filter operators can use an index. The engine uses an index only when the operator is one of the index-compatible set.
| Operator | Uses index? | Notes |
|---|---|---|
eq | Yes | Most selective — exact match on the indexed value. |
in | Yes | Performs one index lookup per value in the list, then unions results. |
starts_with | Yes | Prefix range scan on the index — efficient for string prefixes. |
contains | No | Substring match requires scanning all values; index cannot help. |
ends_with | No | Suffix match requires a full scan. |
like | No | Pattern match — full scan only. |
is_null | No | Null checks scan all records. |
between | No | Range queries require a sorted index structure not yet supported. |
gt / lt | No | Inequality range — full scan. |
gt/lt for a field with a small number of distinct values (e.g. numeric tier), consider rewriting it as an in filter with explicit values. That turns a full scan into an index scan.Deleting an Index
Deleting an index is immediate. Queries on the affected field fall back to full scans from the moment of deletion. Write overhead for that field is also removed instantly.
DELETE /v1/agents/users/indexes/idx_plan Authorization: Bearer spx_root_YOUR_ROOT_KEY
{
"deleted": "idx_plan"
}Multiple Indexes on One Strand
You can create multiple single-field indexes on the same strand — one per frequently-filtered field. Each index is independent. If a query filters on two indexed fields simultaneously, consider a composite index instead, which can be more efficient than two separate single-field lookups.
# Index on 'plan'
POST /v1/agents/users/indexes
{ "name": "idx_plan", "field": "plan" }
# Index on 'region'
POST /v1/agents/users/indexes
{ "name": "idx_region", "field": "region" }full_scan on your most frequent queries, then add indexes for those fields only.