Image Upload
Multimodal Models Only: Image processing requires models with vision capabilities. Currently, Kimi K3, Qwen3-VL 30B, and Gemma 4 31B support image inputs. Other models (Llama, GPT-OSS) are text-only and cannot process images.
See the vision models and chat models pages for complete model specifications and multimodal capabilities.
How It Works
Image processing works through the chat/completions endpoint using base64-encoded images. Images are sent as data URLs in the message content alongside your text prompt.Converting Images to Base64
There are several ways to convert your images to base64 format:# Convert image to base64
base64 -i image.jpg -o image_base64.txt
# Or use it directly in your terminal
base64 image.jpg | pbcopy # Copies to clipboard on macOS
import base64
def image_to_base64(image_path):
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
# Usage
base64_string = image_to_base64("image.jpg")
import fs from 'fs';
function imageToBase64(imagePath) {
const imageBuffer = fs.readFileSync(imagePath);
return imageBuffer.toString('base64');
}
// Usage
const base64String = imageToBase64('image.jpg');
package main
import (
"encoding/base64"
"fmt"
"io"
"log"
"os"
)
func imageToBase64(imagePath string) (string, error) {
file, err := os.Open(imagePath)
if err != nil {
return "", err
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(data), nil
}
func main() {
// Usage
base64String, err := imageToBase64("image.jpg")
if err != nil {
log.Fatal(err)
}
fmt.Println("Base64 encoded image:", base64String[:50], "...") // Print first 50 chars
}
// The Tinfoil Rust SDK ships ImageUrl::from_path which reads the file,
// detects the MIME type, and returns a ready-to-use ImageUrl with a
// base64 data URL. Use it directly in chat messages — no manual encoding.
use tinfoil::chat::ImageUrl;
use tinfoil::multimodal::ImageUrlExt;
let image = ImageUrl::from_path("image.jpg")?;
println!("Base64 data URL: {}...", &image.url[..50.min(image.url.len())]);
API Usage
from tinfoil import TinfoilAI
import base64
import mimetypes
from pathlib import Path
# Initialize client with Qwen3-VL vision model
client = TinfoilAI(
api_key="<YOUR_API_KEY>",
)
# Read and encode image with proper MIME type detection
image_path = "image.jpg"
with open(image_path, "rb") as image_file:
base64_image = base64.b64encode(image_file.read()).decode('utf-8')
# Determine MIME type
mime_type, _ = mimetypes.guess_type(image_path)
if not mime_type or not mime_type.startswith('image/'):
# Fallback based on file extension
ext = Path(image_path).suffix.lower()
mime_type_map = {
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif',
'.webp': 'image/webp'
}
mime_type = mime_type_map.get(ext, 'image/jpeg')
# Create completion with multimodal content
response = client.chat.completions.create(
model="qwen3-vl-30b",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{
"type": "image_url",
"image_url": {
"url": f"data:{mime_type};base64,{base64_image}"
}
}
]
}]
)
print(response.choices[0].message.content)
import { TinfoilAI } from 'tinfoil';
import fs from 'fs';
import path from 'path';
// Initialize client with Qwen3-VL vision model
const client = new TinfoilAI({
apiKey: process.env.TINFOIL_API_KEY
});
// Read and encode image
const imagePath = 'image.jpg';
const imageBuffer = fs.readFileSync(imagePath);
const base64Image = imageBuffer.toString('base64');
// Determine MIME type
const ext = path.extname(imagePath).toLowerCase();
const mimeTypes: Record<string, string> = {
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif',
'.webp': 'image/webp'
};
const mimeType = mimeTypes[ext] || 'image/jpeg';
try {
const response = await client.chat.completions.create({
model: 'qwen3-vl-30b',
messages: [
{
role: 'user',
content: [
{ type: 'text', text: "What's in this image?" },
{
type: 'image_url',
image_url: {
url: `data:${mimeType};base64,${base64Image}`
}
}
] as any
}
]
});
console.log(response.choices[0]?.message?.content);
} catch (error) {
console.error('Image processing failed:', error);
}
package main
import (
"context"
"encoding/base64"
"fmt"
"log"
"mime"
"os"
"path/filepath"
"strings"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/option"
"github.com/tinfoilsh/tinfoil-go"
)
func main() {
// Initialize client with Qwen3-VL vision model
client, err := tinfoil.NewClient(
option.WithAPIKey(os.Getenv("TINFOIL_API_KEY")),
)
if err != nil {
log.Fatal(err)
}
// Read and encode image
imagePath := "image.jpg"
imageData, err := os.ReadFile(imagePath)
if err != nil {
log.Fatal(err)
}
base64Image := base64.StdEncoding.EncodeToString(imageData)
// Determine MIME type
ext := filepath.Ext(imagePath)
mimeType := mime.TypeByExtension(ext)
// Robust fallback for MIME type
if mimeType == "" || !strings.HasPrefix(mimeType, "image/") {
// Fallback based on file extension
mimeTypeMap := map[string]string{
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
".webp": "image/webp",
}
if fallbackType, ok := mimeTypeMap[strings.ToLower(ext)]; ok {
mimeType = fallbackType
} else {
mimeType = "image/jpeg" // final fallback
}
}
// Create completion with multimodal content
imageURL := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Image)
response, err := client.Chat.Completions.New(context.TODO(), openai.ChatCompletionNewParams{
Model: "qwen3-vl-30b",
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage([]openai.ChatCompletionContentPartUnionParam{
openai.TextContentPart("What's in this image?"),
openai.ImageContentPart(openai.ChatCompletionContentPartImageImageURLParam{
URL: imageURL,
}),
}),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Choices[0].Message.Content)
}
use tinfoil::chat::{
ChatCompletionRequestMessage, ChatCompletionRequestMessageContentPartImage,
ChatCompletionRequestUserMessage, ChatCompletionRequestUserMessageContent,
ChatCompletionRequestUserMessageContentPart, CreateChatCompletionRequestArgs, ImageUrl,
};
use tinfoil::multimodal::ImageUrlExt;
use tinfoil::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new_default().await?;
// ImageUrl::from_path handles base64 encoding and MIME detection.
let image = ImageUrl::from_path("image.jpg")?;
let request = CreateChatCompletionRequestArgs::default()
.model("qwen3-vl-30b")
.messages(vec![ChatCompletionRequestMessage::User(
ChatCompletionRequestUserMessage {
content: ChatCompletionRequestUserMessageContent::Array(vec![
ChatCompletionRequestUserMessageContentPart::Text(
"What's in this image?".into(),
),
ChatCompletionRequestUserMessageContentPart::ImageUrl(
ChatCompletionRequestMessageContentPartImage { image_url: image },
),
]),
name: None,
},
)])
.build()?;
let response = client.chat().create(request).await?;
println!("{}", response.choices[0].message.content.as_deref().unwrap_or(""));
Ok(())
}
Best Practices
- Image Size: For optimal performance, resize large images before processing (recommended max: 4096x4096)
- Base64 Encoding: Ensure proper base64 encoding and include the correct MIME type in the data URL
- Multiple Images: You can include multiple images in a single chat completion by adding multiple image_url objects to the content array
- Compression: Consider compressing large images to reduce payload size and improve response times

