Search by meaning, in milliseconds

You send embeddings. The engine builds the index, keeps it current while you write to it, and answers similarity queries up to a billion vectors.

No credit card · Free tier never expires

12 ms
p99 latency k-NN search with a metadata filter, measured server side
1 B
vectors per index Sharded automatically as writes arrive
99.2 %
recall Exact neighbours found, on our validation sets
40
languages Multilingual search inside a single namespace

One call, one answer

There is no console to learn before you can evaluate this. The whole product is a request and what comes back from it.

Below: a k-NN query with a structured filter, and the response it returns. The filter is applied inside the graph traversal, which is why the score of the last result is still high.

Request
POST /v1/indexes/handbook/query
{
  "vector": [0.021, -0.118, 0.334, ...],
  "k": 3,
  "filter": {
    "lang": "en",
    "updated_at": { "$gte": "2026-01-01" }
  }
}
Response 11.4 ms
{
  "matches": [
    { "id": "doc_9f2c", "score": 0.918 },
    { "id": "doc_41ab", "score": 0.902 },
    { "id": "doc_7d30", "score": 0.887 }
  ],
  "took_ms": 11.4
}

The index becomes the product you maintain

It starts well. A library, a graph in memory, good results. Then the corpus grows, filtered queries begin returning almost nothing, reindexing needs a window nobody can schedule, and the p99 drifts while the median looks fine.

None of that is a search problem. It is an operations problem that search handed you — and it lands on the person who least wanted it.

Four things you stop doing

Writes are queryable immediately

No reindexing step, no maintenance window. A vector inserted at 14:02 is a candidate for the query at 14:02.

Filters do not cost you recall

Metadata predicates are evaluated during graph traversal, not after it. On a filter matching 1 % of the corpus, recall@20 stays at 0.97 instead of collapsing to 0.61.

The tail stays flat

p50 and p99 are measured continuously and published. A latency budget can be set per query, so a slow neighbourhood degrades the result rather than the response time.

Changing embedding model is not a migration

Reindex in place, behind the same alias, while the old graph keeps serving. Dimension is declared at creation, not baked into your application.

Three steps

  1. 01

    Send your vectors

    Batch upload from any embedding provider, up to 4096 dimensions. Namespaces keep datasets apart inside one index.

  2. 02

    The graph builds itself

    A multi-layer HNSW graph, sharded automatically, compacted in the background. Nothing to schedule and nothing to tune before you have a reason to.

  3. 03

    Query by similarity

    k nearest neighbours with cosine, dot product or Euclidean distance, a score on every result, and structured filters applied inside the traversal.

In detail

Vector indexing

Ingestion, HNSW construction and continuous updates

  • Batch upload, up to 4096 dimensions
  • Insert, update, delete
  • Multi-layer graph, automatic sharding
  • In-place reindexing when you change model

Similarity queries

k-NN search and control over the recall/latency trade-off

  • Cosine, dot product, Euclidean
  • k set per query, score on every result
  • Adjustable exploration parameter
  • Cursor-based pagination

Metadata filters

Structured constraints inside the vector search

  • Strings, numbers, booleans, dates, arrays
  • Equality, ranges, membership, negation
  • Evaluated during traversal
  • No post-filtering recall loss

Performance

Measured latency, capacity and scaling

  • p50 and p99 measured continuously
  • Shards added without downtime
  • Read replicas per region
  • Quotas per API key

Operations

Regions, security and integration

  • 8 regions, data residency of your choosing
  • Encryption at rest and in transit
  • Scoped API keys, exportable logs
  • HTTP and gRPC, with Python, TypeScript and Go libraries

Filtering inside the index instead of after it was the whole difference. Our recall stopped depending on how narrow the filter was.

Head of engineering, Corpus Legal — read the case study

Asked before adopting

Which embedding model do I have to use?

Any of them. The engine stores and compares vectors; it never produces them. You declare the dimension when you create the index, and you can change model later with an in-place reindex.

Do I have to tune HNSW before I start?

No. The defaults are sized for a general corpus and most indexes never move off them. When you do need to, m and efConstruction are exposed on the Team tier, and efSearch can be set per query.

What happens to queries while I am writing?

They keep being served. Writes are merged into the graph continuously, and compaction runs in the background. There is no read-only phase.

Can I combine vector search with a normal WHERE clause?

That is exactly what metadata filters are. The difference from bolting a filter on afterwards is that the predicate steers the traversal, so a narrow filter no longer destroys recall.

How do I know the recall I am actually getting?

Bring a validation set of known-correct neighbours and the console reports recall@k against it. Recall you have not measured is a guess, whoever is hosting the index.

Is this the right tool for a million vectors?

Often not. Below roughly a million vectors with light filtering, pgvector inside the Postgres you already run is the pragmatic answer, and we say so on the comparison page.

Create your first index

A million vectors, one index, no card and no expiry. If your corpus outgrows it, nothing has to be rebuilt.