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

# Images

> Send images alongside text to multimodal models on the Responses and Chat Completions APIs

Multimodal models on Valar read images mixed in with your text. You attach each image one of two ways - a public URL that Valar fetches, or a base64 data URI you embed directly - and the model treats it as part of the prompt.

Two things decide whether a request works: the model has to be multimodal, and each image has to fall inside the size and format limits below.

## Check the model supports images

Vision is a per-model capability. Look for a check in the **Image** column on the [Models](/models) page, or call `GET /v1/models` to confirm at runtime. Sending image content to a text-only model fails fast:

<Warning>
  A non-multimodal model returns `400` with `this model does not support image input`. No tokens are charged.
</Warning>

## Attach an image

The content block is shaped to match whichever API you're already calling. Each tab shows the URL form first, then the inline base64 form.

<Tabs>
  <Tab title="Responses">
    Add an `input_image` part to the message `content`. Its `image_url` takes either a public URL or a `data:` URI, and the optional `detail` field accepts `"auto"`, `"low"`, or `"high"`.

    ```python theme={"system"}
    from openai import OpenAI

    client = OpenAI(base_url="https://api.valarhq.ai/v1", api_key="YOUR_VALAR_API_KEY")

    response = client.responses.create(
        model="moonshotai/Kimi-K2.7",
        input=[
            {
                "role": "user",
                "content": [
                    {"type": "input_text", "text": "What's on the sign in this photo?"},
                    {"type": "input_image", "image_url": "https://example.com/storefront.jpg"},
                ],
            }
        ],
    )
    print(response.output_text)
    ```

    To send the bytes yourself, base64-encode the file into a data URI and pass it in the same field:

    ```python theme={"system"}
    import base64, pathlib

    b64 = base64.b64encode(pathlib.Path("storefront.jpg").read_bytes()).decode()
    image = {"type": "input_image", "image_url": f"data:image/jpeg;base64,{b64}"}
    ```
  </Tab>

  <Tab title="Chat Completions">
    Use the OpenAI `image_url` content part, where `url` holds the public URL or data URI:

    ```python theme={"system"}
    response = client.chat.completions.create(
        model="moonshotai/Kimi-K2.7",
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "text", "text": "What's on the sign in this photo?"},
                    {"type": "image_url", "image_url": {"url": "https://example.com/storefront.jpg", "detail": "auto"}},
                ],
            }
        ],
    )
    ```

    The same `url` field accepts a data URI - `{"url": f"data:image/jpeg;base64,{b64}"}` - for inline bytes.
  </Tab>
</Tabs>

## Limits

| Limit              | Value                                                                                                  |
| ------------------ | ------------------------------------------------------------------------------------------------------ |
| Images per request | 20                                                                                                     |
| Size per image     | 20 MB, measured on the decoded bytes (not the base64 string)                                           |
| Formats            | JPEG, PNG, WebP, GIF                                                                                   |
| Pixel dimensions   | No cap - the 20 MB ceiling is the real bound; oversized images may be resized or tiled before decoding |
| URL fetch          | `http`/`https` only, reachable on the public internet; Valar gives up after 10 s                       |

## When an image is rejected

Most problems come back as a `400` with a JSON error whose `message` names the failing check:

| Status | Message                                               | Cause                                                   |
| ------ | ----------------------------------------------------- | ------------------------------------------------------- |
| `400`  | `this model does not support image input`             | The model isn't multimodal.                             |
| `400`  | `a request may include at most 20 images`             | More than 20 images in one request.                     |
| `400`  | `each image must be at most 20 MB`                    | An image exceeded 20 MB once decoded.                   |
| `400`  | `image format must be JPEG, PNG, WebP, or GIF`        | The format isn't one Valar accepts.                     |
| `400`  | `image_url must be an https URL or a base64 data URI` | The `image_url` wasn't an `https` URL or a `data:` URI. |

Two fetch failures happen **below** the JSON layer, so branch on the HTTP status rather than the body:

* **`403 Forbidden`** - a referenced URL blocked at the edge comes back as an HTML page from the WAF, not a JSON error.
* **Upstream fetch error** - when Valar can't retrieve a referenced image (unreachable host, timeout, or a non-200 response), the request fails with a generic provider error, sometimes a `503`.

## URL or base64?

Either works - pick based on where the bytes already are:

* **Base64** when you already hold the file (uploads, generated images). It skips a fetch and avoids exposing a URL, at the cost of a larger request body.
* **URL** when the image is already hosted somewhere public. Smaller payload, but Valar has to reach it within 10 seconds.

Either way, image bytes are **never cached between requests** - every call re-sends or re-fetches its images, so reusing the same image across turns pays the transfer each time.
