Embeddings
Turn text into vectors to compare meaning: the call, what the response carries, the limits and the cost.
Updated on Aug 10, 2026
hinow/hiembed turns text into a list of numbers — the embedding. Texts that mean the same thing produce similar lists, even without a single word in common. That is what makes it possible to search by meaning instead of searching for an exact term.
It answers at POST https://api.hinow.ai/v1/embeddings, with the same key and the same base URL as the rest of the API. Pricing is per input token — US$ 0.05 per million.
An embedding does not answer questions or write text: it measures closeness in meaning. Every use grows from that.
- Semantic search — finding the right answer even when the user writes it in other words.
- RAG — selecting the passages that go into the language model's context before it answers.
- Recommendation — "similar to this one" across products, articles, job posts or tickets.
- Clustering and triage — grouping messages about the same subject, with no hand-written list of categories.
- Deduplication — finding the ticket already opened, the repeated record, the question that already has an answer.
The text goes in input and the model id in model:
curl https://api.hinow.ai/v1/embeddings \
-H "Authorization: Bearer $HINOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "hinow/hiembed",
"input": "Como redefinir minha senha?"
}'import os, requests
resposta = requests.post(
"https://api.hinow.ai/v1/embeddings",
headers={"Authorization": f"Bearer {os.environ['HINOW_API_KEY']}"},
json={"model": "hinow/hiembed", "input": "Como redefinir minha senha?"},
).json()
vetor = resposta["data"][0]["embedding"]
print(len(vetor), resposta["usage"]["prompt_tokens"]) # 1024 10const resposta = await fetch("https://api.hinow.ai/v1/embeddings", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HINOW_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "hinow/hiembed",
input: "Como redefinir minha senha?",
}),
});
const { data, usage } = await resposta.json();
console.log(data[0].embedding.length, usage.prompt_tokens); // 1024 10package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
type respostaEmbeddings struct {
Data []struct {
Embedding []float64 `json:"embedding"`
Index int `json:"index"`
} `json:"data"`
Usage struct {
PromptTokens int `json:"prompt_tokens"`
} `json:"usage"`
}
func main() {
corpo, _ := json.Marshal(map[string]any{
"model": "hinow/hiembed",
"input": "Como redefinir minha senha?",
})
req, _ := http.NewRequest("POST", "https://api.hinow.ai/v1/embeddings", bytes.NewReader(corpo))
req.Header.Set("Authorization", "Bearer "+os.Getenv("HINOW_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var r respostaEmbeddings
if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
panic(err)
}
fmt.Println(len(r.Data[0].Embedding), r.Usage.PromptTokens) // 1024 10
}The response, with the vector trimmed to fit the page:
{
"object": "list",
"model": "hinow/hiembed",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [-0.006066, 0.021058, -0.054192, -0.038931, "... mais 1020 números"]
}
],
"usage": { "prompt_tokens": 10, "total_tokens": 10 }
}Three fields matter:
data[].embedding— the vector, with 1024 numbers.data[].index— the position ininputof the text that produced that vector. It is how you recover the order when sending a batch.usage.prompt_tokens— what was charged.
The cost does not come in the response
Unlike /v1/images, /v1/embeddings does not return a cost field. The bill comes from usage.prompt_tokens multiplied by the model price: US$ 0.05 per million tokens. Record the usage of each call if you need to reconcile consumption.
Already using the OpenAI SDK?
The endpoint is compatible: switch the baseURL to https://api.hinow.ai/v1, use your HINOW key and set model to hinow/hiembed. The rest of the code stays the same. See the client SDKs.
It is 1024 numbers between -1 and 1. None of them means anything on its own: there is no "position 42 is the finance topic". The vector only takes on meaning when compared with another vector from the same model.
hiembed vectors already arrive normalised — the norm is exactly 1. That has a practical consequence which saves code: the dot product between two vectors is already the cosine similarity, with no need to divide by any norm.
vetor = resposta["data"][0]["embedding"]
len(vetor) # 1024
vetor[:4] # [-0.006066, 0.021058, -0.054192, -0.038931]
sum(x * x for x in vetor) ** 0.5 # 1.0 -> já normalizadoThe same text does not return the same vector
Twenty calls with the same sentence produced slightly different vectors — the largest difference in a single component was 0.00016, and the similarity between them stayed at 0.999999. For comparison that is irrelevant; for equality it is not: do not use the vector as a cache key, and do not look for duplicates by testing whether two vectors are equal. Compare by the text, or by similarity with a threshold.
Cosine similarity ranges from -1 to 1: the higher it is, the closer the meanings. Since the vectors arrive normalised, the whole function fits on one line:
def similaridade(a, b):
return sum(x * y for x, y in zip(a, b))
# Com numpy, sobre muitos vetores de uma vez:
# import numpy as np
# scores = np.array(vetores) @ np.array(consulta)const similaridade = (a: number[], b: number[]) =>
a.reduce((soma, valor, i) => soma + valor * b[i], 0);func similaridade(a, b []float64) float64 {
var soma float64
for i := range a {
soma += a[i] * b[i]
}
return soma
}The numbers below are the real output of those calls — look at the distance between the bands, not at the values themselves:
| Pair of texts | Similarity |
|---|---|
| "Delivery takes 3 to 5 business days." × "The delivery takes three to five business days." | **0.9598** |
| "cartão de crédito" × "cartao de credito" | 0.8950 |
| "Delivery takes 3 to 5 business days." × "We accept Pix and bank slips." | 0.4872 |
A full paraphrase lands above 0.95 without repeating a single word in the same form. A missing accent barely changes the result — which handles much of Portuguese search with no text processing at all. And two different subjects drop to around 0.48.
What an embedding does not capture
"The cat climbed on the roof" and "The roof climbed on the cat" have a similarity of 0.9558: same words, swapped roles, opposite meaning. An embedding measures subject, not assertion. Do not use it to check facts, to tell a negation from its affirmative counterpart, or to compare numbers, dates and identifiers — that is what exact filters are for, and they are cheaper.
input accepts a list. The response carries one item per text, and index says which one each vector came from — do not trust the array order, trust index:
textos = [
"Para trocar a senha, abra Configurações › Segurança.",
"O prazo de entrega padrão é de 3 a 5 dias úteis.",
"Aceitamos cartão de crédito, Pix e boleto bancário.",
]
resposta = requests.post(
"https://api.hinow.ai/v1/embeddings",
headers={"Authorization": f"Bearer {os.environ['HINOW_API_KEY']}"},
json={"model": "hinow/hiembed", "input": textos},
).json()
vetores = [item["embedding"] for item in sorted(resposta["data"], key=lambda i: i["index"])]
print(len(vetores), resposta["usage"]["prompt_tokens"])const textos = [
"Para trocar a senha, abra Configurações › Segurança.",
"O prazo de entrega padrão é de 3 a 5 dias úteis.",
"Aceitamos cartão de crédito, Pix e boleto bancário.",
];
const resposta = await fetch("https://api.hinow.ai/v1/embeddings", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HINOW_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ model: "hinow/hiembed", input: textos }),
});
const { data, usage } = await resposta.json();
const vetores = [...data]
.sort((a, b) => a.index - b.index)
.map((item) => item.embedding as number[]);The gain is in the network, not in the price: a token costs the same on its own or in a batch. In a measurement of 500 short texts in a single call, the whole response took 2.0 s — against 500 round trips had they been separate calls.
A text has a size ceiling
Each input item fits around 8,192 tokens. An 8,003-token text went through; above that the call fails with a provider error, not with a 400 explaining why. Split the document into passages before sending — which you will want to do anyway for search to work well.
The default is 1024 dimensions. The dimensions parameter asks for a smaller vector, which reduces memory and index size — useful when there are millions of vectors stored.
The saving charges a price in quality, and it is not linear. On the same search, over the same base:
dimensions | Observed result |
|---|---|
1024 (default) | First result correct, with a 0.04 margin over the second |
512 | First result correct, same order as the default |
128 | **First result wrong** — the right passage falls out of the top |
Ask for the dimension, but check what came back
dimensions is not always honoured: in one measurement, a request for 256 returned a 1024 vector. Vectors of different sizes in the same index cannot be compared — read len(embedding) in the response and reject anything that does not match the index.
| Situation | Response |
|---|---|
| A chat model on this endpoint | 400 — Model '...' does not support embeddings |
input missing or an empty list | 400 — input is required |
| A model that does not exist | 404 — Model '...' not found |
encoding_format: "base64" | 502 — not supported; use the default number format |
Empty text is not refused
"input": "" returns 200, with a valid vector, and is charged. Nothing in the response indicates the text was empty. Filter empty strings before assembling the batch — otherwise they enter the index and start showing up in searches.
US$ 0.05 per million input tokens. Only what you send counts; there is no output charge, because the output is the vector.
In the numbers of a real case: indexing 10 thousand passages of roughly 200 tokens each gives 2 million tokens — US$ 0.10, once. After that, each search costs the embedding of the question: a 10-token sentence comes to US$ 0.0000005. The cost of a semantic search is in the storage and the server, not in the API.
Keep the vectors
Generating the embedding again costs again. A generated vector is your data: store it alongside the source text, with the model id and the dimension. That is what lets you reprocess only what changed — and find out, on the day you switch models, what needs redoing.

