Embeddings and Hybrid Search
Base embeddings and semantic search (4 providers, vector storage, /api/embed/*) are covered on the "Embeddings (semantic search)" page. This page is about what's built on top: hybrid search, which combines vector similarity with an ordinary SQL filter in a single request.
The problem it solves
/api/embed/search tells you "row 42 in articles is the closest match" — but you still need a second query to fetch that row's actual columns. And there was no way to combine "most similar to X" with "where category = 'news'" in one call — you had to do it in two round trips.
POST /api/vector/search
One HTTP call does all three steps:
- Embeds the query text (cached for repeated queries);
- Searches the embeddings table for the top-K most similar rows;
- Runs
SELECT * FROM <table> WHERE pk IN (...)with an optional extraWHERE— and returns the rows together with their similarity scores.
curl -X POST $URL/api/vector/search -H "X-Token: $TOK" -d '{
"table": "products",
"text": "warm winter jacket",
"k": 10,
"where": "category = ''outerwear'' AND in_stock = true",
"candidate_multiplier": 3
}'
{
"hits": [
{"pk": "482", "score": 0.91, "row": {"id": 482, "name": "Winter parka", "category": "outerwear"}}
]
}
An important nuance: rank first, filter second
The pipeline gets the top-K by similarity first, then applies WHERE — not the other way around. If you ask for k=10 and WHERE filters out 7 of them, you get 3 results, not 10 from a wider candidate pool. That's what candidate_multiplier is for: if you expect WHERE to cut the results roughly in half, set it to 3 — the engine then fetches the top-30 by similarity first, and filters after.
What's not there yet: a vector function inside arbitrary SQL (ORDER BY similarity(emb, 'x') in a normal SELECT) — that needs a real function in the SQL parser, tracked separately and not part of the current hybrid endpoint.