Chat Completions · image
Sends an image along with the question and receives the answer as text. Same endpoint as text calls, with `content` as a list of parts.
Updated on Aug 10, 2026
The same address as text calls, with one difference in the body: the message content stops being a string and becomes a list of parts, one of them carrying the image. Route, authentication and response envelope are identical.
The guided use — what an image costs, which model to choose, how to turn a document into fields in your database — is in Image understanding. This page is the contract: what goes in the body and what comes back.
https://api.hinow.ai/v1/chat/completionsBearerSends one or more images along with the question and returns the answer as text.
Parâmetros
modelstring· bodyobrigatórioNamespaced model id. `hinow/hivision` is the one dedicated to image reading; the language models accept images too.
messagesarray· bodyobrigatórioConversation messages, as in any call. The call is stateless: the context is whatever you send.
messages[].contentstring | array· bodyobrigatórioA string, when the message is text only. A **list of parts**, when there is an image.
content[].typestring· bodyobrigatório`"text"` or `"image_url"`, depending on the part.
content[].textstring· bodyThe request, in the `text` part.
content[].image_url.urlstring· bodyPublic address of the image, or a `data:` URL with the file in base64.
streamboolean· bodyReturns tokens as they come out, over SSE. `usage` arrives in the last event.
temperaturenumber· bodyControls sampling variation. For field extraction, use low values.
max_tokensinteger· bodyCeiling on response tokens. When it cuts, `finish_reason` comes back as `"length"`.
response_formatobject· body`{"type": "json_object"}` requests content in JSON. Validate the fields in the application.
Respostas
{
"id": "chatcmpl-R2dSu3p8...",
"object": "chat.completion",
"model": "hinow/hivision",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "O gráfico mostra..." },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 389, "completion_tokens": 42, "total_tokens": 431 }
}It is the only structural change compared with a text call. Where a string used to go, a list goes — and the order of the parts is the order in which the model reads them. Put the request before the image when it defines what to look at.
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "O que este gráfico mostra? Responda em português, em uma frase."},
{"type": "image_url", "image_url": {"url": "https://exemplo.com/grafico.png"}}
]
}]| Part | Field that matters | What for |
|---|---|---|
text | text | The request, in natural language |
image_url | image_url.url | The image, by address or embedded |
The role is still user
The list of parts applies to the user message. General instructions — response language, output format — still work better in a system message, as a string, as in any text call.
The url field accepts both, and both work:
- Public address — the API fetches the image, so it must be reachable over the internet. An address on
localhostor behind a login will not do. - The embedded image — a
data:URL with the file in base64. It does not depend on publishing the image anywhere, and it is the path for the file the user has just uploaded.
curl https://api.hinow.ai/v1/chat/completions \
-H "Authorization: Bearer $HINOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "hinow/hivision",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "O que este gráfico mostra? Responda em português, em uma frase."},
{"type": "image_url", "image_url": {"url": "https://exemplo.com/grafico.png"}}
]
}]
}'import base64, os, requests
with open("grafico.png", "rb") as f:
embutida = "data:image/png;base64," + base64.b64encode(f.read()).decode()
resposta = requests.post(
"https://api.hinow.ai/v1/chat/completions",
headers={"Authorization": f"Bearer {os.environ['HINOW_API_KEY']}"},
json={
"model": "hinow/hivision",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "O que este gráfico mostra? Responda em português, em uma frase."},
{"type": "image_url", "image_url": {"url": embutida}},
],
}],
},
).json()
print(resposta["choices"][0]["message"]["content"])
print(resposta["usage"]["prompt_tokens"]) # 389 na imagem de testeimport { readFile } from "node:fs/promises";
const bytes = await readFile("grafico.png");
const embutida = `data:image/png;base64,${bytes.toString("base64")}`;
const resposta = await fetch("https://api.hinow.ai/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HINOW_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "hinow/hivision",
messages: [{
role: "user",
content: [
{ type: "text", text: "O que este gráfico mostra? Responda em português, em uma frase." },
{ type: "image_url", image_url: { url: embutida } },
],
}],
}),
}).then((r) => r.json());
console.log(resposta.choices[0].message.content);A URL the API cannot reach returns 502
The error does not say the address is invalid — it says the call failed. If one specific public host fails while others work, the problem is the reachability of that address, not a lack of URL support. When in doubt, send the image embedded as base64.
| Format | Result |
|---|---|
| PNG | Works |
| JPEG | Works |
| WebP | Works |
The declared type is not what decides
A PNG sent as data:image/jpeg;base64,... was read normally — it is the file content that counts. Even so, declare the right type: it is what keeps your own code honest when the format changes.
The same envelope as a text call: choices[0].message.content with the answer, finish_reason with the reason it stopped and usage with the consumption. There is no separate field for the image — it already went into prompt_tokens.
The count has a behaviour worth knowing beforehand: the same picture sent at 512px (19 KB) and at 1024px (55 KB) consumed 389 prompt tokens both times. Shrinking the image does not make the call cheaper, and it may cost the legibility of small text — send it at a resolution where a person would be able to read it.
Add another image_url part to the same list. It works for comparing two versions of a document, checking before and after, or asking for a conclusion about a set.
"content": [
{"type": "text", "text": "Estes dois documentos são da mesma empresa? Responda sim ou não e por quê."},
{"type": "image_url", "image_url": {"url": "https://exemplo.com/nota-a.png"}},
{"type": "image_url", "image_url": {"url": "https://exemplo.com/nota-b.png"}}
]Each image adds to the bill: with two at 1024px, the prompt went to 660 tokens — against 389 for a single one.
With stream: true the response arrives over server-sent events, exactly as in a text call: the chunks come in choices[0].delta.content, usage only appears in the last event and the stream ends with data: [DONE].
The image changes nothing here — it is consumed whole on input, before the first token comes out. What changes is the wait until that first token, which is longer than in a text-only call.
| Situation | Code | What to do |
|---|---|---|
| The API cannot reach the image address | 502 | Send the image embedded as base64 |
content as a string, with an image expected | 400 | Switch to a list of parts |
A model that does not serve Chat Completions, such as hinow/hiembed | 400 | Check the modality at GET https://api.hinow.ai/v1/models |
| Model does not exist or is not enabled | 404 | Check the namespaced id |
| Per-minute limit exceeded | 429 | Retry with progressive backoff |
| A call with no image at all | 200 | It answers normally, but usage comes back zeroed |
Confirm your account's contract
Availability, prices and modalities evolve. GET https://api.hinow.ai/v1/models returns the current catalog — but treat the category field as informative: models listed only as text_to_text accepted images in testing.

