|

Vision Endpoint

Analyze, describe, OCR, and classify images with GPT-4o, Claude, and Gemini.

Send an image (URL, base64, or file upload) and get back rich analysis — description, OCR, classification, or an answer to your own prompt. Routes across OpenAI, Anthropic, and Google vision-capable models.

Endpoints

PathPurpose
POST /api/ai/visionGeneral analyze — provide your own prompt
POST /api/ai/vision/describeDescribe the image in rich detail
POST /api/ai/vision/ocrExtract all visible text
POST /api/ai/vision/classifyStructured JSON: tags, categories, objects

Basic Request

const result = await cencori.vision.analyze({
  image: { url: 'https://example.com/photo.jpg' },
  prompt: 'What breed of dog is this?'
});
 
console.log(result.analysis);

From a local file (browser)

const b64 = await fileToBase64(file);
const result = await cencori.vision.analyze({
  image: { base64: b64, mimeType: file.type }
});

From a local file (Node.js)

import fs from 'node:fs';
const buf = fs.readFileSync('receipt.png');
const result = await cencori.vision.ocr({
  image: { base64: buf.toString('base64'), mimeType: 'image/png' }
});
console.log(result.text);

Request Parameters

ParameterTypeRequiredDescription
image_urlstringone of thesehttps:// or data: URL
image_base64stringone of theseRaw base64 (no data: prefix)
mime_typestringwith image_base64e.g. image/png
promptstringNoCustom instruction. Ignored on /describe, /ocr, /classify presets
modelstringNoDefaults to gpt-4o-mini
max_tokensnumberNoResponse length cap
temperaturenumberNoSampling temperature
response_format"text" | "json"NoForce JSON mode
streambooleanNoReturn SSE stream instead of a single response

Response

{
  analysis: "The image shows a golden retriever puppy...",
  model: "gpt-4o-mini",
  provider: "openai",
  usage: {
    promptTokens: 1103,
    completionTokens: 87,
    totalTokens: 1190
  },
  cost: {
    providerCostUsd: 0.000217,
    cencoriChargeUsd: 0.000326,
    markupPercentage: 50
  }
}

/describe returns description instead of analysis. /ocr returns text. /classify returns classification (parsed JSON when the model returns valid JSON) plus the raw string in raw.

Multiple images

Pass an images array instead of a single image to analyze several images together. All three providers support multi-image input.

// Compare two product photos
const result = await cencori.vision.analyze({
  images: [
    { url: 'https://cdn.example.com/product-a.jpg' },
    { url: 'https://cdn.example.com/product-b.jpg' },
  ],
  prompt: 'Compare these two products. Which looks better lit and why?',
});
// Multi-page image-based document
const pages = await rasterizePdf(pdfBuffer); // your own helper
const { text } = await cencori.vision.ocr({
  images: pages.map(buf => ({ base64: buf.toString('base64'), mimeType: 'image/png' })),
});

Multipart uploads also support multi-image: send multiple file fields (or a single files[] field with several files):

curl -X POST https://cencori.com/api/ai/vision \
  -H "CENCORI_API_KEY: csk_..." \
  -F "file=@before.jpg" \
  -F "file=@after.jpg" \
  -F "prompt=What changed between these two photos?"

Per-image validation runs before the provider call, so oversize/wrong-format errors name the specific offender.

Use cases: before/after comparison, multi-angle product tagging, multi-page image-based documents, chart-plus-reference-doc analysis, signature verification against a reference.

Streaming

Set stream: true to receive a Server-Sent Events stream of text deltas. The final data: line before [DONE] contains the usage + cost summary.

const res = await fetch('https://cencori.com/api/ai/vision', {
  method: 'POST',
  headers: {
    'CENCORI_API_KEY': 'csk_...',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    image_url: 'https://example.com/photo.jpg',
    prompt: 'Describe this scene',
    stream: true,
  }),
});
 
const reader = res.body!.getReader();
const decoder = new TextDecoder();
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  const text = decoder.decode(value);
  // Parse SSE frames: `data: {...}\n\n`
  for (const line of text.split('\n')) {
    if (line.startsWith('data: ') && line !== 'data: [DONE]') {
      const chunk = JSON.parse(line.slice(6));
      if (chunk.delta) process.stdout.write(chunk.delta);
    }
  }
}

Supported Models

ProviderModelsDefault
OpenAIgpt-4o, gpt-4o-mini, gpt-4-turbogpt-4o-mini
Anthropicclaude-sonnet-4-5, claude-3-5-sonnet-latest, claude-3-5-haiku-latest
Googlegemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite

Image Format Limits

ProviderFormatsMax size per image
OpenAIJPEG, PNG, WEBP, GIF20MB
AnthropicJPEG, PNG, WEBP, GIF5MB
GoogleJPEG, PNG, WEBP, GIF, HEIC, HEIF20MB

