Image input
Three of our models read images. Send one as a base64 data URI inside the usual content array and it works with the OpenAI SDK unchanged. Two details decide whether your first attempt succeeds — and one of them looks exactly like the model being blind.
Which models read images
We probed every model in the catalog with the same two images rather than trusting the capability listed upstream — and the two disagreed. Only these three answered from what was actually in the picture:
| Model | Image input |
|---|---|
qwen3.8-27b | Yes — verified |
kimi-k2.6 | Yes — verified |
kimi-k2.7-code | Yes — verified |
glm-5.2 · glm-4.7-flash | No |
deepseek-v4-flash · deepseek-v4-pro | No |
The list is also machine-readable: capabilities.vision on GET /v1/models. Sending an image to any other model is refused with vision_not_available before the request leaves us, and costs you nothing.
Why we refuse instead of forwarding
Two of the models that cannot read images accept the payload anyway, ignore the picture, and answer confidently from the text alone. You would be billed in full for a guess, with nothing in the response saying the image was dropped. A refusal you can act on is worth more than a fluent paragraph about an image nobody looked at.
Sending an image
Use the standard content array: one text part and one image_url part. The URL must be a base64 data URI — a remote http(s) link is not fetched, and we reject it up front with image_url_not_supported rather than letting it fail deeper in the stack.
import base64
from openai import OpenAI
client = OpenAI(base_url="https://api.clfaigateway.dev/v1", api_key="sk-gw-...")
with open("screenshot.png", "rb") as f:
b64 = base64.b64encode(f.read()).decode()
resp = client.chat.completions.create(
model="qwen3.8-27b",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "What does this screen show?"},
{"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{b64}"}},
],
}],
max_completion_tokens=400,
)
print(resp.choices[0].message.content)An empty answer is almost always the token budget
These models think before they answer, and the thinking is spent from max_completion_tokens. Set it too low and the entire budget goes into reasoning, leaving content empty — which is indistinguishable from a model that cannot see. This cost us an hour before we recognised it. Keep the budget at 400 tokens or more when sending images.
Limits and billing
- Billing: images are charged as input tokens at the model’s normal input rate. There is no separate image fee.
- Token cost: roughly 64 tokens per 256×256 tile of the image — an image up to 256×256 costs about 66 tokens, a 512×512 one about 258. Those tokens count against the context window like any other input.
- Request size: up to 10 MB per request body, base64 included. Larger bodies are rejected with
request_too_large. - Multiple images: allowed — add more
image_urlparts to the same content array. - Privacy: images are request content, and we log metadata only — never prompt or response content. See the data policy.
What we have not measured
Our probe establishes that a model genuinely reads the image it is given. It does not establish OCR quality: small print, scanned documents and dense charts are untested, so we do not sell them as features. Test those on your own material first — a failed request that produced nothing costs $0, and count_tokens is free.