> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tinfoil.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Image processing

> Learn how to use Tinfoil for image processing with multimodal models.

## Image Upload

<Warning>
  **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.
</Warning>

<Info>
  See the [vision models](/models/vision) and [chat models](/models/chat) pages for complete model specifications and multimodal capabilities.
</Info>

### 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:

<CodeGroup>
  ```bash Command Line theme={"dark"}
  # 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
  ```

  ```python Python theme={"dark"}
  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")
  ```

  ```javascript JavaScript theme={"dark"}
  import fs from 'fs';

  function imageToBase64(imagePath) {
      const imageBuffer = fs.readFileSync(imagePath);
      return imageBuffer.toString('base64');
  }

  // Usage
  const base64String = imageToBase64('image.jpg');
  ```

  ```go Go theme={"dark"}
  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
  }
  ```

  ```rust Rust theme={"dark"}
  // 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())]);
  ```
</CodeGroup>

### API Usage

<CodeGroup>
  ```python Python theme={"dark"}
  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)
  ```

  ```typescript JavaScript theme={"dark"}
  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);
  }
  ```

  ```go Go theme={"dark"}
  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)
  }
  ```

  ```rust Rust theme={"dark"}
  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(())
  }
  ```
</CodeGroup>

### Best Practices

1. **Image Size**: For optimal performance, resize large images before processing (recommended max: 4096x4096)
2. **Base64 Encoding**: Ensure proper base64 encoding and include the correct MIME type in the data URL
3. **Multiple Images**: You can include multiple images in a single chat completion by adding multiple image\_url objects to the content array
4. **Compression**: Consider compressing large images to reduce payload size and improve response times