Universal safe set: JPEG, PNG, WEBP, GIF. Requests that violate the provider's limit return a structured 400 with error: "unsupported_format" or "image_too_large".

Multipart file upload

The endpoints also accept multipart/form-data for direct file upload:

curl -X POST https://cencori.com/api/ai/vision \
  -H "CENCORI_API_KEY: csk_..." \
  -F "file=@photo.jpg" \
  -F "prompt=What's in this image?" \
  -F "model=gpt-4o"

Drop-in React component

The cencori/react subpath ships a <VisionUploader /> component you can embed in your own product — drag-drop, client-side format validation, auto-downscale for oversize JPEG/PNG/WEBP, clear error banner:

import { VisionUploader } from 'cencori/react';
 
<VisionUploader
  endpoint="https://cencori.com/api/ai/vision"
  apiKey={process.env.NEXT_PUBLIC_CENCORI_KEY}
  task="describe"
  onResult={(result) => console.log(result)}
/>

HTTP API

curl -X POST https://cencori.com/api/ai/vision \
  -H "CENCORI_API_KEY: csk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "image_url": "https://example.com/receipt.png",
    "prompt": "Extract the total amount as JSON",
    "response_format": "json"
  }'

In other SDKs

Python

from cencori import Cencori
 
cencori = Cencori()
 
# Analyze
result = cencori.vision.analyze(
    image_url="https://example.com/photo.jpg",
    prompt="What breed of dog is this?",
)
print(result["analysis"])
 
# OCR from a local file
import base64
with open("receipt.png", "rb") as f:
    b64 = base64.b64encode(f.read()).decode()
ocr = cencori.vision.ocr(image_base64=b64, mime_type="image/png")
print(ocr["text"])

Async variants: a_analyze(), a_describe(), a_ocr(), a_classify(). Full example: examples/vision.py.

Go

import (
    "context"
    "encoding/base64"
    "os"
 
    "github.com/cencori/cencori-go"
)
 
client, _ := cencori.NewClient(cencori.WithAPIKey(os.Getenv("CENCORI_API_KEY")))
 
// Analyze
result, _ := client.Vision.Analyze(context.Background(), &cencori.VisionParams{
    ImageURL: "https://example.com/photo.jpg",
    Prompt:   "What breed of dog is this?",
})
fmt.Println(result.Analysis)
 
// OCR from a local file
data, _ := os.ReadFile("receipt.png")
ocr, _ := client.Vision.OCR(context.Background(), &cencori.VisionParams{
    ImageBase64: base64.StdEncoding.EncodeToString(data),
    MimeType:    "image/png",
})
fmt.Println(ocr.Text)

Full example: examples/12-vision.

PHP

use Cencori\Cencori;
 
$cencori = new Cencori();
 
// Analyze
$result = $cencori->vision->analyze([
    'image_url' => 'https://example.com/photo.jpg',
    'prompt' => 'What breed of dog is this?',
]);
echo $result['analysis'];
 
// OCR from a local file
$b64 = base64_encode(file_get_contents('receipt.png'));
$ocr = $cencori->vision->ocr(['image_base64' => $b64, 'mime_type' => 'image/png']);
echo $ocr['text'];

Full example: examples/vision.php.

Rust

use cencori::vision::VisionParams;
use cencori::Cencori;
 
let cencori = Cencori::new(None, None, None)?;
 
// Analyze
let params = VisionParams::from_url("https://example.com/photo.jpg")
    .with_prompt("What breed of dog is this?");
let result = cencori.vision.analyze(&params)?;
println!("{}", result.analysis);
 
// OCR: build with VisionParams::from_base64(base64_string, "image/png")
let ocr = cencori.vision.ocr(&VisionParams::from_base64(b64, "image/png"))?;
println!("{}", ocr.text);

Async variants: cencori.vision.async_analyze(&params).await. Full example: examples/vision.rs.

Also: automatic vision routing in chat

You don't have to use /api/ai/vision directly to get vision. If you send image content through the regular /api/ai/chat endpoint, Cencori auto-detects the image, transparently routes through Vision, and even upgrades the model if the one you picked doesn't support vision (claude-3-5-haikuclaude-3-5-sonnet-latest, gpt-3.5-turbogpt-4o-mini, etc.). See Vision Auto-Routing in Chat.

Use /api/ai/vision directly when:

  • You want OCR / describe / classify presets
  • You want structured JSON classification
  • You want the multipart file upload path
  • You want cleaner cost tracking against a Vision endpoint

Use /api/ai/chat with images when:

  • You're extending an existing chat flow
  • You want a model-agnostic path that "just works" no matter which model your users pick

Errors

errorMeaning
unsupported_formatImage mime type isn't supported by the selected provider
image_too_largeImage exceeds the provider's max size
provider_not_configuredNo API key set for the selected provider
bad_requestMissing image_url / image_base64, or malformed body