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

EndpointPOST /v1/chat/completions (same as text-only chat)
Recommended modelmistral/pixtral-large-latest
Max images per request20
Max total size20 MB
Detail levelslow, high, auto (default: auto)

Image from URL

vision-url.ts
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:

vision-base64.ts
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:

ValueBehaviour
lowSingle low-res tile. Fast, cheap, less recall.
highMultiple high-res tiles. Best recall, more tokens.
autoProvider 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:

ModelProviderNotes
mistral/pixtral-large-latestScalewayStrong general vision
meta/llama-3.2-90b-visionTensorixOpen-weight, good OCR
qwen/qwen2.5-vl-72bScalewayBilingual 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.