Embeddings
An embedding is a numeric fingerprint of a piece of text or an image. The model reads the input and returns a vector, a fixed-length list of numbers, positioned so that inputs with similar meaning land close together and unrelated inputs land far apart. Once your content lives as vectors, “find the most relevant document” becomes “find the nearest vector”, which databases do quickly and at scale. Embeddings are the substrate underneath semantic search, retrieval-augmented generation, clustering, deduplication and classification.
The flinq models
Section titled “The flinq models”flinq’s models are specialized on the European construction (AEC) domain, with
German as the deepest-trained language. flinq-embed-2.0 embeds text and
images into one 2048-dim vector space; flinq-embed-1.5-beta embeds text
into 1024 dimensions. See Models for the full spec tables.
Because every vector is L2-normalized, cosine similarity is just a dot product, and you can compare two vectors by multiplying and summing. If your store wants inner product, you can use it directly.
Request
Section titled “Request”/v1/embeddings is OpenAI-compatible, so the OpenAI SDKs work unchanged — set
the base_url and pass a flinq model id (required; see Models).
curl https://api.flinq.ai/v1/embeddings \ -H "Authorization: Bearer flq_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "model": "flinq-embed-2.0", "input": ["Stahlbetonfertigteildecke d = 20 cm", "Bewehrungsanschluss BSt 500"], "dimensions": 512 }'from openai import OpenAI
client = OpenAI(base_url="https://api.flinq.ai/v1", api_key="flq_your_key_here")r = client.embeddings.create( model="flinq-embed-2.0", input=["Stahlbetonfertigteildecke d = 20 cm", "Bewehrungsanschluss BSt 500"], dimensions=512,)vectors = [d.embedding for d in r.data] # order matches the input orderimport OpenAI from 'openai';
const client = new OpenAI({ baseURL: 'https://api.flinq.ai/v1', apiKey: 'flq_your_key_here',});
const r = await client.embeddings.create({ model: 'flinq-embed-2.0', input: ['Stahlbetonfertigteildecke d = 20 cm', 'Bewehrungsanschluss BSt 500'], dimensions: 512,});const vectors = r.data.map((d) => d.embedding);The response mirrors the OpenAI layout: data[i].embedding is the L2-normalized
vector for input[i], and usage carries the billed token count.
Queries and documents
Section titled “Queries and documents”On flinq-embed-2.0, a search query and the passages it should find are
embedded differently. Embed your corpus as documents (the default) and your
search queries with "input_type": "query". A query can also name a task,
which selects the instruction the model was trained with for that kind of
search:
lv_matching: a bill-of-quantities (LV) position matched against a cost or product catalogue.image_search: a text query against figures, drawings and diagrams.retrieval: general passages. The default.
The instruction is added on the server and is not billed. input_type and
task are flinq fields outside the OpenAI schema, so the OpenAI SDKs send them
through extra_body:
curl https://api.flinq.ai/v1/embeddings \ -H "Authorization: Bearer flq_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "model": "flinq-embed-2.0", "input": "Mauerwerk Kalksandstein, d = 17,5 cm, Innenwand", "input_type": "query", "task": "lv_matching" }'docs = client.embeddings.create( model="flinq-embed-2.0", input=["KS-Mauerwerk 17,5 cm, Steinfestigkeitsklasse 20", "Porenbeton-Planstein 17,5 cm"],) # input_type defaults to "document"
query = client.embeddings.create( model="flinq-embed-2.0", input="Mauerwerk Kalksandstein, d = 17,5 cm, Innenwand", extra_body={"input_type": "query", "task": "lv_matching"},)const docs = await client.embeddings.create({ model: 'flinq-embed-2.0', input: ['KS-Mauerwerk 17,5 cm, Steinfestigkeitsklasse 20', 'Porenbeton-Planstein 17,5 cm'],}); // input_type defaults to "document"
const query = await client.embeddings.create({ model: 'flinq-embed-2.0', input: 'Mauerwerk Kalksandstein, d = 17,5 cm, Innenwand', // flinq fields outside the OpenAI schema input_type: 'query', task: 'lv_matching',});flinq-embed-1.5-beta has no trained tasks: it accepts input_type, but a
task returns 422. /v1/search and
/v1/rerank treat their query as a query
automatically and take the same optional task.
Images
Section titled “Images”flinq-embed-2.0 embeds images into the same vector space as text, so a
text query finds the detail drawing, section or diagram it describes. Send each
image as an OpenAI chat content part inside input, with the image inline as a
base64 data URL. Texts and images can be mixed in one request.
# The body goes through a file: a base64 image is too long for a command-line argument.printf '{"model": "flinq-embed-2.0", "input": [ {"type": "image_url", "image_url": {"url": "data:image/png;base64,%s"}}, "Fensteranschluss mit Kompriband, Vertikalschnitt"]}' "$(base64 < fensteranschluss.png | tr -d '\n')" > body.json
curl https://api.flinq.ai/v1/embeddings \ -H "Authorization: Bearer flq_your_key_here" \ -H "Content-Type: application/json" \ -d @body.jsonimport base64import requests
def image_part(path, mime="image/png"): with open(path, "rb") as f: b64 = base64.b64encode(f.read()).decode() return {"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}}
r = requests.post( "https://api.flinq.ai/v1/embeddings", headers={"Authorization": "Bearer flq_your_key_here"}, json={ "model": "flinq-embed-2.0", "input": [image_part("fensteranschluss.png"), image_part("attika.png")], },)image_vectors = [d["embedding"] for d in r.json()["data"]]
# Text-to-image search: the query side with the image_search task.q = requests.post( "https://api.flinq.ai/v1/embeddings", headers={"Authorization": "Bearer flq_your_key_here"}, json={ "model": "flinq-embed-2.0", "input": "Anschluss Fenster an Mauerwerk mit Dichtband", "input_type": "query", "task": "image_search", },)query_vector = q.json()["data"][0]["embedding"]scores = [sum(a * b for a, b in zip(query_vector, v)) for v in image_vectors]import { readFile } from 'node:fs/promises';
async function imagePart(path, mime = 'image/png') { const b64 = (await readFile(path)).toString('base64'); return { type: 'image_url', image_url: { url: `data:${mime};base64,${b64}` } };}
const r = await fetch('https://api.flinq.ai/v1/embeddings', { method: 'POST', headers: { Authorization: 'Bearer flq_your_key_here', 'Content-Type': 'application/json', }, body: JSON.stringify({ model: 'flinq-embed-2.0', input: [await imagePart('fensteranschluss.png'), await imagePart('attika.png')], }),});const imageVectors = (await r.json()).data.map((d) => d.embedding);The rules for image inputs:
- Formats. PNG, JPEG and WebP, as
data:image/png;base64,...(orimage/jpeg,image/webp). Remotehttp(s)URLs are not fetched and return422. - Size. Up to 5 MB and 50 megapixels per image, up to 16 images per request. The model sees each image scaled down, keeping the aspect ratio, to at most 802,816 pixels (about 0.8 MP, for example 896 × 896). EXIF rotation is respected.
- Billing. An image is billed as its visual tokens: one token per 32 × 32-pixel block after scaling, at most 784 tokens per image, at the same EUR 0.40 per 1M tokens as text.
- Images are documents. Embed them with the default
input_type; an image with"input_type": "query"returns422. The query side of an image search is text, with"task": "image_search". - Not on
/v1/search. Hybrid search is text-only because its BM25 leg needs text. To rerank images, pass them asdocumentstoflinq-rerank-2.0on/v1/rerank.
flinq-embed-1.5-beta is text-only and returns 422 for image inputs.
Parameters
Section titled “Parameters”| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
model | string | yes | — | Model id: flinq-embed-2.0 or flinq-embed-1.5-beta. Unknown ids return 404 with the list of available models; discover them via GET /v1/models. |
input | string or array | yes | — | Text to embed, or an array of texts and, on flinq-embed-2.0, image parts ({"type": "image_url", ...}, see Images). Up to 256 inputs per request on both embedding models; over the cap returns 422. Each text may have up to 8,192 tokens — an over-limit text returns 422 unless the request opts into "truncation": "auto"; nothing is ever cut silently. |
input_type | string | no | "document" | "document" for what is searched, "query" for search queries. Images are always documents. |
task | string | no | "retrieval" | flinq-embed-2.0 only: lv_matching, image_search or retrieval. Selects the query instruction; ignored for documents. On the 1.5 beta it returns 422. |
dimensions | integer | no | native | Matryoshka output size: 2048, 1024, 512, 256 or 128 on 2.0 (native 2048); 1024, 768, 512, 256 or 128 on the 1.5 beta (native 1024). The server truncates the native vector to the first N components and re-normalizes. Smaller = cheaper storage and faster ANN at a small quality cost. Same price — billing counts input tokens, not output size. |
truncation | string | no | "error" | "error" rejects an over-limit text with 422; "auto" truncates it at the limit and reports its index in truncated. Images are never truncated. |
encoding_format | string | no | "float" | Accepted for OpenAI compatibility; only "float" (JSON number arrays) is currently served. |
Headers. Authorization: Bearer flq_... (required). Optionally send an
Idempotency-Key header: if you retry a request with the same key (e.g. after a
network timeout), the charge is deduplicated — you are never billed twice for the
same logical request.
Response fields.
| Field | Type | Description |
|---|---|---|
data[i].index | integer | Position of this vector in your input array — order is always preserved. |
data[i].embedding | float[] | L2-normalized vector (dimensions long). Cosine similarity = plain dot product. |
model | string | The model that served the request. |
usage.total_tokens | integer | Billed tokens, counted server-side with the model’s own tokenizer (not whitespace), plus the visual tokens of any images. |
truncated | integer[] | Indices of texts truncated under "truncation": "auto". |
warnings | string[] | Human-readable notes, for example a truncation. |
Billing. tokens(input) × EUR 0.40 / 1M, with images counted as their
visual tokens. The query instruction selected by task is not billed. A request
rejected before inference (unknown model, over the input cap, bad schema) costs
nothing.
Matryoshka dimensions
Section titled “Matryoshka dimensions”Both embedding models are trained so that the first N components of the native vector
remain a faithful, self-contained embedding on their own. That is the
Matryoshka property: nest a smaller vector inside the larger one. Request a
smaller dimensions and flinq truncates to that prefix and re-normalizes.
The tradeoff is concrete. A 256-dim slice of a flinq-embed-2.0 vector is an
eighth of the storage and roughly an eighth of the nearest-neighbor work of the
full 2048 dimensions, for a drop in retrieval quality. You pick the point on
that curve that fits your index budget and latency target, without re-embedding
your corpus: store the full vector once, and you can slice it later.
Two failure modes flinq fixes
Section titled “Two failure modes flinq fixes”General-purpose embedding APIs are trained on broad web text. On construction language they fail in two specific, repeatable ways.
Compound-word vocabulary smear
Section titled “Compound-word vocabulary smear”German builds long compounds: Stahlbetonfertigteildecke,
Dämmerungsschalter, Bewehrungsanschluss. A generic tokenizer shreds these
into subword fragments and the model averages them into a vague blur, losing the
precise object the compound names. flinq is tuned on this vocabulary, so the
compound resolves to the thing it actually denotes rather than to a smeared
average of its parts.
Exact-match blindness
Section titled “Exact-match blindness”In construction, a single token is the difference between right and wrong.
DN 100 and DN 150 are different pipe diameters. C20/25 and C25/30 are
different concrete strength classes. XC4 is a specific exposure class. Generic
models treat these as near-identical because they look alike as strings, and
they confidently return the wrong one. flinq is trained to keep these
distinctions sharp. For the cases where lexical precision must be absolute, the
hybrid search endpoint adds a BM25 leg that anchors
on the exact code or unit.