Skip to main content

Embeddings, rerank and redaction

Besides chat completions, Serenity Edge serves three building blocks for search, retrieval-augmented generation and privacy. Each has its own route and its own model, and each is billed on input tokens only.

EmbeddingsRerankRedaction
EndpointPOST /v1/embeddingsPOST /v1/rerankPOST /v1/redact
FormatOpenAI's embeddings APIThe rerank API of Cohere and JinaThe privacy filter's own format
Modelqwen3-embedding-0.6bqwen3-reranker-0.6bprivacy-filter (optional)
ResultA vector of 1024 numbers per inputA relevance score per documentThe text with its personal data masked, and where each item was found
Billed tokensReported in usage.prompt_tokensEstimated, returned in usageEstimated
Per requestUp to 512 inputs and about 300,000 tokensUp to 1000 documents, a query of up to 4096 characters and about 300,000 tokensUp to 256 inputs and 192 KiB
AccessSerenity Edge API keysSerenity Edge API keysSerenity Edge API keys

See Access for the keys, Errors for what can go wrong and Pricing and limits for billing.

Models​

Each route serves one model. Each id also has an alias with its publisher's prefix (qwen/qwen3-embedding-0.6b, qwen/qwen3-reranker-0.6b, openai/privacy-filter) that you can send instead; the model of an embeddings or rerank answer is always the id without the prefix.

The table is read from GET /v1/models?format=openai when this page is published and again when you open it. Prices are in US dollars per million input tokens.

Model idInputEndpoint
privacy-filteralso openai/privacy-filter$0.009/v1/redact
qwen3-embedding-0.6balso qwen/qwen3-embedding-0.6b$0.008/v1/embeddings
qwen3-reranker-0.6balso qwen/qwen3-reranker-0.6b$0.008/v1/rerank

Read from the API (GET /v1/models?format=openai) when this page was published, on 6 November 2026, 19:49 UTC.

These models are not chat models: the default catalogue of GET /v1/models does not list them. In the OpenAI-style list (?format=openai) each one carries an endpoints array with its route, a pricing.input per million tokens and a pricing.output of 0:

{
"id": "qwen3-embedding-0.6b",
"object": "model",
"created": 1792000000,
"owned_by": "serenity",
"pricing": { "input": 0.008, "output": 0 },
"endpoints": ["/v1/embeddings"]
}

A model is served only on its own route. Sending qwen3-reranker-0.6b to /v1/embeddings is refused with HTTP 400, and a chat or decision model id with HTTP 404 (see Errors).

Embeddings​

POST /v1/embeddings turns text into vectors for semantic search, clustering, deduplication and retrieval-augmented generation. It takes OpenAI's embeddings request and returns OpenAI's answer, so the official OpenAI SDKs work as they are. qwen3-embedding-0.6b is multilingual and returns vectors of 1024 dimensions.

Request​

FieldRequiredDescription
modelYesqwen3-embedding-0.6b, or its alias qwen/qwen3-embedding-0.6b.
inputYesThe text to embed: a non-empty string, or an array of up to 512 non-empty strings. Token ids are refused: send the text itself (see Errors).
encoding_formatNofloat or base64, as in OpenAI's API. Vectors always come back as arrays of numbers, also when base64 is asked for; the official OpenAI SDKs accept either form. dimensions is not supported: every vector has 1024 numbers.

curl​

curl https://api.serenityedge.ai/v1/embeddings \
-H "Authorization: Bearer $SERENITY_EDGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3-embedding-0.6b",
"input": [
"Refunds are issued within 5 working days.",
"Orders ship from Valencia within 48 hours."
],
"encoding_format": "float"
}'

Python​

The OpenAI Python library's client.embeddings.create works with the Serenity Edge base URL:

from openai import OpenAI

client = OpenAI(
base_url="https://api.serenityedge.ai/v1",
api_key="YOUR_SERENITY_EDGE_API_KEY",
)

response = client.embeddings.create(
model="qwen3-embedding-0.6b",
input=[
"Refunds are issued within 5 working days.",
"Orders ship from Valencia within 48 hours.",
],
encoding_format="float",
)

vectors = [item.embedding for item in response.data]
print(len(vectors), len(vectors[0])) # 2 1024
print(response.usage.prompt_tokens)

Node.js​

import OpenAI from "openai";

