Skip to content

Hybrid search

POST /v1/search ranks a set of candidate documents against a query. It runs two retrievers and fuses their results:

  • A dense retriever that scores by embedding similarity, so it understands meaning and paraphrase.
  • A BM25 lexical retriever that scores by exact term overlap, so it rewards the precise code, number or unit appearing verbatim.

The two ranked lists are combined with reciprocal rank fusion (RRF), which merges by rank position rather than by raw score. That keeps the fusion robust when the two retrievers live on different scales, and lets each leg promote a document the other missed.

You can isolate a leg with the mode parameter: hybrid (default), dense, or bm25.

Pure dense retrieval is strong at meaning and weak at exactness. In construction, exactness is the job. The lexical leg is what makes that reliable:

  • Standard and position codes. A query that names a specific standard reference (a DIN code in Germany, an ÖNORM in Austria) or catalog code must surface the line that carries that exact code, not a semantically adjacent one.
  • Position numbers. Bidding documents across Europe are organized by position numbers (01.02.030, Positionsnummern in a German LV). These are identifiers, not prose, and BM25 matches them precisely.
  • Units and dimensions. DN 100, C25/30, 230 V, 60 s, 5 bis 1000 lx: the lexical leg ensures the literal value carries weight, so the result with the right number ranks above the one that merely reads similarly.

Dense retrieval supplies recall and paraphrase tolerance. BM25 supplies the exact-match precision the domain demands. RRF gives you both in one ranked list.

On flinq-embed-2.0, the dense leg embeds query as a search query and the candidates as documents, automatically. Pass task to pick the instruction the query is read with: lv_matching when the query is an LV position and the candidates are catalogue entries, retrieval (the default) for general passages. The instruction is not billed.

/v1/search is text-only, because the BM25 leg needs text to match on. For image search, embed your images with /v1/embeddings and the text query with "task": "image_search", or pass image documents to flinq-rerank-2.0 on /v1/rerank.

/v1/search is a flinq-native endpoint (not part of the OpenAI schema), so Python and JavaScript call it with a plain HTTP client. model is required.

curl https://api.flinq.ai/v1/search \
-H "Authorization: Bearer flq_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"model": "flinq-embed-2.0",
"query": "Dämmerungsschalter 230 V AC, Einstellbereich 5 bis 1000 lx",
"documents": [
"Dämmerungsschalter 230 V AC, Einstellbereich 5-1000 lx, Einschaltverzögerung 60 s",
"Bewegungsmelder Aufputz 360 Grad, Reichweite 8 m",
"Zeitschaltuhr digital, Wochenprogramm, 230 V"
],
"top_k": 3,
"mode": "hybrid"
}'
{
"object": "search",
"model": "flinq-embed-2.0",
"mode": "hybrid",
"data": [
{
"index": 0,
"score": 0.0328,
"text": "Dämmerungsschalter 230 V AC, Einstellbereich 5-1000 lx, Einschaltverzögerung 60 s"
},
{
"index": 2,
"score": 0.0161,
"text": "Zeitschaltuhr digital, Wochenprogramm, 230 V"
},
{
"index": 1,
"score": 0.0159,
"text": "Bewegungsmelder Aufputz 360 Grad, Reichweite 8 m"
}
],
"usage": { "prompt_tokens": 84, "total_tokens": 84 }
}

Each hit carries the original index into your documents array, the fused score, and the text. Scores are RRF scores, so read them as a ranking rather than as a calibrated probability.

ParameterTypeRequiredDefaultDescription
modelstringyes—Model id used for the dense leg: flinq-embed-2.0 or flinq-embed-1.5-beta. Unknown ids return 404.
querystringyes—The search query. Queries are never truncated — one over the model’s input limit (8,192 tokens) returns 422.
documentsstring[]yes—Candidate pool to rank: texts only. Per-model cap: 256 documents on both embedding models, 422 over it. Ranking happens within this pool only — /v1/search is stateless and stores nothing; bring the candidates on every call.
taskstringno"retrieval"flinq-embed-2.0 only: lv_matching, image_search or retrieval. The query instruction for the dense leg; the documents are embedded as documents. On the 1.5 beta it returns 422.
top_kintegerno10Number of hits returned. Values above len(documents) return the whole pool.
modestringno"hybrid""hybrid" (dense + BM25, RRF-fused), "dense" (embeddings only), "bm25" (lexical only). See the section below for when to isolate a leg.
dimensionsintegernonativeMatryoshka slice for the dense leg: 2048/1024/512/256/128 on 2.0 (native 2048), 1024/768/512/256/128 on the 1.5 beta (native 1024). Ignored by the BM25 leg.
truncationstringno"error""auto" truncates over-limit documents at the limit and lists them in truncated; the query is never truncated.

Headers. Authorization: Bearer flq_... (required); Idempotency-Key (optional) deduplicates the charge on retries.

Response fields.

FieldTypeDescription
data[i].indexintegerPosition of the hit in your documents array.
data[i].scorefloatRanking score — its meaning depends on mode: RRF fusion value (hybrid, typically ~0.01–0.03, read as rank not probability), cosine similarity (dense, comparable across requests of the same model), or raw BM25 (bm25, corpus-relative).
data[i].textstringThe document text, echoed back for convenience.
usage.total_tokensintegerBilled tokens: tokens(query) + tokens(documents) — the whole pool is embedded, regardless of top_k or mode.

Billing. (tokens(query) + Σ tokens(documents)) × EUR 0.40 / 1M. Large candidate pools dominate the cost — pre-filter cheaply where you can, then rank the shortlist here.

Hybrid is the default because it wins on mixed workloads, but both legs are available on their own via mode — the endpoint does not force BM25 on you:

  • "mode": "dense" — pure embedding similarity, scores are cosine values. Use this when you run your own lexical layer (your search engine’s BM25, a keyword filter, a code-exact index) and only want flinq’s semantic ranking to fuse yourself, or when your queries are natural-language paraphrases with no exact codes.
  • "mode": "bm25" — pure lexical ranking, no embedding call. Useful as a cheap baseline or when the query is nothing but exact identifiers.
curl https://api.flinq.ai/v1/search \
-H "Authorization: Bearer flq_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"model": "flinq-embed-2.0",
"query": "Leuchte schaltet bei Daemmerung automatisch",
"documents": ["...", "..."],
"mode": "dense"
}'

Dense-mode scores are plain cosine similarities (comparable across requests of the same model), while hybrid scores are RRF rank fusions.