Embeddings are live

PrivatAI now serves an OpenAI-compatible embeddings endpoint at /api/v1/embeddings. It is available on Essential and Professional, costs €0.10 per million input tokens, and output is free.

This closes the most common gap people ran into with our API: you could run a conversation on EU infrastructure, but the moment you wanted the model to answer from your own documents, you had to go somewhere else for the embeddings — usually to a US provider, which defeated the point.

What an embedding actually is

An embedding turns a piece of text into a list of numbers — a vector — that represents its meaning. Texts that mean similar things end up close together, even when they share no words at all.

"Die Kündigungsfrist beträgt drei Monate" and "notice period is three months" contain nothing in common at the character level. As vectors, they sit almost on top of each other. That is the whole trick, and it is what makes semantic search work where keyword search fails.

You store those vectors in a vector index. When a question comes in, you embed the question too, find the nearest stored vectors, and you have the passages that are actually about the question.

Why this endpoint is stateless — and why that is the point

We do not store your text. We do not store the vectors. There is no upload, no document store, no index on our side, and nothing to delete afterwards, because we never held it.

flowchart LR
    A[Your documents<br/>your servers] -->|text| B[PrivatAI<br/>/v1/embeddings]
    B -->|vectors| C[Your index<br/>your database]
    C --> D[Your application]

This is a deliberate design choice, not a missing feature. The alternative — you upload your contracts, we host and index them — would mean we hold your most sensitive material, and "we delete it on request" is a much weaker promise than "it was never ours." Your documents stay in your systems. The vectors go into your index. We see the text for exactly as long as it takes to compute the vector, and inference runs on Scaleway in France, the same EU provider and region as our chat models.

The trade-off is honest: you need somewhere to put the vectors. pgvector on a Postgres you already run is the boring, correct answer for most teams. Qdrant, Weaviate or a managed EU vector database work equally well.

What to build with it

Search over your own material that actually understands the question. Handbooks, contracts, wikis, ticket histories. Someone asks "how long do I have to cancel?" and finds the clause that says "Kündigungsfrist," without having guessed the word.

RAG — answers grounded in your documents. Retrieve the most relevant passages, pass them to /api/v1/chat/completions as context, and cite them in the answer. The model stops guessing and starts quoting, and you can check where every claim came from.

Deduplication. Find near-identical support tickets, CRM records or product descriptions that differ in wording. Exact matching misses all of them.

Classification and routing. Embed a handful of examples per category, then route incoming mail or tickets to the nearest one. Often good enough without training anything, and cheaper than asking a model per message.

Recommendation. "More articles like this one," computed from meaning rather than from tags someone forgot to maintain.

Using it

The endpoint is OpenAI-compatible, so existing tooling works by changing the base URL.

curl -X POST https://privatai.com/api/v1/embeddings \
  -H "Authorization: Bearer privat_your_key" \
  -H "Content-Type: application/json" \
  -d '{
        "model": "qwen3-embedding-8b",
        "input": ["Die Kündigungsfrist beträgt drei Monate.", "Notice period is three months."],
        "dimensions": 1024
      }'

With the OpenAI SDK:

from openai import OpenAI

client = OpenAI(api_key="privat_your_key", base_url="https://privatai.com/api/v1")

res = client.embeddings.create(
    model="qwen3-embedding-8b",
    input=["Passage one", "Passage two"],
    dimensions=1024,
)
vectors = [row.embedding for row in res.data]

With the Vercel AI SDK:

import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { embedMany } from 'ai';

const privatai = createOpenAICompatible({
  name: 'privatai',
  baseURL: 'https://privatai.com/api/v1',
  apiKey: process.env.PRIVATAI_API_KEY,
});

const { embeddings } = await embedMany({
  model: privatai.textEmbeddingModel('qwen3-embedding-8b'),
  values: ['Passage one', 'Passage two'],
});

Batch up to 2,048 strings and 1 MB of text per request — indexing a document set is far faster and cheaper in batches than one string at a time.

The detail that trips people up: dimensions

We serve two models:

Model Dimensions Can be shortened?
qwen3-embedding-8b (default) 4096 Yes — 32…4096
bge-multilingual-gemma2 3584 No

Both are multilingual and handle German and English well. The difference that matters in practice is that pgvector's HNSW index refuses vectors above 2000 dimensions.

So if you are indexing in Postgres — which most teams are — a raw 4096- or 3584-dim vector cannot be indexed at all. qwen3-embedding-8b is Matryoshka-trained, meaning it was built so the vector can be cut short without wrecking quality. Ask for dimensions: 1024 and it fits, indexes, and searches fast. Smaller vectors are also cheaper to store and quicker to compare.

bge-multilingual-gemma2 cannot be shortened. Ask it for custom dimensions and you get a clear 400 rather than a silently degraded index — we would rather fail loudly than let you build a search that quietly returns worse results for a year.

One rule, and it is not negotiable: never mix models or dimension sizes inside one index. Vectors from different models are not comparable, and neither are vectors from the same model at different sizes. Changing either means re-embedding everything you have stored. Decide once, at the start.

What it costs

€0.20 per million input tokens, on both models, on Essential and Professional. Embeddings have no output, so there is nothing else to bill.

To put that in perspective: a 200-page handbook is roughly 100,000 tokens — about two cent to embed, once. Queries are a handful of tokens each. For most internal knowledge-base use, embedding is not the line item you will notice.

Start

The full reference, including error codes and limits, is in the API documentation. You need an API key from your dashboard and a base URL change. Nothing else about your setup has to move … and your documents do not have to move at all.