Documentation
Images & vision
Send images to vision-capable models through one API: image_url content parts with https URLs or base64 data URIs, up to 20MB per request.
Models with the vision capability accept images alongside text in the same request. Images go in as content parts on a user message — a public https URL or a base64 data: URI — using the standard OpenAI-compatible shape. INFRO translates provider-specific image formats, so the same request body works on every vision model in the catalog.
Image requests are capped at 20MB, and a single message can carry several images. Images are billed as input tokens under each provider's conversion rules; the exact charge comes back in usage.cost on every response.
Vision is input only. No model on INFRO returns images — responses are always text or tool calls. There is no image generation endpoint.
Image content parts
When a user message contains images, its content field is an array of parts instead of a plain string. Text and image parts can be interleaved in any order, and the model reads them in sequence. The full message format is covered in the chat completions reference.
typestringrequired"image_url"for an image part,"text"for a text part.image_urlobjectrequired- Wrapper for the image reference. Present only on
image_urlparts. image_url.urlstringrequired- An https URL, or a
data:URI with base64-encoded bytes, e.g.data:image/png;base64,iVBORw0.... Requests are capped at 20MB of image data. textstring- The text of a
"text"part.
Prefer an https URL when the image is publicly reachable — the serving provider fetches it directly and your request body stays small. Use a data: URI when the file is local or behind authentication. A request over the 20MB image limit fails with a 400 invalid_request_error — see error handling.
Describe an image
A complete request against a vision model. The example uses anthropic/claude-sonnet-5; any model with the vision capability works identically.
curl https://api.infro.io/v1/chat/completions \
-H "Authorization: Bearer $INFRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-5",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "Describe this image in two sentences."},
{
"type": "image_url",
"image_url": {"url": "https://upload.example.com/photos/harbor.jpg"}
}
]
}
]
}'Send a local image
For local files, base64-encode the bytes and pass them as a data: URI with the correct MIME type. Resize before encoding — base64 adds roughly 33% overhead, and an oversized image costs more tokens without improving answers.
import base64
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.infro.io/v1",
api_key=os.environ["INFRO_API_KEY"],
)
with open("receipt.png", "rb") as f:
encoded = base64.b64encode(f.read()).decode()
response = client.chat.completions.create(
model="openai/gpt-5.1",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Extract the merchant, date, and total from this receipt."},
{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{encoded}"},
},
],
}
],
)
print(response.choices[0].message.content)Multiple images in one message
Send several image parts in one message to compare, cross-reference, or batch-describe. Interleave text parts to label each image — models handle before/after prompts better when every image is introduced. The 20MB limit applies to the request as a whole, and every image adds input tokens.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.infro.io/v1",
api_key=os.environ["INFRO_API_KEY"],
)
response = client.chat.completions.create(
model="google/gemini-3-pro",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "First screenshot, before the deploy:"},
{"type": "image_url", "image_url": {"url": "https://cdn.example.com/before.png"}},
{"type": "text", "text": "Second screenshot, after the deploy:"},
{"type": "image_url", "image_url": {"url": "https://cdn.example.com/after.png"}},
{"type": "text", "text": "List every visual difference."},
],
}
],
)
print(response.choices[0].message.content)Which models support vision
Vision support is declared per model: look for "vision" in the capabilities array returned by GET /v1/models, or browse the model catalog. Frontier models such as openai/gpt-5.1, anthropic/claude-sonnet-5, google/gemini-3-pro, and google/gemini-2.5-flash all accept images.
curl -s https://api.infro.io/v1/models \
-H "Authorization: Bearer $INFRO_API_KEY" \
| jq -r '.data[] | select(.capabilities | index("vision")) | .id'Sending an image to a model without the capability returns a 400 invalid_request_error. If you use fallbacks, keep every model in the chain vision-capable — a text-only fallback rejects the request rather than silently dropping your images.
Cost and sizing tips
- Images are billed as input tokens. Each provider converts image dimensions to tokens by its own rules, charged at the model's prompt rate. The exact amount appears in
usage.cost. - Resize before you send. Most models downscale large images internally, so a 4000px photo usually answers no better than a 1024px resize — it just costs more tokens and upload time.
- Keep repeated images in stable positions. When the same images ride along in chat history across turns, prompt caching can discount them; reordering or editing earlier parts breaks the cache.
- Pair vision with structured outputs for extraction. For receipts, forms, and screenshots, combine an image part with a strict JSON schema via structured outputs to get typed fields back instead of prose.