const client = new OpenAI({
baseURL: "https://api.serenityedge.ai/v1",
apiKey: process.env.SERENITY_EDGE_API_KEY,
});

const response = await client.embeddings.create({
model: "qwen3-embedding-0.6b",
input: ["Refunds are issued within 5 working days.", "Orders ship from Valencia within 48 hours."],
encoding_format: "float",
});

console.log(response.data.length, response.data[0].embedding.length); // 2 1024

The reply​

{
"object": "list",
"data": [
{ "object": "embedding", "index": 0, "embedding": [0.0213, -0.0472, 0.0091, 0.0368] },
{ "object": "embedding", "index": 1, "embedding": [-0.0154, 0.0327, 0.0619, -0.0083] }
],
"model": "qwen3-embedding-0.6b",
"usage": { "prompt_tokens": 19, "total_tokens": 19 }
}

The vectors are shortened to four numbers here; each one has 1024. The token count is illustrative.

  • data has one entry per input; index is the position of the input in input.
  • model is qwen3-embedding-0.6b, also when the request used the alias.
  • usage.prompt_tokens is the number of tokens the model read, and what you are billed for. Each input counts its own tokens, so a batch costs the same as sending its inputs one by one.

Compare vectors with cosine similarity, and embed your queries and your documents with the same model. In our measurements of 13 October 2026, embedding 25 texts took about 71 ms of server time, without the network.

Usage and billing​

You are billed on usage.prompt_tokens at the model's input price, shown in the table above; output is not billed. The cost of a request is usage.prompt_tokens × the price per million tokens ÷ 1,000,000.

If an answer ever comes without usage, the request is billed on an estimate instead: a quarter of the characters of each string, rounded up. For the two inputs above, 41 and 42 characters give ceil(41 / 4) + ceil(42 / 4) = 11 + 11 = 22 tokens.

Limits​

  • At most 512 inputs per request.
  • At most 300,000 tokens per request, estimated as a quarter of the characters of all the inputs. A larger request is refused with HTTP 400 (see Errors): split it into several requests.
  • Each input is read up to its first 8,192 tokens. The rest is ignored, without an error, and is not billed: split longer texts into chunks before embedding them.
  • At most 4 MB per request body.

Errors​

Besides the errors shared by the three routes, the request is refused with HTTP 400 and code: "invalid_request_error" when a field is wrong. param names the field and message says what to fix:

