Core APIsVision
Vision
Image inputs on multimodal models — URLs or base64, with size and count limits.
Multimodal chat models accept images alongside text in the messages
array. Same shape as OpenAI's vision input — type: 'image_url'
content parts with a URL or a base64 data URL.
At a glance
| Endpoint | POST /v1/chat/completions (same as text-only chat) |
| Recommended model | mistral/pixtral-large-latest |
| Max images per request | 20 |
| Max total size | 20 MB |
| Detail levels | low, high, auto (default: auto) |
Image from URL
import { createCleverRouter } from '@cleverrouter/sdk';
const cr = createCleverRouter({ apiKey: process.env.CLEVERROUTER_API_KEY! });
const res = await cr.chat({
model: 'mistral/pixtral-large-latest',
messages: [
{
role: 'user',
content: [
{ type: 'text', text: 'What do you see in this invoice?' },
{
type: 'image_url',
image_url: {
url: 'https://example.com/invoice.png',
detail: 'high',
},
},
],
},
],
});
console.log(res.choices[0]?.message.content);Image from local file (base64)
For non-public images (your own uploads, screenshots, scanned documents) inline the bytes as a data URL:
import { readFileSync } from 'node:fs';
const data = readFileSync('./invoice.png').toString('base64');
const res = await cr.chat({
model: 'mistral/pixtral-large-latest',
messages: [
{
role: 'user',
content: [
{ type: 'text', text: 'Extract invoice number and total.' },
{
type: 'image_url',
image_url: { url: `data:image/png;base64,${data}` },
},
],
},
],
response_format: { type: 'json_object' },
});
const parsed = JSON.parse(res.choices[0]!.message.content!);Multiple images per turn
const res = await cr.chat({
model: 'mistral/pixtral-large-latest',
messages: [
{
role: 'user',
content: [
{ type: 'text', text: 'Are these two screenshots from the same app?' },
{ type: 'image_url', image_url: { url: 'https://example.com/a.png' } },
{ type: 'image_url', image_url: { url: 'https://example.com/b.png' } },
],
},
],
});Hard caps:
- 20 images per request.
- 20 MB total across all images.
Detail levels
The detail knob trades quality for token cost:
| Value | Behaviour |
|---|---|
low | Single low-res tile. Fast, cheap, less recall. |
high | Multiple high-res tiles. Best recall, more tokens. |
auto | Provider picks based on image dimensions. |
Vision-capable models
Set kind=chat and filter by capability:
const visionModels = await cr.listModels({ kind: 'chat' });
const supportsVision = visionModels.filter(
(m) => (m as any).capabilities?.includes('vision'),
);Today's recommended picks:
| Model | Provider | Notes |
|---|---|---|
mistral/pixtral-large-latest | Scaleway | Strong general vision |
meta/llama-3.2-90b-vision | Tensorix | Open-weight, good OCR |
qwen/qwen2.5-vl-72b | Scaleway | Bilingual EN/CN, charts |
No GPT-4o vision
OpenAI's vision models aren't hosted inside the EU, so they're not in the catalog. Pixtral and Llama 3.2 Vision cover the same use-cases.
Common gotchas
- CORS on
image_url. The gateway fetches the URL server-side, so browser CORS doesn't apply — but the URL must be reachable from EU IPs. - PDFs are not images. Convert to PNG or split into pages first.
- Don't paste massive base64 into the message. Resize to ~1500px long edge first — vision models don't gain accuracy past that and your token bill grows linearly.
Related
- Chat completions — base request shape.
- Cookbook: voice bot — multimodal pipeline.
- Model catalog — capability filters.