parammessage
input'input' is required.
input'input' must be a non-empty string, or an array of non-empty strings.
input'input' must be text: this model does not take token ids. Send the strings themselves (in LangChain's OpenAIEmbeddings, set check_embedding_ctx_length=False).
input'input' takes at most 512 inputs per request.
input'input' holds about 300001 tokens (a quarter of the characters); this API takes at most 300000 per request. Split it into several requests. (with the request's own estimate)
encoding_format'encoding_format' must be float or base64.

Rerank​

POST /v1/rerank scores how relevant each document in a list is to a query, in the format of the rerank APIs of Cohere and Jina. Use it after a first, fast retrieval step (vector or keyword search) to put the best passages first before you send them to a chat model. qwen3-reranker-0.6b is multilingual.

Request​

FieldRequiredDescription
modelYesqwen3-reranker-0.6b, or its alias qwen/qwen3-reranker-0.6b.
queryYesThe query, a non-empty string of at most 4096 characters.
documentsYes1 to 1000 documents: strings, or objects with a string text (other members of such an object are accepted).
top_nNoReturn only the top_n most relevant documents. An integer of at least 1.
return_documentsNoAccepted for compatibility with Cohere and Jina clients, but results never repeat the documents: use index to find each one in your list.

curl​

curl https://api.serenityedge.ai/v1/rerank \
-H "Authorization: Bearer $SERENITY_EDGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3-reranker-0.6b",
"query": "Which city is the capital of Spain?",
"documents": [
"Madrid is the capital and largest city of Spain.",
"Valencia is known for paella and the City of Arts and Sciences.",
"Málaga lies on the Costa del Sol, in Andalucía."
],
"top_n": 2
}'

Python​

The OpenAI SDKs have no rerank method, so send the request as plain JSON over HTTPS. With httpx, which the OpenAI Python library already installs:

import os

import httpx

response = httpx.post(
"https://api.serenityedge.ai/v1/rerank",
headers={"Authorization": f"Bearer {os.environ['SERENITY_EDGE_API_KEY']}"},
json={
"model": "qwen3-reranker-0.6b",
"query": "Which city is the capital of Spain?",
"documents": [
"Madrid is the capital and largest city of Spain.",
"Valencia is known for paella and the City of Arts and Sciences.",
"Málaga lies on the Costa del Sol, in Andalucía.",
],
"top_n": 2,
},
timeout=60,
)
response.raise_for_status()

reranked = response.json()
for result in reranked["results"]:
print(result["index"], result["relevance_score"])
print(reranked["usage"]["prompt_tokens"]) # 67

The reply​

{
"results": [
{
"index": 0,
"relevance_score": 0.982
},
{
"index": 2,
"relevance_score": 0.031
}
],
"model": "qwen3-reranker-0.6b",
"usage": { "prompt_tokens": 67, "total_tokens": 67 }
}

The scores in this example are illustrative.

  • results holds the top_n most relevant documents, or every document when top_n is not set, sorted from the most to the least relevant. Each result has the index of its document in documents and its relevance_score, between 0 and 1: the higher, the more relevant. Use index to find the document in your own list.
  • model is qwen3-reranker-0.6b, also when the request used the alias.
  • usage is the token estimate described below, which the API writes into every answer. It is what you are billed for.
  • The answer may carry other members of Cohere's format, such as id or meta. Ignore the ones you do not use.

In our measurements of 13 October 2026, reranking 25 documents took about 28 ms of server time, without the network.

Usage and billing​

The model reads the query together with each document, so the tokens are estimated per document and added up:

tokens = sum over documents of ceil((characters(query) + characters(document)) / 4)

Characters are Unicode code points, not bytes. For the request above, the query has 35 characters and the documents 48, 63 and 47 ("Málaga lies on the Costa del Sol, in Andalucía." is 47 characters, 49 bytes in UTF-8):

DocumentCharacters with the queryTokens
035 + 48 = 8321
135 + 63 = 9825
235 + 47 = 8221
Total67

The answer reports "usage": {"prompt_tokens": 67, "total_tokens": 67} and you are billed 67 tokens at the model's input price, shown in the table above. Every document is scored and billed, also when top_n returns fewer; and the query counts once per document, so keep queries short when you rerank many documents.

Limits​

  • At most 1000 documents per request, and a query of at most 4096 characters.
  • At most 300,000 tokens per request, estimated as in Usage and billing: a quarter of the characters of the query and each document, the query counted once per document. A larger request is refused with HTTP 400 (see Errors).
  • The model reads each document together with the query up to 8,192 tokens, its prompt template included. The rest of a longer document is ignored, without an error.
  • At most 4 MB per request body.

Errors​

Besides the errors shared by the three routes, the request is refused with HTTP 400 and code: "invalid_request_error" when a field is wrong:

parammessage
query'query' is required.
query'query' must be a non-empty string.
query'query' takes at most 4096 characters.
documents'documents' is required.
documents'documents' must be a non-empty array of strings or of objects with a string 'text'.
documents'documents' takes at most 1000 documents per request.
documents'documents' with the query hold about 300001 tokens (a quarter of the characters); this API takes at most 300000 per request. Split it into several requests. (with the request's own estimate)
top_n'top_n' must be an integer of at least 1.
return_documents'return_documents' must be a boolean.

Redaction​

POST /v1/redact finds personal data in text (names, postal addresses, email addresses, phone numbers, dates, account numbers, URLs and secrets such as passwords) and masks it, so that you can remove it before the text reaches a model, a log or a third party. It serves privacy-filter, the open privacy filter model published by OpenAI, in the filter's own request and answer format.

Your text is processed in memory only

The text you send to /v1/redact, and the masked text and spans the API returns, are processed in memory only. They are never written to disk or logs, are discarded once the answer is sent, and are never used for training. See Data residency.

Request​

FieldRequiredDescription
inputsYesThe text to redact: a string, or an array of 1 to 256 strings.
placeholdersNotrue (the default) to replace each item found with a placeholder that names its category, such as [PERSON], [EMAIL] or [PHONE]; false to remove the item from the text.
categoriesNoLook only for these categories, as an array of strings such as ["private_email", "private_phone"]. Omit it, or send an empty array, to look for every category.
modelNoprivacy-filter, or its alias openai/privacy-filter. When it is left out, privacy-filter answers.

Categories​

The filter knows eight categories. Their names are the values of categories in the request and of entity_group in the answer.

CategoryFindsIn textplaceholder
private_personNames of people[PERSON]<PRIVATE_PERSON>
private_addressPostal addresses[ADDRESS]<PRIVATE_ADDRESS>
private_emailEmail addresses[EMAIL]<PRIVATE_EMAIL>
private_phonePhone numbers[PHONE]<PRIVATE_PHONE>
private_dateDates, such as a date of birth[DATE]<PRIVATE_DATE>
account_numberAccount numbers, such as an IBAN[ACCOUNT_NUMBER]<ACCOUNT_NUMBER>
private_urlURLs, such as a personal web page[URL]<PRIVATE_URL>
secretSecrets, such as a password or an API key[SECRET]<SECRET>
Unknown names are ignored

Write the names exactly as above, in lower case. A name the filter does not know, PRIVATE_PERSON in capitals included, is ignored without an error: a request whose categories holds only unknown names answers HTTP 200 with the text unchanged and no spans.

curl​

curl https://api.serenityedge.ai/v1/redact \
-H "Authorization: Bearer $SERENITY_EDGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": "Hi, I am Ana García. Write to [email protected] or call +34 600 000 000.",
"placeholders": true
}'

Python​

As for rerank, send plain JSON over HTTPS:

import os

import httpx

response = httpx.post(
"https://api.serenityedge.ai/v1/redact",
headers={"Authorization": f"Bearer {os.environ['SERENITY_EDGE_API_KEY']}"},
json={
"inputs": "Hi, I am Ana García. Write to [email protected] or call +34 600 000 000.",
"placeholders": True,
},
timeout=60,
)
response.raise_for_status()

redacted = response.json()
print(redacted["text"]) # Hi, I am [PERSON]. Write to [EMAIL] or call [PHONE].
for span in redacted["spans"]:
print(span["entity_group"], span["start"], span["end"])

The reply​

For a string in inputs, the answer is one object:

{
"text": "Hi, I am [PERSON]. Write to [EMAIL] or call [PHONE].",
"spans": [
{ "entity_group": "private_person", "score": 1.0, "word": "Ana García", "start": 9, "end": 19, "placeholder": "<PRIVATE_PERSON>" },
{ "entity_group": "private_email", "score": 1.0, "word": "[email protected]", "start": 30, "end": 52, "placeholder": "<PRIVATE_EMAIL>" },
{ "entity_group": "private_phone", "score": 1.0, "word": "+34 600 000 000", "start": 61, "end": 76, "placeholder": "<PRIVATE_PHONE>" }
]
}
  • text is the input with each item found masked: replaced with its placeholder, or removed when placeholders is false.

  • spans has one entry per item found:

    • its category (entity_group, such as private_person, private_email or private_phone);
    • the model's confidence (score, from 0 to 1);
    • the text found (word);
    • its position in the input you sent (start and end, in characters, end excluded);
    • the category's tag (placeholder, such as <PRIVATE_PERSON>).

    Ignore the fields you do not use.

For an array in inputs, the answer is an array with one such object per input, in the same order:

[
{
"text": "My name is [PERSON].",
"spans": [{ "entity_group": "private_person", "score": 1.0, "word": "Ana García", "start": 11, "end": 21, "placeholder": "<PRIVATE_PERSON>" }]
},
{
"text": "Call [PHONE]",
"spans": [{ "entity_group": "private_phone", "score": 1.0, "word": "+34 600 000 000", "start": 5, "end": 20, "placeholder": "<PRIVATE_PHONE>" }]
}
]

The answer has no model and no usage: it is returned exactly as the filter writes it.

warning

A model finds the personal data, so it can miss an item or mask text that is not personal. Check the results on a sample of your own texts, and do not make redaction the only safeguard for data you are not allowed to disclose.

Usage and billing​

The tokens are estimated over the whole request: a quarter of the characters of all the inputs together, rounded up once.

tokens = ceil(characters of all inputs / 4)

Characters are Unicode code points, not bytes. The string of the curl example has 77 characters (78 bytes in UTF-8), so it is billed ceil(77 / 4) = 20 tokens. The two inputs of the array example, "My name is Ana García." and "Call +34 600 000 000", have 22 and 20 characters: ceil(42 / 4) = 11 tokens. You are billed these tokens at the model's input price, shown in the table above. Since the answer carries no usage, compute the estimate on your side if you need it per request.

Limits​

  • At most 256 inputs per request.
  • At most 192 KiB (196,608 bytes) per request body, field names and JSON syntax included. A larger body is refused with HTTP 413; split long documents into several requests.

Errors​

Besides the errors shared by the three routes, the request is refused with HTTP 400 and code: "invalid_request_error" when a field is wrong:

parammessage
inputs'inputs' is required.
inputs'inputs' must be a string or a non-empty array of strings.
inputs'inputs' takes at most 256 inputs per request.
placeholders'placeholders' must be a boolean.
categories'categories' must be an array of strings.

Access​

The three routes can be used only with Serenity Edge API keys, the keys Substrate AI issues directly to customers and partner platforms. Today they are issued on request: see Enterprise or write to [email protected].

  • Any other key, such as a key from a Serenity Star account, gets HTTP 403 with code: "permission_denied" and a message that names the route's API: This API key cannot use the embeddings API. Use a Serenity Edge API key. (rerank or redact on the other two routes).
  • The three models are not part of the OpenRouter onboarding, which covers the chat models only.

Errors​

Errors use the same error object as chat completions. These can happen on all three routes; the field errors of each route are listed in its own section (embeddings, rerank, redaction).

Statuscodemessage and whenWhat to do
400invalid_request_errorThe request body is not a JSON object.; 'model' is required. (not on /v1/redact); 'model' must be a string.Fix the request. Do not retry as is.
400invalid_request_errorThe model 'qwen3-reranker-0.6b' is not served on /v1/embeddings.: the model exists but is served on another of these routes (param: "model").Use the model of the route (see Models).
400invalid_request_errorThe model server rejected the request. Check the input sizes and parameters.: the model could not process the request as sent, for example an input too long for it or a parameter it does not accept (param is null).Shorten the inputs or check the parameters. Do not retry as is.
401invalid_api_keyMissing or invalid API key. Send it as 'Authorization: Bearer <key>'.Check the Authorization header and the key.
403permission_deniedThis API key cannot use the embeddings API. Use a Serenity Edge API key. (rerank, redact): the key is not a Serenity Edge API key (see Access).Use a Serenity Edge API key.
404model_not_foundThe model 'orion-pro' does not exist or you do not have access to it.: an unknown id, or a chat or decision model (param: "model").Check GET /v1/models?format=openai.
413request_too_largeThe request body exceeds the maximum size of 4194304 bytes. (196608 bytes on /v1/redact).Send fewer or shorter inputs per request.
429rate_limit_exceededThe service is at capacity for this model. Retry after the number of seconds in the Retry-After header.: your rate limit is exceeded, or the model is temporarily at capacity.Wait for Retry-After, then retry with backoff.
500upstream_errorThe model service returned an unexpected response. Please retry.Retry with backoff.
500timeoutThe request timed out before the model produced a response. (after 600 seconds)Retry with fewer inputs per request.
503upstream_unavailableThe model service is temporarily unavailable. Please retry later.Retry later; check the status page if it persists.

The messages are written by Serenity Edge: the model's own error text never reaches you. The three routes are idempotent, so retrying a request that failed with 429 or a 5xx is safe.

Pricing and limits​

  • Input tokens only. Output is not billed and there is no cached input price. Embeddings are billed on the usage.prompt_tokens the model reports; rerank and redaction on the estimates described in their sections, with characters counted as Unicode code points. Prices are in US dollars per million tokens, shown live in the table above and on Pricing and limits.
  • Per request. Embeddings: 512 inputs and about 300,000 tokens, each input read up to its first 8,192 tokens. Rerank: 1000 documents, a query of 4096 characters and about 300,000 tokens, each document read with the query up to 8,192 tokens. Redaction: 256 inputs and a body of 192 KiB. The other two routes take a body of up to 4 MB, as chat completions.
  • Rate limits and capacity. Requests are admitted per model as on chat completions. Over your limit or when the model is at capacity, the API answers HTTP 429 with Retry-After; handle it as described in Errors and rate limits.
  • Data. The same data policy as chat completions applies (see Data residency): request and response bodies are processed in memory, never written to disk or logs and never used for training.

Next steps​